Có gì sai với các cột nullable trong các khóa chính tổng hợp?


149

ORACLE không cho phép các giá trị NULL trong bất kỳ cột nào chứa khóa chính. Dường như điều tương tự cũng đúng với hầu hết các hệ thống "cấp doanh nghiệp" khác.

Đồng thời, hầu hết các hệ thống cũng cho phép các chống chỉ định duy nhất trên các cột không thể.

Tại sao các ràng buộc duy nhất có thể có NULL nhưng các khóa chính thì không thể? Có một lý do logic cơ bản cho điều này, hay đây là một hạn chế kỹ thuật?


Câu trả lời:


216

Các khóa chính dành cho các hàng xác định duy nhất. Điều này được thực hiện bằng cách so sánh tất cả các phần của khóa với đầu vào.

Theo định nghĩa, NULL không thể là một phần của một so sánh thành công. Ngay cả một so sánh với chính nó ( NULL = NULL) sẽ thất bại. Điều này có nghĩa là một khóa chứa NULL sẽ không hoạt động.

Ngoài ra, NULL được cho phép trong khóa ngoại, để đánh dấu mối quan hệ tùy chọn. (*) Cho phép nó trong PK cũng sẽ phá vỡ điều này.


(*) Một lời cảnh báo: Có khóa ngoại không có giá trị là không sạch thiết kế cơ sở dữ liệu quan hệ.

Nếu có hai thực thể ABnơi Acó thể tùy chọn liên quan đến B, các giải pháp làm sạch là để tạo ra một bảng có độ phân giải (giả sử AB). Bảng đó sẽ liên kết Avới B: Nếu có một mối quan hệ sau đó nó sẽ chứa một kỷ lục, nếu có không phải là sau đó nó sẽ không được.


5
Tôi đã thay đổi câu trả lời được chấp nhận cho câu hỏi này. Đánh giá bằng phiếu bầu, câu trả lời này là rõ ràng nhất đối với nhiều người hơn. Tôi vẫn cảm thấy rằng câu trả lời của Tony Andrew giải thích ý định đằng sau thiết kế này tốt hơn; kiểm tra nó là tốt!
Roman Starkov 16/2/2015

2
Q: Khi nào bạn muốn có một NULL FK thay vì thiếu một hàng? Trả lời: Chỉ trong một phiên bản của lược đồ được chuẩn hóa để tối ưu hóa. Trong các lược đồ không tầm thường, các vấn đề không chuẩn hóa như thế này có thể gây ra sự cố bất cứ khi nào các tính năng mới được yêu cầu. otoh, đám đông thiết kế web không quan tâm. Tôi ít nhất sẽ thêm một lưu ý thận trọng về điều này thay vì làm cho nó có vẻ như là một ý tưởng thiết kế tốt.
zxq9

3
"Có khóa ngoại không có giá trị không phải là thiết kế cơ sở dữ liệu quan hệ sạch." - một thiết kế cơ sở dữ liệu không có giá trị (dạng thông thường thứ sáu) luôn luôn tăng thêm độ phức tạp, tiết kiệm không gian thu được thường vượt trội hơn so với công việc lập trình viên thêm cần thiết để nhận ra những lợi ích đó.
Đại

1
Nếu đó là bảng phân giải ABC thì sao? với tùy chọn C
Bart Calixto

1
Tôi đã cố gắng tránh viết "bởi vì tiêu chuẩn cấm nó", vì điều này thực sự không giải thích được gì.
Tomalak

62

Khóa chính xác định một mã định danh duy nhất cho mỗi hàng trong bảng: khi một bảng có khóa chính, bạn có một cách đảm bảo để chọn bất kỳ hàng nào từ nó.

Một ràng buộc duy nhất không nhất thiết phải xác định mỗi hàng; nó chỉ xác định rằng nếu một hàng có các giá trị trong các cột của nó, thì chúng phải là duy nhất. Điều này là không đủ để xác định duy nhất mỗi hàng, đó là điều mà một khóa chính phải làm.


10
Trong Sql Server, một ràng buộc duy nhất có cột nullable, chỉ cho phép giá trị 'null' trong cột đó (đưa ra các giá trị giống hệt nhau cho các cột khác của ràng buộc). Vì vậy, một ràng buộc duy nhất như vậy về cơ bản hoạt động như một pk với một cột không thể.
Gerard

Tôi xác nhận tương tự cho Oracle (11.2)
Alexander Malakhov

