唯一列中的 NULL 值永久链接
一个众所周知但令人讨厌的怪异值NULL是NULL != NULL,因此一UNIQUE列仍然可以有多个NULL值。
#PG培训#PG考试#postgresql培训#postgresql考试#postgresql认证
(为了简单起见,示例使用数字 id 列,但我通常更喜欢更复杂的 id,例如ULID。)
CREATE TABLE test (
id serial PRIMARY KEY,
value TEXT UNIQUE
);
INSERT INTO test (value) VALUES ('a');
-- This fails on the duplicate:
-- ERROR: duplicate key value violates unique constraint "test_value_key"
-- DETAIL: Key (value)=(a) already exists.
INSERT INTO test (value) VALUES ('a');
-- But this does not:
INSERT INTO test (value) VALUES (null);
INSERT INTO test (value) VALUES (null);
SELECT * from test;
id | value
----+-------
1 | a
3 |
4 |
(3 rows)
然而,PostgreSQL 15 发布了一项新功能,可以改变这种行为UNIQUE NULLS NOT DISTINCT::
CREATE TABLE test (
id serial PRIMARY KEY,
value TEXT UNIQUE NULLS NOT DISTINCT
);
-- Now this fails on the second insert:
-- ERROR: duplicate key value violates unique constraint "test_value_key"
-- DETAIL: Key (value)=(null) already exists.
INSERT INTO test (value) VALUES (null);
INSERT INTO test (value) VALUES (null);
确保单个列有值永久链接
我遇到的一个常见用例是,一个表有多个外键,但只需要填充一个。例如,假设我们有一个notifications表来表示我们发出的通知(例如电子邮件、短信等)。这些通知可能会被触发并与我们系统中的特定实体相关,例如订单、用户、公司等。我们想添加外键来表示此通知的用途,但我们只想填充其中一个。
例如:
CREATE TABLE notifications (
id serial,
company_id INT REFERENCES companies (id),
order_id INT REFERENCES orders (id),
user_id INT REFERENCES users (id)
);
INSERT INTO notifications (company_id) VALUES (100);
INSERT INTO notifications (order_id) VALUES (200);
SELECT * from notifications;
id | company_id | order_id | user_id
----+------------+----------+---------
1 | 100 | |
2 | | 200 |
(2 rows)
通常,我会看到像这样的表添加约束以确保至少有一列被填充:
ALTER TABLE notifications
ADD CONSTRAINT notifications_reference
CHECK (company_id IS NOT NULL OR order_id IS NOT NULL OR user_id IS NOT NULL);
但是,这并不能阻止您意外填充多个列。如果您使用为您生成 SQL 的对象关系映射器 (ORM),并且您意外设置了对象的多个属性,则很容易发生这种情况:
INSERT INTO notifications (company_id, order_id, user_id)
VALUES (NULL, 300, 400);
SELECT * from notifications;
id | company_id | order_id | user_id
----+------------+----------+---------
1 | 100 | |
2 | | 200 |
3 | | 300 | 400
(3 rows)
不过,有一对 PostgreSQL 函数可以让我们编写更好的约束检查,称为num_nulls/num_nonnulls。这让我们可以检查一组列中是否只有一个非 NULL 值。例如:
ALTER TABLE notifications
ADD CONSTRAINT notifications_reference
CHECK (num_nonnulls(company_id, order_id, user_id) = 1);
-- Now we get an error on insert if there is more than one value:
-- ERROR: new row for relation "notifications" violates check constraint "notifications_reference"
-- DETAIL: Failing row contains (3, null, 300, 400).
INSERT INTO notifications (company_id, order_id, user_id)
VALUES (NULL, 300, 400);
-- Or if there are no values:
-- ERROR: new row for relation "notifications" violates check constraint "notifications_reference"
-- DETAIL: Failing row contains (4, null, null, null).
INSERT INTO notifications (company_id, order_id, user_id)
VALUES (NULL, NULL, NULL);