2010-02-04 5 views
12

Come indicato nel titolo, quando si progettano database, qual è il modo preferito per gestire tabelle che hanno più colonne che stanno semplicemente memorizzando valori vero/falso come solo una singola o valore (ad esempio "S/N: o" 0/Allo stesso modo, ci sono alcuni problemi che potrebbero sorgere tra database diversi (ad esempio Oracle e SQL Server) che potrebbero influenzare il modo in cui vengono gestite le colonne?Durante la progettazione di database, qual è il modo preferito per memorizzare più valori vero/falso?

risposta

14

In SQL Server, c'è il tipo di dati BIT. È possibile memorizzare 0 o 1 lì, confrontare i valori ma non eseguire MIN o MAX.

In Oracle, è sufficiente utilizzare NUMBER o CHAR(1).

In MySQL e PostgreSQL qualsiasi tipo di dati è implicitamente convertibile in BOOLEAN.

Entrambi i sistemi supportano BOOLEAN tipo di dati che si può usare come è, senza gli operatori, nelle clausole WHERE o ON:

SELECT * 
FROM mytable 
WHERE col1 

, che è impossibile in SQL Server e Oracle (è necessario avere un qualche tipo o un predicato lì).

In MySQL, BOOLEAN è un sinonimo di TINYINT(1).

Anche in PostgreSQL (in termini di spazio di archiviazione), ma logicamente, non è implicitamente convertibile in nessun altro tipo.

+0

C'è qualche problema di progettazione con l'avere più campi, o sono considerati buoni così com'è? Ad esempio, dovrebbero essere uniti e fare in modo che l'applicazione utilizzi operazioni bit a bit? – rjzii

+7

Un'aggiunta: se si utilizzano più colonne BIT in una tabella in SQL Server, le combinerà insieme. Ad esempio, se si dispone di una singola colonna BIT, utilizzerà la memorizzazione a 1 byte. Ma se hai 8 colonne BIT, userà ancora 1 byte per memorizzarle entrambe. D'altra parte, Oracle alloca 1 byte per ciascuno. –

+0

@Ekin: Nice point – Quassnoi

0

Le colonne di bit vengono in genere utilizzate per rappresentare T/F o Y/N digitare valori, almeno in SQL Server. Sebbene un purista del database possa dirti che le colonne di bit non hanno spazio nei database perché sono "troppo vicine all'hardware" - Joe Celko.

+0

Hai una citazione per il bit "Troppo vicino all'hardware"? –

2

Utilizzare qualsiasi cosa abbia senso per il database specifico motore che stai utilizzando. È l'interfaccia per il database che ha bisogno di gestirlo. Se l'interfaccia lato codice del database è sufficientemente modulare, allora non sarà nulla di più di una semplice modifica di una riga per gestire un tipo booleano diverso nel database sottostante.

+0

Mentre è vero, penso che la domanda non riguardi questo. Credo che, come in molti casi, l'interrogante abbia nascosto il vero motivo della sua domanda, che sta scrivendo un'app che supporterà più di un fornitore di database ... quindi ha bisogno di fare la scelta meno problematica dei tipi di dati. –

5

Dalla mia esperienza, preferisco char (1) per 'Y' o 'N'. Usare 0 e 1 può essere un po 'confuso a seconda di quante birre ho già bevuto e la funzione main + di C++ restituisce 0 in caso di successo. I tipi ENUM e BIT sono più problemi di quanti ne valgano.

È interessante notare che MySQL information_schema utilizza VARCHAR (3) per "SÌ" o "NO".

Esempio:

information_schema.USER_PRIVILEGES (
    ... 
    IS_GRANTABLE VARCHAR(3) NOT NULL DEFAULT '' 
) 
+1

L'unico svantaggio è che l'uso di un tipo 'CHAR' consente possibili valori che includono 'Y' e 'N', AND '1' e '0'. Nel tuo codice tu (o un altro sviluppatore) potresti scrivere 'if (myTrueFalseFieldFromDatabase.equals (" 1 ")) {...}', quando intendi comparare con '" Y "'. Il vantaggio dell'uso di un tipo 'INT' o' BIT' è che sono consentiti solo uno e zero, quindi non puoi fare questo errore. Ma fintanto che stai attento, questo non è un gran problema. – Tenner

+5

Francamente, sconsiglioi di usare ENUM ('Y', 'N') semplicemente perché entrambi si traducono in 1/true in altri linguaggi di programmazione. Se usi 0 e 1, anche se è solo ENUM (1,0) invece di BIT/BOOLEAN, lo 0 si tradurrà in falso in linguaggi "soft" come Perl, PHP, ecc. 0 e 1 sono i classici modi di denotare valori booleani e saranno interpretati in modo accurato dalla maggior parte dei linguaggi di programmazione. – Teekin

+1

@Tenner: un INT di solito può memorizzare più valori di solo 0 o 1. Se si mette un vincolo di controllo sulla colonna (supponendo che MySQL li supporti), allora i valori consentiti possono essere solo 'Y' e 'N' (o '1' e '0'). Vorrei andare con Y/N, ma non c'è un grosso problema con 1/0 invece - in un campo CHAR (1). –

4

Invece di tipi di dati booleani, si può prendere in considerazione un altro modello di dati per la memorizzazione di valori booleani, che può essere particolarmente appropriato nei seguenti casi:

  • quando si vuole avere molte colonne sì/no.
  • quando è necessario aggiungere più colonne sì/no in futuro.
  • quando i valori sì/no non cambiano molto frequentemente.

La definizione delle autorizzazioni utente può essere un tipico esempio di quanto sopra. Prendere in considerazione le seguenti tabelle:

Table "Users":    (user_id, name, surname, country) 

Table "Permissions":  (permission_id, permission_text) 

Table "Users_Permissions": (user_id, permission_id) 

Nella tabella Permissions definirebbe tutte le possibili autorizzazioni che potrebbero essere applicabili per gli utenti. Dovresti aggiungere una riga alla tabella Permissions per ciascun attributo sì/no. Come avrai notato, questo rende molto facile aggiungere nuove autorizzazioni in futuro senza dover modificare lo schema del database.

Con il modello sopra, si dovrebbe quindi indicare un valore VERO assegnando un user_id con un permission_id nella tabella Users_Permissions. Altrimenti sarebbe FALSE per impostazione predefinita.

Ad esempio:

Table "Permissions" 

permission_id text 
----------------------------------- 
1    "Read Questions" 
2    "Answer Questions" 
3    "Edit Questions" 
4    "Close Questions" 


Table "Users_Permissions" 

user_id   permission_id 
----------------------------------- 
1    1 
1    2 
1    3 
2    1 
2    3 

Vantaggi

  • indicizzazione: si può facilmente utilizzare un indice di query per fatti specifici.
  • Spazio: la convenzione di default salva spazio quando si hanno molti valori falsi.
  • Normalizzato: i fatti sono definiti nelle rispettive tabelle (nelle tabelle Permissions e Users_Permissions). È possibile memorizzare facilmente più informazioni su ciascun fatto.

Svantaggi

  • Query: query semplici richiederebbe join.
  • impostazione in False: Per impostare un valore su false, si dovrà eliminare una riga In caso contrario è possibile utilizzare un flag 'eliminato' nella tabella Users_Permissions, che sarebbe anche permettere di (dalla tabella Users_Permissions.) memorizzare le informazioni per gli audit trail, ad esempio quando un permesso è stato modificato e da chi. Se elimini la riga, non saresti in grado di memorizzare queste informazioni.
-1

Normalmente farei questo senza valori BIT/BOOLEAN. Invece avrei tre tavoli. Diciamo che abbiamo un sistema di gestione dei progetti con progetti e questi progetti hanno un sacco di attributi.

Poi abbiamo i tavoli:

 
Project 
- Project_ID (INT), 
- Name (VARCHAR) 

Attribute 
- Attribute_ID (INT), 
- Name (VARCHAR) 

ProjectAttribute_Rel 
- Project_ID (INT), 
- Attribute_ID (INT) 

Sia l'attributo di un progetto è vera o falsa dipende dal fatto che c'è una linea per esso in ProjectAttribute_Rel.

In genere, devi avere a che fare con l'Attribute_IDs nel codice, in modo che quando si leggono gli attributi del progetto (dove si ha presumibilmente l'project_id), basta fare (PHP arbitrario usato come esempio):