2
Trong Oracle (tôi không biết về SQL Server), bảng có thể chứa nhiều hàng trong đó tất cả các cột trong một ràng buộc duy nhất là null. Tuy nhiên, nếu một số cột trong ràng buộc duy nhất không phải là null và một số cột là null thì tính duy nhất được thi hành.
Tony Andrew

Làm thế nào điều này áp dụng cho UNIITE tổng hợp?
Dims

1
@Dims Như với hầu hết mọi thứ khác trong cơ sở dữ liệu SQL "nó phụ thuộc vào việc triển khai". Trong hầu hết các dbs, "khóa chính" thực sự là một ràng buộc ĐỘC ĐÁO bên dưới. Ý tưởng về "khóa chính" không thực sự đặc biệt hay mạnh mẽ hơn khái niệm về ĐỘC ĐÁO. Sự khác biệt thực sự là nếu bạn có hai khía cạnh độc lập của một bảng có thể được đảm bảo ĐỘC ĐÁO thì bạn không có cơ sở dữ liệu được chuẩn hóa theo định nghĩa (bạn đang lưu trữ hai loại dữ liệu trong cùng một bảng).
zxq9

46

Về cơ bản, không có gì sai với NULL trong khóa chính nhiều cột. Nhưng có một ý nghĩa mà nhà thiết kế có thể không có ý định, đó là lý do tại sao nhiều hệ thống gặp lỗi khi bạn thử điều này.

Hãy xem xét trường hợp của các phiên bản mô-đun / gói được lưu trữ dưới dạng một loạt các trường:

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

5 phần tử đầu tiên của khóa chính là các phần được xác định thường xuyên của phiên bản phát hành, nhưng một số gói có phần mở rộng tùy chỉnh thường không phải là số nguyên (như "rc-foo" hoặc "vanilla" hoặc "beta" hoặc bất cứ ai khác cho người mà bốn lĩnh vực là không đủ có thể mơ ước). Nếu một gói không có phần mở rộng, thì đó là NULL trong mô hình trên và sẽ không có hại gì khi để mọi thứ theo cách đó.

Nhưng những gì một NULL? Nó được cho là đại diện cho việc thiếu thông tin, một ẩn số. Điều đó nói rằng, có lẽ điều này có ý nghĩa hơn:

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

Trong phiên bản này, phần "mở rộng" của bộ dữ liệu KHÔNG phải là NULL mà mặc định là một chuỗi trống - có nghĩa là về mặt ngữ nghĩa (và thực tế) khác với NULL. Một NULL là một ẩn số, trong khi một chuỗi trống là một bản ghi có chủ ý của "một cái gì đó không có mặt". Nói cách khác, "trống rỗng" và "null" là những thứ khác nhau. Đó là sự khác biệt giữa "Tôi không có giá trị ở đây" và "Tôi không biết giá trị ở đây là gì".

Khi bạn đăng ký gói thiếu phần mở rộng phiên bản, bạn biết rằng nó thiếu phần mở rộng, vì vậy một chuỗi trống thực sự là giá trị chính xác. Một NULL sẽ chỉ đúng nếu bạn không biết liệu nó có phần mở rộng hay không, hoặc bạn biết rằng nó đã làm nhưng không biết nó là gì. Tình huống này dễ xử lý hơn trong các hệ thống trong đó các giá trị chuỗi là chuẩn, bởi vì không có cách nào để biểu diễn một "số nguyên trống" ngoài việc chèn 0 hoặc 1, sẽ cuộn lên trong bất kỳ phép so sánh nào được thực hiện sau này (có ý nghĩa riêng của nó) *.

Ngẫu nhiên, cả hai cách đều hợp lệ trong Postgres (vì chúng ta đang thảo luận về RDMBS "doanh nghiệp"), nhưng kết quả so sánh có thể thay đổi khá nhiều khi bạn ném NULL vào hỗn hợp - vì NULL == "không biết" nên tất cả kết quả so sánh liên quan đến NULL sẽ trở thành NULL vì bạn không thể biết điều gì đó chưa biết. NGUY HIỂM! Hãy suy nghĩ cẩn thận về điều đó: điều này có nghĩa là kết quả so sánh NULL lan truyền thông qua một loạt các so sánh. Đây có thể là một nguồn lỗi tinh tế khi sắp xếp, so sánh, v.v.

