Cosa c'è che non va nelle colonne nullable nelle chiavi primarie composite?


149

ORACLE non consente valori NULL in nessuna delle colonne che comprendono una chiave primaria. Sembra che lo stesso valga per la maggior parte degli altri sistemi di "livello aziendale".

Allo stesso tempo, la maggior parte dei sistemi consente anche contrapposizioni uniche su colonne nullable.

Perché i vincoli univoci possono avere NULL ma le chiavi primarie non possono? C'è una ragione logica fondamentale per questo, o è più una limitazione tecnica?


Risposte:


216

Le chiavi primarie servono per identificare in modo univoco le righe. Questo viene fatto confrontando tutte le parti di una chiave con l'input.

Per definizione, NULL non può far parte di un confronto riuscito. Anche un confronto con se stesso ( NULL = NULL) fallirà. Ciò significa che una chiave contenente NULL non funzionerebbe.

Inoltre, NULL è consentito in una chiave esterna per contrassegnare una relazione facoltativa. (*) Autorizzarlo anche nel PK lo spezzerebbe.


(*) Un avvertimento: avere chiavi esterne nullable non è un design pulito del database relazionale.

Se ci sono due entità Ae Bdove Apuò essere facoltativamente correlato B, la soluzione pulita è quella di creare una tabella di risoluzione (diciamo AB). Quel tavolo collegherebbe Acon B: Se c'è un rapporto allora conterrebbe un record, se non ci non è , allora non sarebbe.


5
Ho cambiato la risposta accettata a questa. A giudicare dai voti, questa risposta è la più chiara per più persone. Sento ancora che la risposta di Tony Andrews spiega meglio l' intenzione alla base di questo progetto; provalo pure!
Roman Starkov,

2
Q: Quando vuoi un NULL FK invece di una mancanza di riga? A: Solo in una versione di uno schema denormalizzato per l'ottimizzazione. In schemi non banali problemi non normalizzati come questo possono causare problemi ogni volta che sono richieste nuove funzionalità. anzi, alla folla del web design non importa. Aggiungerei almeno una nota di cautela al riguardo invece di far sembrare una buona idea progettuale.
zxq9,

3
"Avere chiavi esterne nullable non è un progetto pulito di database relazionale." - un design del database privo di null (sesta forma normale) aggiunge inevitabilmente complessità, i risparmi di spazio guadagnati sono spesso compensati dal lavoro extra del programmatore necessario per realizzare tali guadagni.
Dai,

1
e se fosse una tabella di risoluzione ABC? con opzionale C
Bart Calixto

1
Ho cercato di evitare di scrivere "perché lo standard lo proibisce", poiché questo non spiega davvero nulla.
Tomalak,

62

Una chiave primaria definisce un identificatore univoco per ogni riga di una tabella: quando una tabella ha una chiave primaria, hai un modo garantito per selezionare qualsiasi riga da essa.

Un vincolo univoco non identifica necessariamente ogni riga; specifica solo che se una fila ha valori nelle sue colonne, allora essi devono essere unici. Ciò non è sufficiente per identificare in modo univoco ogni riga, che è ciò che deve fare una chiave primaria.


10
In SQL Server un vincolo univoco che ha una colonna nullable, consente il valore 'null' in quella colonna solo una volta (dati identici per le altre colonne del vincolo). Quindi un vincolo così unico si comporta essenzialmente come un pk con una colonna nullable.
Gerard,

Confermo lo stesso per Oracle (11.2)
Alexander Malakhov,

2
In Oracle (non conosco SQL Server), la tabella può contenere molte righe in cui tutte le colonne in un vincolo univoco sono null. Tuttavia, se alcune colonne nel vincolo univoco non sono nulle e alcune sono nulle, viene applicata l'univocità.
Tony Andrews,

Come si applica a UNIQUE composito?
Dims

1
@Dims Come quasi ogni altra cosa nei database SQL "dipende dall'implementazione". Nella maggior parte dei dbs una "chiave primaria" è in realtà un vincolo UNICO sottostante. L'idea di "chiave primaria" non è in realtà più speciale o potente del concetto di UNIQUE. La vera differenza è che se hai due aspetti indipendenti di una tabella che possono essere garantiti UNICI, non hai un database normalizzato per definizione (stai memorizzando due tipi di dati nella stessa tabella).
zxq9,

46

Fondamentalmente, nulla è sbagliato in un NULL in una chiave primaria multi-colonna. Ma avendo uno ha implicazioni che il progettista probabilmente non intendeva, motivo per cui molti sistemi generano un errore quando provi questo.

Si consideri il caso delle versioni del modulo / pacchetto memorizzate come una serie di campi:

CREATE TABLE module
  (name        varchar(20) PRIMARY KEY,
   description text DEFAULT '' NOT NULL);

CREATE TABLE version
  (module      varchar(20) REFERENCES module,
   major       integer NOT NULL,
   minor       integer DEFAULT 0 NOT NULL,
   patch       integer DEFAULT 0 NOT NULL,
   release     integer DEFAULT 1 NOT NULL,
   ext         varchar(20),
   notes       text DEFAULT '' NOT NULL,
   PRIMARY KEY (module, major, minor, patch, release, ext));

