Co jest nie tak z kolumnami dopuszczającymi wartość null w złożonych kluczach podstawowych?

149

ORACLE nie zezwala na wartości NULL w żadnej z kolumn, które zawierają klucz podstawowy. Wydaje się, że to samo dotyczy większości innych systemów „na poziomie przedsiębiorstwa”.

Jednocześnie większość systemów dopuszcza również unikalne ograniczenia w kolumnach dopuszczających wartość zerową.

Dlaczego jest tak, że ograniczenia unikatowe mogą mieć wartości NULL, a klucze podstawowe nie? Czy istnieje jakiś fundamentalny logiczny powód, czy jest to raczej ograniczenie techniczne?

Roman Starkov
źródło

Odpowiedzi:

216

Klucze podstawowe służą do jednoznacznego identyfikowania wierszy. Odbywa się to poprzez porównanie wszystkich części klucza z danymi wejściowymi.

Z definicji NULL nie może być częścią udanego porównania. Nawet porównanie do samego siebie ( NULL = NULL) nie powiedzie się. Oznacza to, że klucz zawierający NULL nie zadziała.

Dodatkowo w kluczu obcym dozwolone jest NULL, aby oznaczyć opcjonalną relację. (*) Pozwolenie na to w PK również by to zepsuło.


(*) Uwaga: posiadanie kluczy obcych dopuszczających wartość null nie jest czystym projektem relacyjnej bazy danych.

Jeśli istnieją dwie jednostki Ai Bgdzie Amożna je opcjonalnie powiązać B, czystym rozwiązaniem jest utworzenie tabeli rozdzielczości (powiedzmy AB). Że stół będzie połączyć Az B: Jeśli nie jest relacja wtedy ona zawierać zapis, jeśli nie jest , to nie będzie.

Tomalak
źródło
5
Zmieniłem zaakceptowaną odpowiedź na tę. Sądząc po głosach, ta odpowiedź jest najwyraźniejsza dla większej liczby osób. Nadal uważam, że odpowiedź Tony'ego Andrewsa lepiej wyjaśnia intencję tego projektu; sprawdź to również!
Roman Starkov
2
P: Kiedy chcesz NULL FK zamiast braku rzędu? O: Tylko w wersji schematu zdenormalizowanej do optymalizacji. W nietrywialnych schematach takie nieznormalizowane problemy mogą powodować problemy, gdy wymagane są nowe funkcje. otoh, tłum projektantów stron internetowych nie dba o to. Chciałbym przynajmniej dodać uwagę na ten temat, zamiast mówić, że brzmi to jak dobry pomysł na projekt.
zxq9
3
„Posiadanie kluczy obcych dopuszczających wartość null nie jest czystym projektem relacyjnej bazy danych”. - projekt bazy danych pozbawiony zera (szósta normalna postać) niezmiennie zwiększa złożoność, a uzyskane oszczędności miejsca są często przeważane przez dodatkową pracę programisty potrzebną do osiągnięcia tych korzyści.
Dai
1
co jeśli jest to tabela rozdzielczości ABC? z opcjonalnym C
Bart Calixto
1
Starałem się unikać pisania „bo norma tego zabrania”, bo to naprawdę niczego nie wyjaśnia.
Tomalak
62

Klucz podstawowy definiuje unikatowy identyfikator dla każdego wiersza w tabeli: jeśli tabela ma klucz podstawowy, masz gwarantowany sposób na wybranie dowolnego wiersza w tabeli.

Unikalne ograniczenie niekoniecznie identyfikuje każdy wiersz; po prostu określa, że jeśli wiersz zawiera wartości w swoich kolumnach, to muszą one być unikalne. To nie wystarczy, aby jednoznacznie zidentyfikować każdy wiersz, co musi zrobić klucz podstawowy.

