[英]Complex foreign key constraint in SQLAlchemy
我有两个表, SystemVariables
和VariableOptions
。 SystemVariables
应该是不言自明的, VariableOptions
包含所有VariableOptions
所有可能选择。
VariableOptions
有一个外键variable_id
,它指出了哪个变量是一个选项。 SystemVariables
有一个外键, choice_id
,它指出哪个选项是当前选择的选项。
我已经得到了,周围用循环关系use_alter
上choice_id
,并post_update
上SystemVariables
“ choice
关系。 但是,我想添加一个额外的数据库约束,以确保choice_id
有效(即它指的是引用它的选项)。
我需要的逻辑,假设sysVar
代表SystemVariables
表中的SystemVariables
,基本上是:
VariableOptions[sysVar.choice_id].variable_id == sysVar.id
但我不知道如何使用SQL,声明式或任何其他方法构造这种约束。 如果有必要,我可以在应用程序级别验证这一点,但如果可能的话,我想在数据库级别进行验证。 我正在使用Postgres 9.1。
这可能吗?
你可以在没有肮脏技巧的情况下实现。 只需扩展引用所选选项的外键 ,除了choice_id
之外还要包含variable_id
。
这是一个工作演示。 临时表,所以你可以轻松玩它:
CREATE TEMP TABLE systemvariables (
variable_id integer PRIMARY KEY
, variable text
, choice_id integer
);
INSERT INTO systemvariables(variable_id, variable)
VALUES
(1, 'var1')
, (2, 'var2')
, (3, 'var3');
CREATE TEMP TABLE variableoptions (
option_id integer PRIMARY KEY
, option text
, variable_id integer REFERENCES systemvariables(variable_id)
ON UPDATE CASCADE ON DELETE CASCADE
, 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);
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".
Voilá 。 正是你想要的。
我想我在后面的回答中找到了一个更好的解决方案:
在注释中解决@ ypercube的问题 ,以避免具有未知关联的条目使所有键列NOT NULL
,包括外键。
循环依赖通常会使这种情况变得不可能。 这是经典的鸡蛋问题:两者中的一个必须先在那里产生另一个。 但大自然找到了解决方法,Postgres也是如此: 可延迟的外键约束 。
CREATE TEMP TABLE systemvariables (
variable_id integer PRIMARY KEY
, variable text
, choice_id integer NOT NULL
);
CREATE TEMP TABLE variableoptions (
option_id integer PRIMARY KEY
, option text
, variable_id integer NOT NULL
REFERENCES systemvariables(variable_id)
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!
必须在同一事务中插入新变量和相关选项:
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;
NOT NULL
约束不能延迟,它会立即强制执行。 但是外键约束可以 ,因为我们这样定义它。 在交易结束时检查,这避免了鸡蛋问题。
在此编辑的方案中, 两个外键都是延迟的 。 您可以按任意顺序输入变量和选项。
您可能已经注意到第一个外键约束没有CASCADE
修饰符。 (允许对variableoptions.variable_id
更改进行级联反应是没有意义的。
另一方面,第二个外键具有CASCADE
修饰符,并且仍被定义为可延迟。 这带来了一些限制。 手册 :
即使约束被声明为可延迟,也不能延迟除
NO ACTION
检查以外的参考动作。
NO ACTION
是默认值。
因此,对INSERT
参照完整性检查是延迟的,但DELETE
和UPDATE
上声明的级联操作不是。 PostgreSQL 9.0或9.1中不允许以下内容,因为在每个语句后强制执行约束:
UPDATE option SET var_id = 4 WHERE var_id = 5;
DELETE FROM var WHERE var_id = 5;
细节:
奇怪的是,同样的事情在PostgreSQL 8.4中有效 ,而文档声称具有相同的行为。 看起来像旧版本中的一个错误 - 即使它看起来有益而不是乍一看有害。 必须已针对较新版本进行修复。
编辑: SQLAlchemy的0.7.4版本(在我开始询问此问题的同一天发布,7/12 / '11!),包含主键的新autoincrement
值,也是外键的一部分, ignore_fk
。 文档也已经扩展到包括我最初想要完成的一个很好的例子。
现在所有人都在这里解释清楚。
如果您想查看我在上述版本之前提出的代码,请查看此答案的修订历史记录。
我真的不喜欢循环引用。 通常有一种方法可以避免它们。 这是一种方法:
SystemVariables
---------------
variable_id
PRIMARY KEY (variable_id)
VariableOptions
---------------
option_id
variable_id
PRIMARY KEY (option_id)
UNIQUE KEY (variable_id, option_id)
FOREIGN KEY (variable_id)
REFERENCES SystemVariables(variable_id)
CurrentOptions
--------------
variable_id
option_id
PRIMARY KEY (variable_id)
FOREIGN KEY (variable_id, option_id)
REFERENCES VariableOptions(variable_id, option_id)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.