Opzioni per eliminare le colonne Nullable da un modello DB (al fine di evitare la logica a tre valori di SQL)?

StackOverflow https://stackoverflow.com/questions/3079885

Domanda

Qualche tempo fa, ho letto il libro SQL e Teoria Relazionale da CJ Data . L'autore è ben noto per aver criticato la logica a tre valori di SQL (3VL). 1)

L'autore fa alcuni punti di forza sul perché 3VL deve essere evitato in SQL, ma non lo fa contorno come un modello di database potrebbe apparire come se le colonne nullable non sono stati autorizzati . Ho pensato su questo per un po 'e sono venuto su con le seguenti soluzioni. Se ho perso altre opzioni di progettazione, mi piacerebbe sentire su di loro!

1) critica di Data di 3VL di SQL è a sua volta stato criticato troppo: vedi questo scritto da Claude Rubinson (comprende la critica originale di CJ Data).


Tavolo Esempio:

A titolo di esempio, prendere la seguente tabella, dove abbiamo una colonna Null (DateOfBirth):

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

Opzione 1: Emulazione NULL attraverso una bandiera e un valore di default:

Invece di rendere il nullable colonna, qualsiasi valore predefinito è specificato (ad esempio 1900-01-01). Una colonna BOOLEAN supplementare specificare se il valore in DateOfBirth dovrebbe essere semplicemente ignorato o se contiene effettivamente i dati.

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

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

La colonna nullable viene sostituito 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 risultato in molte tabelle che devono unirsi per una singola query. Dal momento che OUTER JOINs non sarà permesso (perché avrebbero introdurre NULL nel set di risultati), tutti i dati necessari potrebbero eventualmente non essere più inverosimile con appena una singola query come prima.


Domanda: Ci sono altre opzioni per l'eliminazione NULL (e in caso affermativo, quali sono)?

È stato utile?

Soluzione

collega ho visto di Data Hugh Darwen discutere di questo tema in una presentazione eccellente "Come gestire le informazioni mancanti senza utilizzare NULL", che è disponibile su il sito web di terze Manifesto .

La sua soluzione è una variante sul 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          |
#                                            +------------+

La selezione da parte di persone quindi richiede che unisce tutti e tre i tavoli, tra cui boilerplate per indicare le date di nascita sconosciuti.

Naturalmente, questo è un po 'teorica. Lo stato di SQL in questi giorni non è ancora sufficientemente avanzata per gestire tutto questo. presentazione di Hugh copre queste lacune. Una cosa che egli cita, non è del tutto corretto: alcuni sapori di SQL supportano l'assegnazione di più - per esempio di Oracle INSERT ALL sintassi .

Altri suggerimenti

Vi consiglio di andare per la vostra opzione 2. Sono abbastanza certo Chris data sarebbe troppo, perché in sostanza quello che stai facendo è completamente normalizzante a 6NF , la più alta forma normale possibile, che Data era congiuntamente responsabile per l'introduzione . I secondi la raccomandata di Darwen carta sulla gestione delle informazioni mancanti.

  

Dato outer join non sarà consentito (perché sarebbero introdurre NULL   nel set di risultati), tutti i dati necessari potrebbe eventualmente più   da prelevare con appena una singola query come prima.

... questo non è il caso, ma sono d'accordo la questione della outer join non è esplicitamente menzionato nel documento Darwen; era l'unica cosa che mi ha lasciato che vogliono. La risposta esplicita può essere trovata in un altro del libro di Data ...

In primo luogo, si noti che Data e proprio linguaggio veramente relazionale di Darwen Tutorial D ha ma un tipo di join essendo unirsi al naturale. La giustificazione è che solo un tipo di join è effettivamente necessaria.

Il libro Data ho accennato è l'eccellente Teoria SQL e relazionale: come Scrivi Accurate SQL Codice :

  

4.6: un'osservazione sulla outer join: "Relazionali parlando, [outer join è] un   tipo di fucile da caccia del matrimonio: Costringe tabelle in una sorta di unione-sì,   fare sindacato media, non unirsi, anche quando le tabelle in questione non riescono a   conformarsi ai soliti requisiti per l'unione ... Lo fa, in   effetto, da un'imbottitura o entrambe le tabelle con i valori null prima di fare   l'unione, rendendoli conformi a dette prescrizioni usuali   Dopotutto. Ma non c'è motivo per cui non dovrebbe essere fatto che padding   con valori corretti invece di valori nulli

Usando il tuo esempio e valore di default '1900-01-01' come 'padding', l'alternativa alla join esterno 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
                  );

L'articolo di Darwen prose due tabelle esplicite, dicono BirthDate e BirthDateKnown, ma l'SQL non sarebbe molto diverso ad esempio un semi uniscono per BirthDateKnown al posto della differenza semi di BirthDate sopra.

Si noti l'uso sopra JOIN e INNER JOIN solo perché standard SQL-92 e NATURAL JOIN UNION 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 farne Standard ).

un'ulteriore nota gli sguardi di sintassi sopra 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;

Non l'ho letto, ma c'è un articolo intitolato come gestire le informazioni mancanti con S-by-C sulla sito web di terze Manifesto che è gestito da Hugh Darwen e CJ Data. Questo non è scritto da C.J. Data, ma mi piacerebbe pensare che, dal momento che è uno degli articoli sul sito web che è probabilmente simile a sue opinioni.

Un'alternativa potrebbe essere l'href="http://en.wikipedia.org/wiki/Entity-attribute-value_model" rel="nofollow noreferrer"> entità-attributo-valore del modello

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

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

Opzione 3: onere per lo scrittore di registrazione:

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

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

Si può eliminare null nell'output e 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 tutte le banche dati sostenere COALESCE, ma quasi tutti hanno un'opzione di riserva chiamato
IFNULL(arg1, arg2) o qualcosa simular che farà lo stesso (ma solo per 2 argomenti) .

Una possibilità è quella di utilizzare espliciti , analoghe a funtore Maybe di Haskell.

Purtroppo molte implementazioni SQL esistenti hanno scarso supporto per i tipi di dati algebrica definiti dall'utente e supporto ancora più poveri per i costruttori di tipo definito dall'utente che si ha realmente bisogno di fare questo in modo pulito.

Questa recupera una sorta di "null" per solo gli attributi in cui è esplicitamente chiedere per essa, ma senza stupida logica a tre valori di null. Nothing == Nothing è True, non unknown o null.

Il 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, una banca dati a sostegno di questa avrebbe anche bisogno di sostenere il vincolo di chiave esterna più complicata del solito che viene con esso.)

In mancanza di questo, sono d'accordo con APC 's e onedaywhen 's risposte su 6NF.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top