Tony Andrews
źródło
10
W Sql Server unikalne ograniczenie, które ma kolumnę dopuszczającą wartość null, zezwala na wartość „null” w tej kolumnie tylko raz (przy tych samych wartościach dla innych kolumn ograniczenia). Tak więc takie unikalne ograniczenie zasadniczo zachowuje się jak pk z kolumną dopuszczającą wartość null.
Gerard,
Potwierdzam to samo dla Oracle (11.2)
Alexander Malakhov,
2
W Oracle (nie wiem o SQL Server), tabela może zawierać wiele wierszy, w których wszystkie kolumny w jednym ograniczeniu są zerowe. Jeśli jednak niektóre kolumny w ograniczeniu unikalności nie są puste, a niektóre są puste, wymuszana jest unikalność.
Tony Andrews
Jak to się ma do kompozytu UNIQUE?
Przyciemnia
1
@Dims Jak prawie wszystko inne w bazach danych SQL „zależy to od implementacji”. W większości dbs „klucz podstawowy” jest w rzeczywistości unikalnym ograniczeniem znajdującym się pod spodem. Idea „klucza podstawowego” nie jest tak naprawdę bardziej wyjątkowa ani potężna niż koncepcja UNIQUE. Prawdziwa różnica polega na tym, że jeśli masz dwa niezależne aspekty tabeli, które można zagwarantować jako UNIKALNE, z definicji nie masz znormalizowanej bazy danych (przechowujesz dwa typy danych w tej samej tabeli).
zxq9
46

Zasadniczo nie ma nic złego w NULL w wielokolumnowym kluczu podstawowym. Ale posiadanie takiego ma konsekwencje, których projektant prawdopodobnie nie zamierzał, dlatego wiele systemów wyświetla błąd podczas próby.

Rozważmy przypadek wersji modułu / pakietu przechowywanych jako seria pól:

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));

Pierwszych 5 elementów klucza podstawowego to regularnie definiowane części wersji wydania, ale niektóre pakiety mają dostosowane rozszerzenie, które zwykle nie jest liczbą całkowitą (np. „Rc-foo”, „vanilla”, „beta” lub cokolwiek innego dla których cztery pola są niewystarczające, może wymarzyć) Jeśli pakiet nie ma rozszerzenia, to w powyższym modelu ma wartość NULL i pozostawienie rzeczy w ten sposób nie byłoby szkodliwe.

Ale co to jest NULL? Ma reprezentować brak informacji, nieznane. To powiedziawszy, być może ma to większy sens:

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));

W tej wersji część „ext” krotki NIE ma wartości NULL, ale domyślnie jest to pusty ciąg - który jest semantycznie (i praktycznie) różny od wartości NULL. Wartość NULL jest nieznana, podczas gdy pusty łańcuch to celowy zapis „czegoś nieobecnego”. Innymi słowy, „pusty” i „pusty” to różne rzeczy. To różnica między „Nie mam tutaj wartości” a „Nie wiem, jaka jest tutaj wartość”.

Kiedy rejestrujesz pakiet, który nie ma rozszerzenia wersji, wiesz, że brakuje mu rozszerzenia, więc pusty ciąg jest w rzeczywistości poprawną wartością. Wartość NULL byłaby poprawna tylko wtedy, gdybyś nie wiedział, czy ma rozszerzenie, czy nie, lub wiedziałeś, że ma, ale nie wiesz, co to jest. Ta sytuacja jest łatwiejsza do rozwiązania w systemach, w których wartości łańcuchowe są normą, ponieważ nie ma innego sposobu na przedstawienie „pustej liczby całkowitej” niż wstawienie 0 lub 1, która zakończy się zawijaniem w późniejszych porównaniach (co ma własne implikacje) *.

Nawiasem mówiąc, oba sposoby są poprawne w Postgres (ponieważ omawiamy "korporacyjne" RDMBS), ale wyniki porównania mogą się znacznie różnić, gdy dodasz NULL do miksu - ponieważ NULL == "nie wiem", więc wszystko wyniki porównania z wartością NULL kończą się wartością NULL, ponieważ nie możesz wiedzieć czegoś, co jest nieznane. ZAGROŻENIE! Zastanów się dokładnie: oznacza to, że wyniki porównań NULL są propagowane przez serię porównań. Może to być źródłem subtelnych błędów podczas sortowania, porównywania itp.