Postgres cho rằng bạn là người lớn và có thể tự đưa ra quyết định này. Oracle và DB2 cho rằng bạn đã không nhận ra mình đang làm điều gì đó ngớ ngẩn và gây ra lỗi. Đây thường là điều đúng, nhưng không phải lúc nào cũng vậy - bạn thực sự có thể không biết và có NULL trong một số trường hợp và do đó để lại một hàng với một yếu tố không xác định mà không thể so sánh có ý nghĩa là hành vi đúng.

Trong mọi trường hợp, bạn nên cố gắng loại bỏ số lượng trường NULL mà bạn cho phép trên toàn bộ lược đồ và gấp đôi khi nói đến các trường là một phần của khóa chính. Trong phần lớn các trường hợp, sự hiện diện của các cột NULL là một dấu hiệu của thiết kế lược đồ không được chuẩn hóa (trái ngược với cố tình không chuẩn hóa) và nên được suy nghĩ rất kỹ trước khi được chấp nhận.

[* LƯU Ý: Có thể tạo một loại tùy chỉnh là liên kết các số nguyên và loại "dưới cùng" về mặt ngữ nghĩa có nghĩa là "trống rỗng" thay vì "không xác định". Thật không may, điều này giới thiệu một chút phức tạp trong các hoạt động so sánh và thường thực sự đúng loại không đáng để nỗ lực trong thực tế vì bạn không nên cho phép nhiều NULLgiá trị ở vị trí đầu tiên. Điều đó nói rằng, thật tuyệt vời nếu các RDBMS sẽ bao gồm một BOTTOMloại mặc định bên cạnh NULLviệc ngăn chặn thói quen kết hợp các ngữ nghĩa của "không có giá trị" với "giá trị không xác định". ]


5
Đây là một câu trả lời RẤT NICE và giải thích rất nhiều về các giá trị NULL và nó có ý nghĩa thông qua nhiều tình huống. Bạn, thưa ngài, bây giờ tôi tôn trọng! Ngay cả ở trường đại học, tôi cũng nhận được một lời giải thích tốt về các giá trị NULL trong cơ sở dữ liệu. Cảm ơn bạn!

Tôi ủng hộ ý chính của câu trả lời này. Nhưng viết như 'được cho là thiếu thông tin, không xác định', 'về mặt ngữ nghĩa (và thực tế) khác với NULL', 'NULL là một ẩn số', 'một chuỗi trống là một bản ghi có chủ ý về "một thứ không có mặt "',' NULL ==" không biết "', v.v ... mơ hồ & gây hiểu lầm & thực sự chỉ ghi nhớ cho các tuyên bố vắng mặt về cách NULL hoặc bất kỳ giá trị nào hoặc có thể hoặc được sử dụng - cho phần còn lại của bài đăng . (Bao gồm cả việc truyền cảm hứng cho thiết kế (xấu) của các tính năng SQL NULL.) Họ không biện minh hay giải thích bất cứ điều gì; chúng nên được giải thích và gỡ lỗi.
philipxy

21

NULL == NULL -> false (ít nhất là trong DBMS)

Vì vậy, bạn sẽ không thể truy xuất bất kỳ mối quan hệ nào bằng cách sử dụng giá trị NULL ngay cả với các cột bổ sung có giá trị thực.


1
Đây có vẻ là câu trả lời hay nhất, nhưng tôi vẫn không hiểu tại sao điều này bị cấm khi tạo khóa chính. Nếu đây chỉ là sự cố truy xuất, bạn có thể sử dụng where pk_1 = 'a' and pk_2 = 'b'với các giá trị bình thường và chuyển sang where pk_1 is null and pk_2 = 'b'khi có giá trị null.
EoghanM

Hoặc thậm chí đáng tin cậy hơn, 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
Câu trả lời sai. NULL == NULL -> KHÔNG GIỚI HẠN. Không sai. Điều đáng chú ý là một ràng buộc không được coi là vi phạm nếu kết quả của bài kiểm tra là UNKNOWN. Điều này thường làm cho nó SEEM như thể so sánh mang lại sai, nhưng nó thực sự không.
Erwin Smout

4

Câu trả lời của Tony Andrew là một câu trả lời đàng hoàng. Nhưng câu trả lời thực sự là đây đã là một quy ước được sử dụng bởi cộng đồng cơ sở dữ liệu quan hệ và KHÔNG phải là một điều cần thiết. Có lẽ đó là một quy ước tốt, có thể không.

