简体   繁体   English

SQLAlchemy中复杂的外键约束

[英]Complex foreign key constraint in SQLAlchemy

I have two tables, SystemVariables and VariableOptions . 我有两个表, SystemVariablesVariableOptions SystemVariables should be self-explanatory, and VariableOptions contains all of the possible choices for all of the variables. SystemVariables应该是不言自明的, VariableOptions包含所有VariableOptions所有可能选择。

VariableOptions has a foreign key, variable_id , which states which variable it is an option for. VariableOptions有一个外键variable_id ,它指出了哪个变量是一个选项。 SystemVariables has a foreign key, choice_id , which states which option is the currently selected one. SystemVariables有一个外键, choice_id ,它指出哪个选项是当前选择的选项。

I've gotten around the circular relationship using use_alter on choice_id , and post_update on SystemVariables ' choice relationship. 我已经得到了,周围用循环关系use_alterchoice_id ,并post_updateSystemVariableschoice关系。 However, I would like to add an extra database constraint that will ensure that choice_id is valid (ie it's referring to an option that is referring back to it). 但是,我想添加一个额外的数据库约束,以确保choice_id有效(即它指的是引用它的选项)。

The logic I need, assuming that sysVar represents a row in the SystemVariables table, is basically: 我需要的逻辑,假设sysVar代表SystemVariables表中的SystemVariables ,基本上是:

VariableOptions[sysVar.choice_id].variable_id == sysVar.id

But I don't know how to construct this kind of constraint using SQL, declarative, or any other method. 但我不知道如何使用SQL,声明式或任何其他方法构造这种约束。 If necessary I could just validate this at the application level, but I'd like to have it at the database level if possible. 如果有必要,我可以在应用程序级别验证这一点,但如果可能的话,我想在数据库级别进行验证。 I'm using Postgres 9.1. 我正在使用Postgres 9.1。

Is this possible? 这可能吗?

You can implement that without dirty tricks . 你可以在没有肮脏技巧的情况下实现。 Just extend the foreign key referencing the chosen option to include variable_id in addition to choice_id . 只需扩展引用所选选项的外键 ,除了choice_id之外还要包含variable_id

Here is a working demo. 这是一个工作演示。 Temporary tables, so you can easily play with it: 临时表,所以你可以轻松玩它:

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);

Choosing an associated option is allowed: 允许选择关联选项:

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;

But there is no getting out of line: 但是没有脱节:

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á . Voilá Exactly what you wanted. 正是你想要的。


All key columns NOT NULL 所有键列NOT NULL

I think I found a better solution in this later answer: 我想我在后面的回答中找到了一个更好的解决方案:

Addressing the @ypercube's question in the comments , to avoid entries with unknown association make all key columns NOT NULL , including foreign keys. 在注释中解决@ ypercube的问题 ,以避免具有未知关联的条目使所有键列NOT NULL ,包括外键。

The circular dependency would normally make that impossible. 循环依赖通常会使这种情况变得不可能。 It's the classical chicken-egg problem: one of both has to be there first to spawn the other. 这是经典的鸡蛋问题:两者中的一个必须先在那里产生另一个。 But nature found a way around it, and so did Postgres: deferrable foreign key constraints . 但大自然找到了解决方法,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!

New variables and associated options have to be inserted in the same transaction: 必须在同一事务中插入变量和相关选项:

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 constraint cannot be deferred, it is enforced immediately. NOT NULL约束不能延迟,它会立即强制执行。 But the foreign key constraint can , because we defined it that way. 但是外键约束可以 ,因为我们这样定义它。 It is checked at the end of the transaction, which avoids the chicken-egg problem. 在交易结束时检查,这避免了鸡蛋问题。

In this edited scenario, both foreign keys are deferred . 在此编辑的方案中, 两个外键都是延迟的 You can enter variables and options in arbitrary sequence. 您可以按任意顺序输入变量和选项。

You may have noticed that the first foreign key constraint has no CASCADE modifier. 您可能已经注意到第一个外键约束没有CASCADE修饰符。 (It wouldn't make sense to allow changes to variableoptions.variable_id to cascade back. (允许对variableoptions.variable_id更改进行级联反应是没有意义的。

On the other hand, the second foreign key has a CASCADE modifier and is defined deferrable nonetheless. 另一方面,第二个外键具有CASCADE修饰符,并且仍被定义为可延迟。 This carries some limitations. 这带来了一些限制。 The manual : 手册

Referential actions other than the NO ACTION check cannot be deferred, even if the constraint is declared deferrable. 即使约束被声明为可延迟,也不能延迟除NO ACTION检查以外的参考动作。

NO ACTION is the default. NO ACTION是默认值。

So, referential integrity checks on INSERT are deferred, but the declared cascading actions on DELETE and UPDATE are not. 因此,对INSERT参照完整性检查是延迟的,但DELETEUPDATE上声明的级联操作不是。 The following is not permitted in PostgreSQL 9.0 or 9.1 because constraints are enforce after each statement: PostgreSQL 9.0或9.1中不允许以下内容,因为在每个语句后强制执行约束:

UPDATE option SET var_id = 4 WHERE var_id = 5;
DELETE FROM var WHERE var_id = 5;

Details: 细节:

Strangely enough, the same thing works in PostgreSQL 8.4 , while the documentation claims the same behavior. 奇怪的是,同样的事情在PostgreSQL 8.4中有效 ,而文档声称具有相同的行为。 Looks like a bug in the old version - even if it seems to be beneficial rather than harmful at a first glance. 看起来像旧版本中的一个错误 - 即使它看起来有益而不是乍一看有害。 Must have been fixed for newer versions. 必须已针对较新版本进行修复。

EDIT: The 0.7.4 release of SQLAlchemy (released the same day I started asking about this issue, 7/12/'11!), contains a new autoincrement value for primary keys that are also part of foreign keys, ignore_fk . 编辑: SQLAlchemy的0.7.4版本(在我开始询问此问题的同一天发布,7/12 / '11!),包含主键的新autoincrement值,也是外键的一部分, ignore_fk The documentation has also been expanded to include a good example of what I was originally trying to accomplish. 文档也已经扩展到包括我最初想要完成的一个很好的例子。

All is now explained well here . 现在所有人都在这里解释清楚。

If you want to see the code I came up with before the above release, check the revision history of this answer. 如果您想查看我在上述版本之前提出的代码,请查看此答案的修订历史记录。

I really do not like circular references. 我真的不喜欢循环引用。 There is usually a way to avoid them. 通常有一种方法可以避免它们。 Here is an approach: 这是一种方法:

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM