2010-06-20 5 views
7

Qualche tempo fa, ho letto il libro SQL and Relational Theory by C. J. Date. L'autore è noto per aver criticato la logica a tre valori di SQL (3VL). 1)Opzioni per eliminare colonne NULLable da un modello DB (per evitare la logica a tre valori di SQL)?

l'autore fa alcuni punti di forza sul perché 3VL deve essere evitato in SQL, tuttavia egli non descrive come un modello di database potrebbe apparire come se le colonne nullable non era permesso. Ci ho pensato un po 'e ho trovato le seguenti soluzioni. Se mi mancassero altre opzioni di design, mi piacerebbe sentir parlare di loro!

1) critica di Data di 3VL di SQL è a sua volta stato criticato troppo: vedi this paper by Claude Rubinson (include la critica originale di C. J. Data).


tabella Esempio:

Come esempio, assumere la seguente tabella dove abbiamo una colonna Null (DateOfBirth):

# +-------------------------------------------+ 
# |     People     | 
# +------------+--------------+---------------+ 
# | PersonID | Name  | DateOfBirth | 
# +============+--------------+---------------+ 
# | 1   | Banana Man | NULL   | 
# +------------+--------------+---------------+ 

Opzione 1: Emulazione di NULL tramite un flag e un valore predefinito:

Invece di rendere nullable la colonna, viene specificato qualsiasi valore predefinito (ad es. 1900-01-01). Una colonna aggiuntiva BOOLEAN specificherà se il valore in DateOfBirth deve semplicemente essere ignorato o se contiene effettivamente dati.

# +------------------------------------------------------------------+ 
# |        People'        | 
# +------------+--------------+----------------------+---------------+ 
# | PersonID | Name  | IsDateOfBirthKnown | DateOfBirth | 
# +============+--------------+----------------------+---------------+ 
# | 1   | Banana Man | FALSE    | 1900-01-01 | 
# +------------+--------------+----------------------+---------------+ 

Opzione 2: attivare una colonna Null in una tabella separata:

La colonna Null è sostituita da una nuova tabella (DatesOfBirth). Se un record non dispone di dati per quella colonna, non ci sarà un record nella nuova tabella:

# +---------------------------+ 1 0..1 +----------------------------+ 
# |   People'   | <-------> |   DatesOfBirth  | 
# +------------+--------------+   +------------+---------------+ 
# | PersonID | Name  |   | PersonID | DateOfBirth | 
# +============+--------------+   +============+---------------+ 
# | 1   | Banana Man | 
# +------------+--------------+ 

Anche se questo sembra la soluzione migliore, questo sarebbe forse tradursi in molte tabelle che devono essere uniti per una singola query. Dal momento che OUTER JOIN s non sarà consentito (perché introdurrebbe NULL nel set di risultati), tutti i dati necessari potrebbero non essere più recuperati con una singola query come prima.


Domanda: ci sono altre opzioni per eliminare NULL (e in caso affermativo, che cosa sono)?

+0

potresti spiegare brevemente perché la logica dei tre valori dovrebbe essere evitata. La ragione per cui sono consapevole è che devi conservare almeno un altro po '. Ma se si aggiunge un'altra colonna, non ha senso.Anche un'altra tabella porta al sovraccarico della query. Un'altra ragione per cui posso pensare è che devi gestire il valore NULL, ma lo hai anche con le tue soluzioni. – TooAngel

+0

@TooAngel: Non si tratta solo di dover archiviare un altro bit. Si tratta di ottenere risultati di query che non sembrano avere senso (comune), ad es. 'COUNT (*)' non conterà 'NULL', o che' NULL' non equivale mai 'NULL' (perché in pratica,' NULL' ha il significato di "sconosciuto"). - Ti suggerisco di leggere il documento di 5 pagine a cui mi sono collegato nella nota a piè di pagina. Contiene la critica di Date (apparentemente imperfetta, ma comunque molto perspicace) di 3VL. Inoltre, potresti voler controllare l'articolo di Wikipedia sulla logica ternaria: http://en.wikipedia.org/wiki/Ternary_logic – stakx

+0

Per quanto sopra, dovrei aggiungere il punto principale, che è quello dovuto a volte "non intuitivo" risultati che puoi ottenere grazie a 3VL, i risultati sono facilmente interpretati male. O ancora peggio, una query non è ciò che si pensa sia, e si otterranno risultati (corretti) che non sembreranno giusti. Un ultimo punto è che 'NULL' è spesso usato per indicare cose diverse, ad es. "sconosciuto", "mancante", "non applicabile", ecc., che rende ancora più difficile formulare query corrette e interpretare correttamente il risultato ottenuto dal DB. – stakx

risposta

4

Ho visto il collega di Date Hugh Darwen discutere di questo problema in un'eccellente presentazione "Come gestire le informazioni mancanti senza utilizzare NULL", che è disponibile su the Third Manifesto website.

La sua soluzione è una variante del secondo approccio. E 'il sesto forma normale, con tavoli per contenere sia Data di nascita, e identificatori in cui non si sa:

# +-----------------------------+ 1 0..1 +----------------------------+ 
# |   People'    | <-------> |   DatesOfBirth  | 
# +------------+----------------+   +------------+---------------+ 
# | PersonID | Name   |   | PersonID | DateOfBirth | 
# +============+----------------+   +============+---------------+ 
# | 1   | Banana Man |   ! 2   | 20-MAY-1991 | 
# | 2   | Satsuma Girl |   +------------+---------------+ 
# +------------+----------------+ 
#         1 0..1 +------------+ 
#         <-------> | DobUnknown | 
#           +------------+ 
#           | PersonID | 
#           +============+ 
#           | 1   | 
#           +------------+ 

Selezione da persone poi richiede che unisce tutti e tre i tavoli, tra cui boilerplate per indicare le date di nascita sconosciuti.

Naturalmente, questo è un po 'teorico. Lo stato di SQL in questi giorni non è ancora sufficientemente avanzato per gestire tutto questo. La presentazione di Hugh copre queste carenze. Una cosa che menziona non è del tutto corretta: alcune versioni di SQL supportano più assegnazioni, ad esempio Oracle's INSERT ALL syntax.

+0

Ho letto il paper su Third Manifesto e abbastanza simile alla soluzione, principalmente perché risolve l'ambiguità di cosa significa esattamente "NULL". Mentre questa separazione di significati diversi di 'NULL' in tabelle separate migliora la qualità dei dati, sono d'accordo con te sul fatto che interrogare effettivamente i dati diventerà più difficile. – stakx

+1

+1 per l'articolo di Darwen. Nota secondaria: se le relazioni sono effettivamente 1: 0..1 come indicato, l'assegnazione multipla non è un problema. – onedaywhen

1

io non l'ho letto, ma c'è un articolo intitolato come gestire le informazioni mancanti con S-by-C sul sito del Third Manifesto che è gestito da Hugh Darwen e C.J. Data. Questo non è stato scritto da C.J.Data, ma suppongo che dal momento che si tratta di uno degli articoli su quel sito, probabilmente è simile alle sue opinioni.

+0

Ci sono alcune informazioni interessanti su quel sito. Tuttavia, il documento specifico che hai menzionato sembra di natura più accademica: fornisce esempi nel linguaggio Tutorial D e non in SQL, che è ciò che viene effettivamente utilizzato oggi. (+1 per il collegamento al sito Web.) – stakx

+0

@stakx: Ah, non mi sorprende, suppongo, ho trovato il sito Web qualche tempo fa dopo aver letto un'altra domanda su SO, ma ho trovato tutto un po 'troppo accademico per leggere effettivamente attraverso qualsiasi cosa –

0

Un'alternativa potrebbe essere il modello entity-attribute-value:

entity attribute value 
1  name   Banana Man 
1  birthdate 1968-06-20 

Se la data di nascita era sconosciuta, si era appena omettere la sua fila.

+0

Immagino che il problema con questo modello sia che devi scegliere un singolo tipo per la colonna 'value' che è appropriata per tutti i tipi di cose (ad esempio ciò richiederebbe l'analisi di stringhe o anche' BLOB's sul lato client). – stakx

+0

@stakx: è possibile aggiungere una colonna 'type' insieme a' str_value', 'int_value',' real_value' colonne. E penso che SQLIte supporti EAV in modo nativo e memorizzi in modo efficiente interi, float e stringhe nella stessa colonna – Andomar

+0

Non sapevo che SQLite abbia il supporto nativo per il modello EAV. È una caratteristica interessante, nonostante sia una versione non standard (?) Che altri RDBMS potrebbero non avere. Grazie per le informazioni! – stakx

0

Opzione 3: onere per lo scrittore di record:

CREATE TABLE Person 
(
    PersonId int PRIMARY KEY IDENTITY(1,1), 
    Name nvarchar(100) NOT NULL, 
    DateOfBirth datetime NOT NULL 
) 

Perché contorcere un modello per consentire la rappresentazione null quando il vostro obiettivo è quello di eliminarli?

+0

* Ogni * soluzione alla mia domanda alla fine avrà 'NOT NULL' su tutte le colonne (dove applicabile). Ma la vera domanda - se non si riduce il mio post solo all'ultima frase - era * come * un modello di dati relazionali dovrebbe essere modificato ** in modo che possa ancora contenere informazioni mancanti o sconosciute ** per alcuni attributi. La tua soluzione tuttavia completamente * non consente * le informazioni mancanti per la colonna 'DateOfBirth' e quindi manca il bit più importante. – stakx

+0

Se si continua a consentire il terzo valore, è comunque necessario scrivere la logica relativa al terzo valore. Non so perché la tua implementazione del terzo valore sarebbe migliore di qualsiasi implementazione di un distributore db. –

+1

Voglio ottenere * rid * del terzo valore ('UNKNOWN'), * ma * allo stesso tempo è ancora in grado di rappresentare il fatto che alcune informazioni sono mancanti (o sconosciute o simili). Questo è ovviamente possibile. Sto studiando diversi modi per farlo. Forse stai dicendo, "Perché non usare solo' NULL', è lì solo per quello scopo? " - Perché il 3VL * può * portare a query confuse e non intuitive e risultati di query. Anche senza 3VL, ovviamente è necessario trattare * in qualche modo * con eventuali informazioni mancanti, ma farlo con 2VL potrebbe impedire alcuni errori logici. – stakx

0

È inoltre possibile eliminare null nell'output utilizzando COALESCE.

SELECT personid /*primary key, will never be null here*/ 
     , COALESCE(name, 'no name') as name 
     , COALESCE(birthdate,'no date') as birthdate 
FROM people 

Non tutti COALESCE supporto database, ma quasi tutti hanno un'opzione di riserva chiamato
IFNULL(arg1, arg2) o qualcosa simular che farà lo stesso (ma solo per 2 argomenti).

1

Ti consiglio di andare per la tua opzione 2. Sono abbastanza certo che anche Chris Date lo farebbe perché essenzialmente ciò che stai facendo è la normalizzazione a 6NF, la forma normale più alta possibile che sia Date was jointly responsible for introducing. I secondo lo Darwen's paper consigliato sulla gestione delle informazioni mancanti.

Poiché OUTER join non sarà permesso (perché introdurrebbero NULL nel set di risultati), tutti i dati necessari potrebbero possibilmente più essere recuperati con una sola query come prima.

... questo non è il caso, ma sono d'accordo sul fatto che il problema di outer join non è esplicitamente menzionato nel documento Darwen; era l'unica cosa che mi lasciava volere. La risposta esplicita può essere trovata in un altro libro della data ...

Per prima cosa, si noti che il linguaggio veramente relazionale di Date e Darwen Tutorial D ha un solo tipo di join come join naturale. La giustificazione è che solo un tipo di join è effettivamente necessario.

Il libro Data ho accennato è l'eccellente SQL and Relational Theory: How to Write Accurate SQL Code:

4,6: un'osservazione sulla outer join: "Relazionali parlando, [outer join è] un sorta di fucile matrimonio: Costringe tabelle in un tipo di unione-sì, I significa unione, non join-anche quando le tabelle in questione non riescono a conformi ai normali requisiti per unione ... Lo fa, nell'effetto , riempendo uno o entrambi i tavoli con null prima di fare il il sindacato, rendendoli quindi conformi ai soliti requisiti dopotutto. Ma c'è n o ragione che padding non dovrebbe essere fatto con valori corretti invece di valori nulli

Usando il tuo esempio e il valore di default '1900-01-01' come 'padding', l'alternativa alla outer join potrebbe assomigliare a questo :

SELECT p.PersonID, p.Name, b.DateOfBirth 
    FROM Person AS p 
     INNER JOIN BirthDate AS b 
      ON p.PersonID = b.PersonID 
UNION 
SELECT p.PersonID, p.Name, '1900-01-01' AS DateOfBirth 
    FROM Person AS p 
WHERE NOT EXISTS (
        SELECT * 
        FROM BirthDate AS b 
        WHERE p.PersonID = b.PersonID 
       ); 

carta di Darwen prose due tabelle esplicite, dicono BirthDate e BirthDateKnown, ma l'SQL non sarebbe molto diverso ad esempio, un semi join a BirthDateKnown al posto della differenza di semilavorato a BirthDate sopra.

Nota gli usi di cui sopra JOIN e INNER JOIN solo perché standard SQL-92 e NATURAL JOINUNION CORRESPONDING non sono ampiamente implementati in prodotti SQL vita reale (non riesce a trovare una citazione, ma IIRC Darwen era in gran parte responsabile per gli ultimi due che lo rende in Standard).

Ulteriori note la sintassi precedente sembra prolisso solo perché SQL in generale è prolisso. In algebra relazionale puro è più simile (pseudo codice):

Person JOIN BirthDate UNION Person NOT MATCHING BirthDate ADD '1900-01-01' AS DateOfBirth; 
0

Una possibilità è quella di utilizzare esplicito option types, analogo a Maybe funtore di Haskell.

Sfortunatamente molte implementazioni SQL esistenti hanno scarso supporto per i tipi di dati algebrici definiti dall'utente e un supporto ancora più scadente per i costruttori di tipi definiti dall'utente che è davvero necessario farlo in modo pulito.

Questo recupera una sorta di "null" solo per quegli attributi in cui viene richiesto esplicitamente, ma senza logica stupida a tre valori null. Nothing == Nothing è True, non unknown o null.

Supporto per i tipi algebrici definiti dall'utente aiuta anche quando ci sono alcune ragioni per le informazioni mancanti, ad esempio un database equivalente del seguente tipo Haskell sarebbe una buona soluzione per l'applicazione ovvia:

data EmploymentStatus = Employed EmployerID | Unemployed | Unknown 

(Naturalmente, un database che supporta questo dovrebbe anche supportare il vincolo di chiave esterna più complicato del solito che viene fornito con esso.)

In breve, sono d'accordo con le risposte di APC e onedaywhen su 6NF.

+1

SQL 'NULL' non significa" nulla ", ma" sconosciuto ". Quindi è corretto che 'NULL = NULL' non sia vero: se confronti due incognite, non puoi essere sicuro che siano uguali. – stakx

+0

È "corretto" perché è così che è definito, ma è _hugely_ problematico per una serie di motivi. Consulta http://www.amazon.com/Foundation-Object-Relational-Databases-Manifesto/dp/0201309785 per i dettagli sanguinosi che non rientrano in questo commento. (È anche una vecchia guerra che non ha bisogno di essere ridiscussa, ma il lato pro-null è oggettivamente sbagliato nel merito.) –

Problemi correlati