I primi 5 elementi della chiave primaria sono parti definite regolarmente di una versione di rilascio, ma alcuni pacchetti hanno un'estensione personalizzata che di solito non è un numero intero (come "rc-foo" o "vanilla" o "beta" o qualsiasi altra cosa per cui qualcuno chi quattro campi è insufficiente potrebbe pensare). Se un pacchetto non ha un'estensione, allora è NULL nel modello sopra e non si danneggerebbe lasciando le cose in quel modo.

Ma che cosa è un NULL? Dovrebbe rappresentare una mancanza di informazioni, uno sconosciuto. Detto questo, forse questo ha più senso:

CREATE TABLE version
  (module      varchar(20) REFERENCES module,
   major       integer NOT NULL,
   minor       integer DEFAULT 0 NOT NULL,
   patch       integer DEFAULT 0 NOT NULL,
   release     integer DEFAULT 1 NOT NULL,
   ext         varchar(20) DEFAULT '' NOT NULL,
   notes       text DEFAULT '' NOT NULL,
   PRIMARY KEY (module, major, minor, patch, release, ext));

In questa versione la parte "ext" della tupla NON è NULL ma per impostazione predefinita è una stringa vuota - che è semanticamente (e praticamente) diversa da un NULL. Un NULL è sconosciuto, mentre una stringa vuota è una registrazione deliberata di "qualcosa che non è presente". In altre parole, "vuoto" e "null" sono cose diverse. È la differenza tra "Non ho un valore qui" e "Non so quale sia il valore qui".

Quando registri un pacchetto privo di un'estensione di versione, sai che manca un'estensione, quindi una stringa vuota è in realtà il valore corretto. Un NULL sarebbe corretto solo se non sapessi se avesse un'estensione o no, o sapevi che lo era, ma non sapevi cosa fosse. Questa situazione è più facile da gestire nei sistemi in cui i valori delle stringhe sono la norma, perché non c'è modo di rappresentare un "numero intero vuoto" diverso dall'inserimento di 0 o 1, che finirà per essere arrotolato in tutti i confronti effettuati in seguito (che ha le sue implicazioni) *.

Per inciso, entrambi i modi sono validi in Postgres (dal momento che stiamo discutendo di RDMBS "enterprise"), ma i risultati del confronto possono variare abbastanza quando si lancia un NULL nel mix - perché NULL == "non lo so" quindi tutti i risultati di un confronto che coinvolgono un NULL finiscono per essere NULL poiché non si può sapere qualcosa di sconosciuto. PERICOLO! Pensaci bene: questo significa che i risultati del confronto NULL si propagano attraverso una serie di confronti. Questo può essere una fonte di bug sottili durante l'ordinamento, il confronto, ecc.

Postgres presume che tu sia un adulto e che tu possa prendere questa decisione da solo. Oracle e DB2 presumono che non ti rendessi conto che stavi facendo qualcosa di stupido e che avessero lanciato un errore. Questa è di solito la cosa giusta, ma non sempre - potresti effettivamente non sapere e avere un NULL in alcuni casi e quindi lasciare una riga con un elemento sconosciuto contro il quale sono impossibili confronti significativi è un comportamento corretto.

In ogni caso dovresti cercare di eliminare il numero di campi NULL che permetti nell'intero schema e doppiamente quando si tratta di campi che fanno parte di una chiave primaria. Nella stragrande maggioranza dei casi la presenza di colonne NULL è un'indicazione di progettazione dello schema non normalizzata (piuttosto che deliberatamente non normalizzata) e dovrebbe essere presa in seria considerazione prima di essere accettata.

[* NOTA: è possibile creare un tipo personalizzato che è l'unione di numeri interi e un tipo "bottom" che significherebbe semanticamente "vuoto" anziché "sconosciuto". Sfortunatamente questo introduce un po 'di complessità nelle operazioni di confronto e in genere essere veramente corretti per tipo non vale la pena in pratica poiché non dovresti prima avere molti NULLvalori. Detto questo, sarebbe meraviglioso se gli RDBMS includessero un BOTTOMtipo predefinito oltre NULLa prevenire l'abitudine di confondere casualmente la semantica di "nessun valore" con "valore sconosciuto". ]


5
Questa è una risposta MOLTO PIACEVOLE e spiega molto sui valori NULL e le sue implicazioni in molte situazioni. Signore, ora avete il mio rispetto! Nemmeno al college ho avuto una spiegazione così valida sui valori NULL all'interno dei database. Grazie!

Sostengo l'idea principale di questa risposta. Ma scrivere come 'dovrebbe rappresentare una mancanza di informazioni, uno sconosciuto', 'semanticamente (e praticamente) diverso da un NULL', 'A NULL è uno sconosciuto', 'una stringa vuota è una registrazione deliberata di "qualcosa che non è presente "',' NULL ==" non lo so "', ecc. Sono vaghi e fuorvianti e in realtà solo mnemonici per assenze assenti su come NULL o qualsiasi valore sia o possa o debba essere utilizzato - per il resto del post . (Incluso nell'ispirare il (cattivo) design delle funzionalità SQL NULL.) Non giustificano o spiegano nulla; dovrebbero essere spiegati e sfatati.
Philipxy,