$arrAttributes = array(); 
$oQuery = mysql_query(' 
    SELECT Attribute_ID 
    FROM ProjectAttribute_Rel 
    WHERE Project_ID = '.addslashes($iProjectId).' 
'); 
while ($rowAttribute = mysql_fetch_assoc($oQuery)) { 
    $arrAttributes[] = $rowAttribute['Attribute_ID']; 
} 

A questo punto, è possibile verificare se l'attributo del progetto è true controllando se esiste in $ arrAttributes. In PHP, questo sarebbe:

if (in_array($arrAttributes, $iAttributeId)) { 
    // Project attribute is true! 
} 

Questo approccio permette anche di fare ogni sorta acrobazie per evitare di elencare una pletora di attributi quando si aggiorna, ancora una volta quando si seleziona (perché SELECT * è un male in codice), quando inserisci e così via. Questo perché puoi sempre passare in rassegna l'attributo table per trovare gli attributi disponibili, quindi se ne aggiungi uno e fai le cose in questo modo, l'aggiunta/modifica/eliminazione degli attributi è banale. Le probabilità sono che il tuo SQL non avrà nemmeno bisogno di essere cambiato, perché gli attributi stessi sono definiti nel database, non nel codice.

Spero che questo aiuti.

+2

OMG, più persone raccomandano costrutti EAV. Sparami ora –

+0

Ma invece di tirare semplicemente i campi da un record in un set di risultati, dobbiamo scorrere i risultati cercando i campi che vogliamo. Un nome di campo errato è difficile da distinguere da un campo con un valore nullo. Invece che lo schema è, per definizione, un elenco definitivo di quali attributi si applicano a una determinata tabella, non esiste un elenco definitivo da nessuna parte. (Qualcuno potrebbe scriverlo su un pezzo di carta, ma chi assicurerà che sia attuale.) Io penserei che questa sia una soluzione a una categoria di problemi molto specificata, non qualcosa che vorresti fare in generale. – Jay

+0

Mi dispiace se questo è un po 'scortese, ma secondo la mia personale opinione, i nomi dei campi errati sono facilmente individuabili osservandoli. Non considero questo problema più reale del problema della "semi-virgola mancante". Ovviamente ci sono alcuni vantaggi rispetto agli attributi semplicemente codificanti, ma l'ultima volta che ho controllato, l'hard-coding di queste cose è stato scoraggiato proprio perché poi sono scritte in pietra. Per ottenere un elenco di attributi, "SELECT Name FROM Attributes;" - Non riesco a vedere il problema. Per favore, i disaccordi dei geek sembrano sempre più ostili di quello che sono. :) – Teekin

2

Se il DBMS supporta un tipo di dati booleani, come MySQL, utilizzarlo. Se non lo fa, come Oracle, di solito uso un char (1) con valori di Y o N. In quest'ultimo caso, è una buona idea scrivere un paio di funzioni per convertire Java o C++ o qualsiasi tipo booleano in e da Y/N in modo da evitare di avere codice ridondante per farlo. È una funzione piuttosto banale, ma dovrà trattare casi come valori nulli o valori diversi da Y o N e si desidera farlo in modo coerente.

NON aggiungerei i flag in una singola variabile con operazioni di bit. Sì, questo farà risparmiare spazio su disco, ma il prezzo è molto più complesso e offre opportunità di errore. Se il tuo DBMS non supporta le operazioni di bit - e come non ho mai avuto il desiderio di fare una cosa del genere, non so in cima alla mia testa che, se ce ne sono, fare - allora avrai un vero momento difficile selezionando o ordinando basato su una tale bandiera. Certo, è possibile recuperare tutti i record che soddisfano altri criteri e quindi fare in modo che il codice chiamante elimini quelli con il valore di flag corretto. Ma cosa succede se solo una piccola percentuale dei record ha il valore di flag desiderato e hai una query che unisce molti altri record? Come "selezionare employee.name, sum (pay.amount) da employee join pay utilizzando (employee_id) dove employee.executive = true e pay.bonus = true". Con la clausola where, probabilmente si recupera un numero molto modesto di record. Senza di esso, si recupera l'intero database.

Lo spazio su disco è economico in questi giorni, quindi qualsiasi risparmio sul disco sarebbe probabilmente non importante. Se hai davvero un numero enorme di bandiere - come centinaia o migliaia di esse per record - suppongo che potrebbe esserci un caso per comprarle. Ma sarebbe in fondo alla mia lista di scelte di design.

Modifica: Consentitemi di elaborare una classe per convertire il vostro "SQL boolean" in "Java booleano". Lo stesso vale per qualsiasi lingua, ma userò Java come esempio.

Se il DBMS ha un tipo booleano incorporato, con Java è possibile leggerlo direttamente in una variabile booleana con ResultSet.getBoolean().

Ma se è necessario memorizzarlo come, ad esempio, un carattere "Y" o "N", è necessario leggerlo in una stringa. Quindi ha senso per me di dichiarare una classe come questa:

class MyBoolean 
{ 
    boolean value; 
    final static MyBoolean TRUE=new MyBoolean(true), FALSE=new MyBoolean(false); 
    public MyBoolean(boolean b) 
    { 
    value=b; 
    } 
    public MyBoolean(String s) 
    { 
    if (s==null) 
     return null; 
    else if (s.equals("Y")) 
     return MyBoolean.TRUE; 
    else 
     return MyBoolean.FALSE; 
    } 
    public static String toString(MyBoolean b) 
    { 
    if (b==null) 
     return null; 
    else if (b.value) 
     return "Y"; 
    else 
     reutrn "N"; 
    } 
    public String toString() 
    { 
    return toString(this); 
    } 
} 

allora si può facilmente raccogliere booleani dal database con "bandiera myBoolean = new myBoolean (rs.getString (" flag "));" e scrivere nel database con "rs.setString (" flag ", flag.toString()); "

E ovviamente è possibile aggiungere qualsiasi altra logica necessaria alla classe se si dispone di altre risorse booleane che è necessario eseguire. Se per alcuni scopi si desidera visualizzare booleani come T/F o Sì/No o On/Off o qualsiasi altra cosa, puoi semplicemente aggiungere varianti toString alternative aTFString o toString (valore, truetext, falsetext) o altro - piuttosto che scrivere codice simile più volte.

2

Penso che "Y/N" i valori sono più significativo di '1/0' Con Oracle, vorrei fare le seguenti operazioni per avere i dati validati, per quanto possibile dal motore di database:.

  • definire le colonne come cha R (1)
  • aggiungere un vincolo di controllo che eventuali valori sono limitati a "in ('Y', 'N')
  • se coerenti con le regole di business, li rendono non annullabile - questo può prevenire problemi quando si implicitamente scontato che tutto ciò che non è 'Y' ha un valore di 'N' nel vostro SQL
1

Invece di aggiungere una colonna, vi consiglio di creare un'altra tabella. Ascoltami ...

Supponiamo di avere una tabella denominata Customer:

CREATE TABLE Customer 
(
    CustomerID NUMBER, 
    Name  VARCHAR(100) 
) 

Ora, si supponga di voler indicare se un cliente è permesso di mostrare nei risultati di ricerca. Una possibilità è quella di aggiungere un po 'di colonna che rappresenta uno dei due possibili stati:

CREATE TABLE Customer 
(
    CustomerID NUMBER, 
    Name  VARCHAR(100), 
    Searchable BOOLEAN /* or CHAR(1) or BIT... */ 
) 

tua richiesta di ricerca avrà un aspetto simile a questo:

SELECT CustomerID, Name 
    FROM Customer 
WHERE Name LIKE '%TheCustomerNameIAmLookingFor%' 
    AND Searchable = TRUE /* or 'Y' or 0... */ 

Questo è bello e semplice. Molte persone su questo thread stanno dando buoni consigli per scegliere quale tipo di dati dovrebbe essere questa colonna per far sì che la sintassi funzioni bene su vari database.

Alternativa: creazione di una tabella separata

Invece di aggiungere un'altra colonna a Customer, creerò una tabella separata che memorizza il CustomerID di ogni cliente ricercabile.

CREATE TABLE Customer 
(
    CustomerID NUMBER, 
    Name  VARCHAR(100) 
) 

CREATE TABLE SearchableCustomer 
(
    CustomerID NUMBER 
) 

In questo caso, un cliente è considerato ricercabile se il loro CustomerID esiste nella tabella SearchableCustomer.La query per la ricerca di clienti diventa ora:

SELECT CustomerID, Name 
    FROM Customer 
WHERE Name LIKE '%TheCustomerNameIAmLookingFor%' 
    AND CustomerID IN (SELECT CustomerID FROM SearchableCustomer) 

Vedrete che questa strategia è molto trasferibili attraverso RDBMS:

  • trovare clienti ricercabili utilizza la clausola IN o un JOIN
  • Fare un cliente ricercabile utilizza la dichiarazione INSERT
  • Rendere un cliente non ricercabile utilizza la dichiarazione DELETE

Una Sorpresa beneficio

Tu sei libero di fare la definizione di un cliente ricercabile elaborati come si desidera se si effettua SearchableCustomer una vista invece di un tavolo:

CREATE VIEW SearchableCustomer AS 
SELECT CustomerID 
    FROM Customer 
WHERE Name LIKE 'S%' /* For some reason, management only cares about customers whose name starts with 'S' */ 

La tua ricerca la query non cambia affatto! : D Nella mia esperienza, questo ha portato a una straordinaria flessibilità.

+2

E 'a causa di cose come questa che gli ottimizzatori devono lavorare così duramente: ID cliente IN (SELEZIONA ID cliente da ricercabile cliente) –

+1

Hmm, non mi è chiaro il vero motivo per cui "dove customerid in (seleziona cliente da ricercabile)" è qualcosa che io Preferisco scrivere quindi "dove ricercabile = vero". E se ho 3 di questi campi, allora invece di 1 tabella ne ho 4 (il "master" più 3 "flag table"). Se sei preoccupato della portabilità cross-DBMS dei booleani, okay, usa solo un char (1) invece di dichiararlo booleano. – Jay

+0

@ Jay: abbastanza giusto. Tendo ad essere un po 'indietro per alcuni aspetti. :) Tendo a impostare le operazioni nelle query, quindi mi è sembrato naturale. Per la cronaca, questa strategia non è cresciuta a causa della necessità di tipi di dati booleani multipiattaforma. In realtà è nato dall'esigenza di un modo per centralizzare la definizione di un cliente "ricercabile" (vedi il "Surprise Benefit"). Nel nostro sistema c'erano diversi punti che richiedevano il refactoring se la nostra definizione diventava una funzione piuttosto che un valore di colonna. –

0

"SELECT * FROM tabella WHERE col1

, il che è impossibile in SQL Server e Oracle (è necessario avere un qualche tipo o di un predicato lì)."

Che va solo a mostrare ciò che un abominevole e ridicolo abominio Oracle e SQL Server sono davvero.

Se col1 è dichiarato di tipo BOOLEAN, l'espressione "col1" è un predicato.

Se la semantica della clausola WHERE richiede che la sua espressione valuti solo un valore di verità, e se alcune colonne sono dichiarate del tipo "valore di verità", allora "WHERE that-column" dovrebbe essere consentito e supportato. Periodo. Qualsiasi sistema che non esponga solo i suoi autori per i ciarlatani mediocri incompetenti che sono.

+0

wow, ridicolo e un abominio perché devi digitare un intero * 4 caratteri extra * per aggiungere un predicato dopo il nome di una colonna? ok ... –

+0

penso che qualcuno abbia un'ascia per macinare. Innanzitutto non è possibile definire una colonna booleana in Oracle. Quindi ovviamente non c'è WHERE boolcol. Non esiste Erwin, dacci la tua scelta in RDBMS, selezionerò 100 funzioni che Oracle ha che la tua preferita non fa. –