So sánh mọi thứ với kết quả NULL trong UNKNOWN (giá trị thật thứ 3). Vì vậy, như đã được đề xuất với null, tất cả sự khôn ngoan truyền thống liên quan đến sự bình đẳng đi ra ngoài cửa sổ. Vâng, đó là cách nó có vẻ như thoạt nhìn.

Nhưng tôi không nghĩ rằng điều này nhất thiết phải như vậy và ngay cả cơ sở dữ liệu SQL cũng không nghĩ rằng NULL phá hủy tất cả khả năng để so sánh.

Chạy trong cơ sở dữ liệu của bạn truy vấn CHỌN * TỪ GIÁ TRỊ (NULL) UNION CHỌN * TỪ GIÁ TRỊ (NULL)

Những gì bạn thấy chỉ là một tuple với một thuộc tính có giá trị NULL. Vì vậy, công đoàn nhận ra ở đây hai giá trị NULL là bằng nhau.

Khi so sánh một khóa tổng hợp có 3 thành phần với một tuple với 3 thuộc tính (1, 3, NULL) = (1, 3, NULL) <=> 1 = 1 VÀ 3 = 3 VÀ NULL = NULL Kết quả của việc này là UNKNOWN .

Nhưng chúng ta có thể định nghĩa một loại toán tử so sánh mới, vd. ==. X == Y <=> X = Y HOẶC (X LÀ NULL VÀ Y LÀ NULL)

Có loại toán tử đẳng thức này sẽ làm cho các khóa tổng hợp có các thành phần null hoặc khóa không tổng hợp có giá trị null không có gì khó hiểu.


1
Không, UNION đã công nhận hai NULL là không khác biệt. Mà không giống như "bằng". Thay vào đó, hãy thử UNION ALL và bạn sẽ nhận được hai hàng. Và đối với "loại toán tử so sánh mới", SQL đã có nó. KHÔNG PHẢI LÀ TỪ CHỐI. Nhưng điều đó tự nó là không đủ. Sử dụng điều này trong các cấu trúc SQL như NATURAL THAM GIA hoặc mệnh đề TÀI LIỆU THAM KHẢO của khóa ngoại, sẽ yêu cầu các tùy chọn bổ sung trên các cấu trúc đó.
Erwin Smout

Aha, Erwin Smout. Thật sự rất vui được gặp bạn trên diễn đàn này! Tôi đã không nhận thức được "KHÔNG PHẢI LÀ TỪ CHỐI" của SQL. Rất thú vị! Nhưng có vẻ như đó chính xác là những gì tôi muốn nói với toán tử == của tôi. Bạn có thể giải thích cho tôi tại sao bạn nói rằng: "điều đó tự nó không đủ"?
Rami Ojares

Mệnh đề TÀI LIỆU THAM KHẢO xây dựng trên sự bình đẳng, theo định nghĩa. Một loại TÀI LIỆU THAM KHẢO phù hợp với một tuple / hàng con với một tuple / hàng cha, dựa trên các giá trị thuộc tính tương ứng là KHÔNG DISTINCT thay vì (THIẾT BỊ), sẽ yêu cầu khả năng chỉ định tùy chọn này, nhưng cú pháp không cho phép nó Ditto cho THAM GIA TỰ NHIÊN.
Erwin Smout

Để khóa ngoại hoạt động, phần giới thiệu phải là duy nhất (nghĩa là tất cả các giá trị phải khác biệt). Điều đó có nghĩa là nó có thể có một giá trị null duy nhất. Tất cả các giá trị null sau đó có thể tham chiếu đến giá trị null đơn đó nếu TÀI LIỆU THAM KHẢO sẽ được xác định bằng toán tử KHÔNG DISTINCT. Tôi nghĩ rằng nó sẽ tốt hơn (theo nghĩa hữu ích hơn). Với THAM GIA (cả bên ngoài và bên trong) tôi nghĩ rằng các giá trị nghiêm ngặt sẽ tốt hơn bởi vì "MATLLES" sẽ nhân lên khi null ở bên trái sẽ khớp với tất cả các null ở bên phải.
Rami Ojares

1

Tôi vẫn tin rằng đây là một lỗ hổng cơ bản / chức năng do một kỹ thuật mang lại. Nếu bạn có một trường tùy chọn mà bạn có thể xác định một khách hàng mà bây giờ bạn phải hack một giá trị giả vào đó, chỉ vì NULL! = NULL, không đặc biệt thanh lịch nhưng đó là một "tiêu chuẩn ngành"

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.