SQLAlchemy中复杂的外键约束

时间:2011-12-06 01:20:09

标签: python sql postgresql database-design sqlalchemy

我有两个表,SystemVariablesVariableOptionsSystemVariables应该是不言自明的,VariableOptions包含所有变量的所有可能选择。

VariableOptions有一个外键variable_id,它指出了哪个变量是一个选项。 SystemVariables有一个外键choice_id,表示哪个选项是当前选中的。

我使用use_alter上的choice_idpost_update'SystemVariables choice关联choice_id来解决循环关系问题。但是,我想添加一个额外的数据库约束来确保sysVar有效(即它指的是一个引用它的选项)。

假设SystemVariables代表VariableOptions[sysVar.choice_id].variable_id == sysVar.id 表中的一行,我需要的逻辑基本上是:

{{1}}

但我不知道如何使用SQL,声明式或任何其他方法构造这种约束。如果有必要,我可以在应用程序级别验证这一点,但如果可能的话,我想在数据库级别进行验证。我正在使用Postgres 9.1。

这可能吗?

3 个答案:

答案 0 :(得分:11)

您可以实施不带诡计。除了variable_id之外,只需扩展外键引用所选选项以包含choice_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À即可。正是你想要的。


所有键列NOT NULL

我认为我在后面的回答中找到了更好的解决方案:

寻址@ypercube's question in the comments,以避免关联未知的条目生成所有关键列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修饰符,但仍被定义为可延迟。这带来了一些限制。 The manual

  

NO ACTION支票以外的参考操作无法延期,   即使约束被宣布为可延迟的。

NO ACTION是默认设置。

因此,INSERT上的参照完整性检查被推迟,但DELETEUPDATE上声明的级联操作不会。 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 中有效,而文档声称同样的行为。看起来像旧版本中的一个错误 - 即使它看起来有益而不是乍一看有害。必须为新版本修复。

答案 1 :(得分:4)

编辑: SQLAlchemy的0.7.4版本(在我开始询问此问题的同一天发布,7/12 / '11!),包含一个新的autoincrement值主键也是外键的一部分,ignore_fk。文档也已经扩展到包括我最初想要完成的一个很好的例子。

现在很好地解释了所有here

如果您想查看我在上述版本之前提出的代码,请查看此答案的修订历史记录。

答案 2 :(得分:2)

我真的不喜欢循环引用。通常有一种方法可以避免它们。这是一种方法:

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)