the*_*erd 21 postgresql constraint referential-integrity postgresql-9.5
我有一个专栏: standard BOOLEAN NOT NULL
我想强制执行一行 True,而所有其他行都是 False。根据此约束,没有 FK 或其他任何东西。我知道我可以用 plpgsql 完成它,但这似乎是一个大锤。我更喜欢 a CHECKorUNIQUE约束之类的东西。越简单越好。
一行必须为 True,它们不能全部为 False(因此插入的第一行必须为 True)。
该行将需要更新,这意味着我必须等待检查约束,直到更新完成,因为所有行可能先设置为 False,然后再将一行设置为 True。
products.tax_rate_id和之间有一个 FK tax_rate.id,但它与默认或标准税率无关,用户可选择以轻松创建新产品。
PostgreSQL 9.5 如果重要的话。
表是税率。其中一种税率是默认值(standard因为默认值是 Postgres 命令)。添加新产品时,标准税率适用于该产品。如果没有standard,则数据库必须进行猜测或进行各种不需要的检查。我想,简单的解决方案是确保有一个standard.
上面的“默认”是指表示层(UI)。用户可以选择更改默认税率。我要么需要添加额外的检查以确保 GUI/用户不会尝试将 tax_rate_id 设置为 NULL,或者只是设置默认税率。
Erw*_*ter 18
由于您只需要一个带有 的列standard = true,因此在所有其他行中将标准设置为 NULL。然后一个简单的UNIQUE约束工作,因为 NULL 值不违反它:
CREATE TABLE taxrate (
taxrate int PRIMARY KEY
, standard bool DEFAULT true
, CONSTRAINT standard_true_or_null CHECK (standard) -- yes, that's the whole constraint
, CONSTRAINT standard_only_1_true UNIQUE (standard)
);
Run Code Online (Sandbox Code Playgroud)
DEFAULT是一个可选的提醒,输入的第一行应成为默认值。它没有强制执行任何事情。虽然您不能将多行设置为standard = true,但您仍然可以将所有行设置为 NULL。没有干净的方法可以仅通过单个表中的约束来防止这种情况。CHECK约束不考虑其他行(没有肮脏的技巧)。
有关的:
更新:
BEGIN;
UPDATE taxrate SET standard = NULL WHERE standard;
UPDATE taxrate SET standard = TRUE WHERE taxrate = 2;
COMMIT;
Run Code Online (Sandbox Code Playgroud)
允许这样的命令(仅在语句末尾满足约束):
WITH kingdead AS (
UPDATE taxrate
SET standard = NULL
WHERE standard
)
UPDATE taxrate
SET standard = TRUE
WHERE taxrate = 1;
Run Code Online (Sandbox Code Playgroud)
..UNIQUE约束必须是DEFERRABLE. 看:
dbfiddle在这里
有一个单行的第二个表,如:
以超级用户身份创建:
CREATE TABLE taxrate (
taxrate int PRIMARY KEY
);
CREATE TABLE taxrate_standard (
taxrate int PRIMARY KEY REFERENCES taxrate
);
CREATE UNIQUE INDEX taxrate_standard_singleton ON taxrate_standard ((true)); -- singleton
REVOKE DELETE ON TABLE taxrate_standard FROM public; -- can't delete
INSERT INTO taxrate (taxrate) VALUES (42);
INSERT INTO taxrate_standard (taxrate) VALUES (42);
Run Code Online (Sandbox Code Playgroud)
现在总是有一行指向标准(在这个简单的例子中也直接代表标准费率)。只有超级用户才能打破它。您也可以使用 trigger 禁止这样做BEFORE DELETE。
dbfiddle在这里
有关的:
您可以添加 aVIEW以查看与变体 1相同的内容:
CREATE VIEW taxrate_combined AS
SELECT t.*, (ts.taxrate = t.taxrate) AS standard
FROM taxrate t
LEFT JOIN taxrate_standard ts USING (taxrate);
Run Code Online (Sandbox Code Playgroud)
在您只需要标准费率的查询中,taxrate_standard.taxrate直接使用(仅)。
你后来补充说:
products.tax_rate_id和之间有一个 FKtax_rate.id
一个穷人对变体 2的实现只是在products(或任何类似的表)中添加一行指向标准税率;如果您的设置允许,您可能会称之为“标准税率”的虚拟产品。
FK 约束强制执行参照完整性。要完成它,请tax_rate_id IS NOT NULL对行强制执行(如果一般列不是这种情况)。并禁止删除。两者都可以放入触发器中。没有额外的桌子,但不那么优雅,也不那么可靠。
McN*_*ets 14
您可以使用过滤索引
Run Code Online (Sandbox Code Playgroud)create table test ( id int primary key, foo bool );
Run Code Online (Sandbox Code Playgroud)CREATE UNIQUE INDEX only_one_row_with_column_true_uix ON test (foo) WHERE (foo); --> where foo is true
Run Code Online (Sandbox Code Playgroud)insert into test values (1, false); insert into test values (2, true); insert into test values (3, false); insert into test values (4, false); insert into test values (5, true);错误:重复键值违反唯一约束“only_one_row_with_column_true_uix” 详细信息:键 (foo)=(t) 已经存在。
dbfiddle在这里
但是正如您所说,第一行必须为真,然后您可以使用 CHECK 约束,但即使使用函数,您也可以稍后删除第一行。
Run Code Online (Sandbox Code Playgroud)create function check_one_true(new_foo bool) returns int as $$ begin return ( select count(*) + (case new_foo when true then 1 else 0 end) from test where foo = true ); end $$ language plpgsql stable;
Run Code Online (Sandbox Code Playgroud)alter table test add constraint ck_one_true check(check_one_true(foo) = 1);
Run Code Online (Sandbox Code Playgroud)insert into test values (1, true); insert into test values (2, false); insert into test values (3, false); insert into test values (4, false);
Run Code Online (Sandbox Code Playgroud)insert into test values (5, true);错误:关系“test”的新行违反检查约束“ck_one_true” 详细信息:失败的行包含 (5, t)。
Run Code Online (Sandbox Code Playgroud)select * from test;身份证 | 富 -: | :-- 1 | 吨 2 | F 3 | F 4 | F
Run Code Online (Sandbox Code Playgroud)delete from test where id = 1;
dbfiddle在这里
您可以通过添加 BEFORE DELETE 触发器来解决它,以确保永远不会删除第一行(foo 为 true)。
Run Code Online (Sandbox Code Playgroud)create function dont_delete_foo_true() returns trigger as $x$ begin if old.foo then raise exception 'Can''t delete row where foo is true.'; end if; return old; end; $x$ language plpgsql;
Run Code Online (Sandbox Code Playgroud)create trigger trg_test_delete before delete on test for each row execute procedure dont_delete_foo_true();
Run Code Online (Sandbox Code Playgroud)delete from test where id = 1;错误:无法删除 foo 为真的行。
dbfiddle在这里
| 归档时间: |
|
| 查看次数: |
18036 次 |
| 最近记录: |