NULL,因 NULL = NULL 為 UNKNOWN,不違反唯一性。
CREATE TABLE t (id NUMBER);
ALTER TABLE t ADD UNIQUE (id);
INSERT INTO t VALUES (1); -- 成功
INSERT INTO t VALUES (1); -- ORA-00001
INSERT INTO t VALUES (NULL); -- 成功
INSERT INTO t VALUES (NULL); -- 成功
INSERT INTO t VALUES (NULL); -- 成功
DROP TABLE t PURGE;
CREATE TABLE t (id NUMBER, id2 NUMBER);
ALTER TABLE t ADD UNIQUE (id, id2);
INSERT INTO t VALUES (1, 1); -- 成功
INSERT INTO t VALUES (1, NULL); -- 成功
INSERT INTO t VALUES (2, NULL); -- 成功
INSERT INTO t VALUES (1, NULL); -- ORA-00001 (ID=1 重複)
INSERT INTO t VALUES (NULL, NULL);-- 成功
INSERT INTO t VALUES (NULL, NULL);-- 成功
INSERT INTO t VALUES (NULL, NULL);-- 成功
Oracle 唯一約束由 唯一 B-Tree 索引 執行。B-Tree 索引**不儲存完全為 NULL 的鍵值** (所有欄位皆 NULL)。
NULL 不進索引 → 索引無重複 → 允許無限 NULL。(A, B):
(1, NULL) → 索引鍵為 (1) (忽略尾部 NULL) → 存入索引。(1, NULL) → 索引鍵同為 (1) → 唯一衝突。(NULL, NULL) → 全 NULL → 不進索引 → 允許無限筆。UNIQUE NULLS NOT DISTINCT 選項,Oracle 至今未實作 (19c 仍無)。直接建立 UNIQUE (doc_no, line_no) 會導致多筆表頭 (line_no=NULL) 衝突。
-- 將 NULL 轉為特殊值 -1 參與唯一性判斷
CREATE UNIQUE INDEX uq_doc_line ON documents (
doc_no,
NVL(line_no, -1)
);
優點:語意清晰、索引可被查詢使用、不修改資料。
ALTER TABLE documents ADD (line_no_nvl NUMBER GENERATED ALWAYS AS (NVL(line_no, -1)) VIRTUAL);
ALTER TABLE documents ADD CONSTRAINT uq_doc_line UNIQUE (doc_no, line_no_nvl);
| 約束類型 | 全 NULL | 部分 NULL | 底層原因 |
|---|---|---|---|
| 單欄位 UNIQUE | 允許無限筆 | N/A | NULL 不進 B-Tree 索引 |
| 複合 UNIQUE | 允許無限筆 | 非 NULL 部分重複即衝突 | 部分 NULL 仍產生索引鍵 (前導非 NULL 欄位) |
設計複合唯一約束時,**務必評估「部分 NULL」業務語意**,必要時用函數式索引或虛擬欄位修正行為。