21

NULL == NULL -> false (almeno nei DBMS)

Quindi non saresti in grado di recuperare alcuna relazione usando un valore NULL anche con colonne aggiuntive con valori reali.


1
Sembra la risposta migliore, ma non capisco ancora perché ciò sia proibito durante la creazione della chiave primaria. Se questo era solo un problema di recupero, è possibile utilizzare where pk_1 = 'a' and pk_2 = 'b'con valori normali e passare a where pk_1 is null and pk_2 = 'b'quando sono presenti valori null.
EoghanM,

O ancora più affidabile, where (a.pk1 = b.pk1 or (a.pk1 is null and b.pk1 is null)) and (a.pk2 = b.pk2 or (a.pk2 is null and b.pk2 is null))/
Jordan Rieger,

8
Risposta sbagliata. NULL == NULL -> SCONOSCIUTO. Non falso. Il problema è che un vincolo non è considerato violato se l'esito del test è SCONOSCIUTO. Questo spesso lo rende SEEM come se il confronto si traducesse in falso, ma in realtà non lo è.
Erwin Smout,

4

La risposta di Tony Andrews è decente. Ma la vera risposta è che questa è stata una convenzione utilizzata dalla comunità di database relazionali e NON è una necessità. Forse è una buona convenzione, forse no.

Confrontando qualsiasi cosa con NULL si ottiene UNKNOWN (3 ° valore di verità). Così, come è stato suggerito con il nulla, tutta la saggezza tradizionale relativa all'uguaglianza esce dalla finestra. Bene, è così che sembra a prima vista.

Ma non penso che sia necessariamente così e anche i database SQL non pensano che NULL distrugga tutte le possibilità di confronto.

Esegui nel tuo database la query SELECT * FROM VALUES (NULL) UNION SELECT * FROM VALUES (NULL)

Quello che vedi è solo una tupla con un attributo che ha il valore NULL. Quindi l'unione ha riconosciuto qui i due valori NULL come uguali.

Quando si confronta una chiave composita che ha 3 componenti con una tupla con 3 attributi (1, 3, NULL) = (1, 3, NULL) <=> 1 = 1 AND 3 = 3 AND NULL = NULL Il risultato è SCONOSCIUTO .

Ma potremmo definire un nuovo tipo di operatore di confronto, ad es. ==. X == Y <=> X = Y OR (X È NULL E Y È NULL)

Avere questo tipo di operatore di uguaglianza renderebbe non problematiche le chiavi composite con componenti null o le chiavi non composite con valore null.


1
No, l'UNION ha riconosciuto i due NULL come non distinti. Che non è la stessa cosa di "uguale". Prova invece UNION ALL e otterrai due righe. E per quanto riguarda il "nuovo tipo di operatore di confronto", SQL lo possiede già. NON È DISTINTO DA. Ma questo da solo non è sufficiente. L'uso di questo nei costrutti SQL come NATURAL JOIN, o la clausola REFERENCES di una chiave esterna, richiederà ancora ulteriori opzioni su tali costrutti.
Erwin Smout,

Ah, Erwin Smout. Davvero un piacere conoscerti anche su questo forum! Non ero a conoscenza di "IS NOT DISTINCT FROM" di SQL. Molto interessante! Ma sembra che sia esattamente quello che intendevo con il mio operatore == inventato. Potresti spiegarmi perché dici: "che da solo non è sufficiente"?
Rami Ojares,

La clausola REFERENCES si basa sull'uguaglianza, per definizione. Una sorta di RIFERIMENTI che abbina una tupla / riga figlio a una tupla / riga padre, in base ai valori dell'attributo corrispondente NON DISTINCT anziché (il più rigoroso) EQUAL, richiederebbe la possibilità di specificare questa opzione, ma la sintassi non Permettilo. Idem per NATURAL JOIN.
Erwin Smout,

Affinché una chiave esterna funzioni, il riferimento deve essere univoco (ovvero tutti i valori devono essere distinti). Ciò significa che potrebbe avere un singolo valore null. Tutti i valori null potrebbero quindi fare riferimento a quel singolo null se i RIFERIMENTI sarebbero definiti con l'operatore NOT DISTINCT. Penso che sarebbe meglio (nel senso di più utile). Con JOINs (sia esterno che interno) penso che gli uguali rigorosi siano migliori perché le "PARTITE NULL" si moltiplicherebbero quando i null sul lato sinistro corrisponderebbero a tutti i null sul lato destro.
Rami Ojares,

1

Credo ancora che questo sia un difetto fondamentale / funzionale causato da un tecnicismo. Se disponi di un campo facoltativo in base al quale puoi identificare un cliente, ora devi hackerare un valore fittizio, solo perché NULL! = NULL, non particolarmente elegante ma è uno "standard del settore"

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.