你可以实现它没有肮脏的伎俩. Just 扩展外键引用所选选项以包含variable_id
此外choice_id
.
这是一个工作演示。临时表,这样你就可以轻松地使用它:
CREATE TABLE systemvariables (
variable_id int PRIMARY KEY
, choice_id int
, variable text
);
INSERT INTO systemvariables(variable_id, variable) VALUES
(1, 'var1')
, (2, 'var2')
, (3, 'var3')
;
CREATE TABLE variableoptions (
option_id int PRIMARY KEY
, variable_id int REFERENCES systemvariables ON UPDATE CASCADE ON DELETE CASCADE
, option text
, UNIQUE (option_id, variable_id) -- needed for the FK
);
ALTER TABLE systemvariables
ADD CONSTRAINT systemvariables_choice_id_fk
FOREIGN KEY (choice_id, variable_id) REFERENCES variableoptions(option_id, variable_id);
INSERT INTO variableoptions VALUES
(1, 'var1_op1', 1)
, (2, 'var1_op2', 1)
, (3, 'var1_op3', 1)
, (4, 'var2_op1', 2)
, (5, 'var2_op2', 2)
, (6, 'var3_op1', 3)
;
允许选择关联选项:
UPDATE systemvariables SET choice_id = 2 WHERE variable_id = 1;
UPDATE systemvariables SET choice_id = 5 WHERE variable_id = 2;
UPDATE systemvariables SET choice_id = 6 WHERE variable_id = 3;
但没有什么出格的地方:
UPDATE systemvariables SET choice_id = 7 WHERE variable_id = 3;
UPDATE systemvariables SET choice_id = 4 WHERE variable_id = 1;
ERROR: insert or update on table "systemvariables" violates foreign key constraint "systemvariables_choice_id_fk"
DETAIL: Key (choice_id,variable_id)=(4,1) is not present in table "variableoptions".
正是您想要的。
所有关键列NOT NULL
我想我在后面的答案中找到了更好的解决方案:
- 如何处理相互依赖的插入 https://stackoverflow.com/questions/24813000/how-to-deal-with-mutually-dependent-inserts/24816197#24816197
解决@ypercube 在评论中提出的问题 https://stackoverflow.com/questions/8394177/complex-foreign-key-constraint-in-sqlalchemy/8395021#comment10383559_8395021,为了避免具有未知关联的条目,请使所有关键列NOT NULL
,包括外键。
循环依赖通常会使这变得不可能。这就是经典鸡蛋问题:两者之一必须先出现才能产生另一个。但大自然找到了解决办法,Postgres 也是如此:可延迟的外键约束.
CREATE TABLE systemvariables (
variable_id int PRIMARY KEY
, variable text
, choice_id int NOT NULL
);
CREATE TABLE variableoptions (
option_id int PRIMARY KEY
, option text
, variable_id int NOT NULL REFERENCES systemvariables
ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED
, UNIQUE (option_id, variable_id) -- needed for the foreign key
);
ALTER TABLE systemvariables
ADD CONSTRAINT systemvariables_choice_id_fk FOREIGN KEY (choice_id, variable_id)
REFERENCES variableoptions(option_id, variable_id) DEFERRABLE INITIALLY DEFERRED; -- no CASCADING here!
New变量和关联选项必须插入到同一事务中:
BEGIN;
INSERT INTO systemvariables (variable_id, variable, choice_id)
VALUES
(1, 'var1', 2)
, (2, 'var2', 5)
, (3, 'var3', 6);
INSERT INTO variableoptions (option_id, option, variable_id)
VALUES
(1, 'var1_op1', 1)
, (2, 'var1_op2', 1)
, (3, 'var1_op3', 1)
, (4, 'var2_op1', 2)
, (5, 'var2_op2', 2)
, (6, 'var3_op1', 3);
END;
The NOT NULL
约束不能推迟,而是立即强制执行。但外键约束can,因为我们就是这样定义的。交易结束时进行检查,避免了先有鸡还是先有蛋的问题。
In this edited设想,两个外键都被延迟。您可以按任意顺序输入变量和选项。
如果您在两个表中输入相关条目,您甚至可以使其与普通的不可延迟 FK 约束一起使用一份声明使用 CTE,详细信息请参见链接的答案 https://stackoverflow.com/a/24816197/939860.
您可能已经注意到第一个外键约束没有CASCADE
修饰符。 (允许更改是没有意义的variableoptions.variable_id
级联回来。
另一方面,第二个外键有一个CASCADE
修饰符并被定义DEFERRABLE
尽管如此。这有一些限制。手册 https://www.postgresql.org/docs/current/sql-createtable.html:
参考动作以外的动作NO ACTION
支票不能延期,
即使约束被声明为可延迟的。
NO ACTION
是默认值。
因此,引用完整性检查INSERT
被推迟,但宣布的级联行动DELETE
and UPDATE
不是。在 PostgreSQL 9.0 或更高版本中不允许执行以下操作,因为在每个语句之后都会强制执行约束:
UPDATE option SET var_id = 4 WHERE var_id = 5;
DELETE FROM var WHERE var_id = 5;
Details:
- 定义 DEFERRABLE INITIALLY IMMEDIATE 的约束仍然是 DEFERRED? https://stackoverflow.com/questions/10032272/constraint-defined-deferrable-initially-immediate-is-still-deferred