Postgres zakłada, że ​​jesteś osobą dorosłą i możesz samodzielnie podjąć tę decyzję. Oracle i DB2 zakładają, że nie zdawałeś sobie sprawy, że robisz coś głupiego i zgłaszasz błąd. Jest to zazwyczaj słuszne, ale nie zawsze - ty może rzeczywiście nie wiedzą i mają wartość null w niektórych przypadkach, a zatem pozostawiając wiersz z nieznanym elemencie przeciwko którym sensowne porównania są niemożliwe jest poprawne zachowanie.

W każdym przypadku powinieneś dążyć do wyeliminowania liczby pól NULL, na które zezwalasz w całym schemacie, a także podwójnie, jeśli chodzi o pola, które są częścią klucza podstawowego. W zdecydowanej większości przypadków obecność kolumn NULL jest oznaką nieznormalizowanego (w przeciwieństwie do celowo zdenormalizowanego) schematu i należy się nad nim bardzo intensywnie przemyśleć, zanim zostanie zaakceptowany.

[* UWAGA: Możliwe jest utworzenie niestandardowego typu będącego połączeniem liczb całkowitych i typu „dolnego”, który semantycznie oznaczałby „pusty” w przeciwieństwie do „nieznanego”. Niestety, wprowadza to trochę złożoności w operacjach porównawczych i zazwyczaj bycie naprawdę poprawnym względem typu nie jest warte wysiłku w praktyce, ponieważ w ogóle nie powinno się pozwalać na wiele NULLwartości. To powiedziawszy, byłoby wspaniale, gdyby systemy RDBMS zawierały domyślny BOTTOMtyp, NULLaby zapobiec nawykowi przypadkowego mieszania semantyki „brak wartości” z „nieznaną wartością”. ]

zxq9
źródło
5
To jest BARDZO MIŁA odpowiedź i wyjaśnia wiele o wartościach NULL i ich konsekwencjach w wielu sytuacjach. Panie, miej teraz mój szacunek! Nawet na studiach nie uzyskałem tak dobrego wyjaśnienia wartości NULL w bazach danych. Dziękuję Ci!
Popieram główną ideę tej odpowiedzi. Ale pisanie w stylu „ma oznaczać brak informacji, nieznane”, „semantycznie (i praktycznie) różni się od wartości NULL”, „NULL jest nieznane”, „pusty ciąg jest celowym zapisem„ czegoś nieobecnego ” "',' NULL ==" nie wiem "', itp. Są niejasne i wprowadzające w błąd i tak naprawdę tylko mnemoniki dla nieobecnych instrukcji dotyczących tego, jak NULL lub jakakolwiek wartość jest lub może lub miała być używana - zgodnie z pozostałą częścią postu . (W tym inspirowanie (złego) projektu funkcji SQL NULL.) Nie usprawiedliwiają ani nie wyjaśniają niczego; powinny zostać wyjaśnione i zdemaskowane.
philipxy
21

NULL == NULL -> false (przynajmniej w DBMS)

Więc nie byłbyś w stanie pobrać żadnych relacji przy użyciu wartości NULL, nawet z dodatkowymi kolumnami z wartościami rzeczywistymi.

Cogsy
źródło
1
To brzmi jak najlepsza odpowiedź, ale nadal nie rozumiem, dlaczego jest to zabronione przy tworzeniu klucza podstawowego. Gdyby to był tylko problem z pobieraniem, można by użyć where pk_1 = 'a' and pk_2 = 'b'z normalnymi wartościami i przełączyć się na where pk_1 is null and pk_2 = 'b'gdy występują wartości null.
EoghanM
Albo nawet bardziej niezawodnie, 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
Zła odpowiedź. NULL == NULL -> UNKNOWN. Nie fałszywe. Problem polega na tym, że ograniczenie nie jest uważane za naruszone, jeśli wynik testu jest NIEZNANY. To często sprawia, że seem jakby porównanie daje fałszywe, ale to naprawdę nie jest.
Erwin Smout
4

Odpowiedź Tony'ego Andrewsa jest przyzwoita. Ale prawdziwa odpowiedź jest taka, że ​​była to konwencja używana przez społeczność relacyjnych baz danych i NIE jest to konieczność. Może to dobra konwencja, a może nie.

Porównanie czegokolwiek z wartością NULL daje wynik NIEZNANY (trzecia wartość prawdy). Tak więc, jak zasugerowano w przypadku wartości zerowych, cała tradycyjna mądrość dotycząca równości wychodzi przez okno. Cóż, tak to wygląda na pierwszy rzut oka.

Ale nie sądzę, żeby tak było i nawet bazy danych SQL nie uważają, że NULL niweczy wszelkie możliwości porównania.

Uruchom w bazie danych zapytanie SELECT * FROM VALUES (NULL) UNION SELECT * FROM VALUES (NULL)

To, co widzisz, to tylko jedna krotka z jednym atrybutem o wartości NULL. Tak więc unia rozpoznała tutaj dwie wartości NULL jako równe.

Podczas porównywania klucza złożonego, który ma 3 składniki, do krotki z 3 atrybutami (1, 3, NULL) = (1, 3, NULL) <=> 1 = 1 AND 3 = 3 AND NULL = NULL Wynik tego jest NIEZNANY .

Ale moglibyśmy zdefiniować nowy rodzaj operatora porównania, np. ==. X == Y <=> X = Y LUB (X JEST NULL AND Y JEST NULL)

Posiadanie tego rodzaju operatora równości sprawiłoby, że klucze złożone ze składnikami o wartości null lub klucz niezłożony o wartości null byłyby bezproblemowe.

Rami Ojares
źródło
1
Nie, UNIA uznała te dwie wartości NULL za nierozróżnialne. Co nie jest tym samym, co „równy”. Spróbuj zamiast tego UNION ALL, a otrzymasz dwa rzędy. A jeśli chodzi o „nowy rodzaj operatora porównania”, SQL już go ma. NIE WYRÓŻNIA SIĘ OD. Ale to samo w sobie nie wystarczy. Użycie tego w konstrukcjach SQL, takich jak NATURAL JOIN lub klauzula REFERENCES klucza obcego, będzie wymagało jeszcze dodatkowych opcji tych konstrukcji.
Erwin Smout,
Aha, Erwin Smout. Naprawdę miło cię poznać również na tym forum! Nie wiedziałem, że SQL „NIE ODRÓŻNIA SIĘ OD”. Bardzo interesujące! Ale wydaje się, że dokładnie to miałem na myśli, mówiąc o moim wymyślonym operatorze ==. Czy mógłbyś mi wyjaśnić, dlaczego mówisz, że „to samo w sobie nie wystarczy”?
Rami Ojares
Klauzula REFERENCES z definicji opiera się na równości. Rodzaj ODNIESIEŃ, które dopasowują podrzędną krotkę / wiersz z nadrzędną krotką / wierszem, na podstawie odpowiednich wartości atrybutów NIE ODRÓŻNIONYCH zamiast (bardziej rygorystycznych) RÓWNE, wymagałby możliwości określenia tej opcji, ale składnia nie pozwól na to. Podobnie dla NATURAL JOIN.
Erwin Smout
Aby klucz obcy działał, odwołanie musi być unikalne (tj. Wszystkie wartości muszą być różne). Co oznacza, że ​​może mieć jedną wartość zerową. Wszystkie wartości null mogą wtedy odnosić się do tego pojedynczego null, jeśli REFERENCES zostałby zdefiniowany za pomocą operatora NOT DISTINCT. Myślę, że byłoby lepiej (w sensie bardziej użytecznym). W przypadku JOIN (zarówno zewnętrznych, jak i wewnętrznych) myślę, że ścisłe równe są lepsze, ponieważ „NULL MATCHES” mnoży się, gdy wartości null po lewej stronie będą pasować do wszystkich zer po prawej stronie.
Rami Ojares,
1

Nadal uważam, że jest to fundamentalna / funkcjonalna wada spowodowana technicznością. Jeśli masz opcjonalne pole, za pomocą którego możesz zidentyfikować klienta, musisz teraz zhakować do niego wartość fikcyjną, tylko dlatego, że NULL! = NULL, niezbyt eleganckie, ale jest to „standard branżowy”

Adriaan Davel
źródło