在这种复杂情况下,如何解决 Django 缺少组合键的问题?
Posted
技术标签:
【中文标题】在这种复杂情况下,如何解决 Django 缺少组合键的问题?【英文标题】:How to work around Django's lack of composite keys in this complicated case? 【发布时间】:2021-02-12 18:09:10 【问题描述】:对于以下情况,我似乎无法弄清楚如何解决 Django 中缺少复合键的问题。我将使用 SQLite3 方言编写我想要的架构。
(对于了解 SQL 但不了解 sqlite 的人来说,下面唯一可能不熟悉的是 sqlite 的“WITHOUT ROWID”子句。默认情况下,sqlite 在表上添加一个隐藏的整数自动递增“rowid”列。使用“WITHOUT ROWID”命令将其关闭。)
PRAGMA foreign_keys = ON;
CREATE TABLE A (
id_a INT PRIMARY KEY,
name_a TEXT
) WITHOUT ROWID;
CREATE TABLE B (
id_b INT PRIMARY KEY,
name_b TEXT
) WITHOUT ROWID;
CREATE TABLE C (
id_c INT PRIMARY KEY,
name_c TEXT
) WITHOUT ROWID;
CREATE TABLE AB (
id_a INT,
id_b INT,
PRIMARY KEY (id_a, id_b),
FOREIGN KEY (id_a) REFERENCES A(id_a),
FOREIGN KEY (id_b) REFERENCES B(id_b)
) WITHOUT ROWID;
CREATE TABLE BC (
id_b INT,
id_c INT,
PRIMARY KEY (id_b, id_c),
FOREIGN KEY (id_b) REFERENCES B(id_b),
FOREIGN KEY (id_c) REFERENCES C(id_c)
) WITHOUT ROWID;
CREATE TABLE ABC (
id_a INT,
id_b INT,
id_c INT,
blah TEXT,
PRIMARY KEY (id_a, id_b, id_c),
FOREIGN KEY (id_a, id_b) REFERENCES AB(id_a, id_b),
FOREIGN KEY (id_b, id_c) REFERENCES BC(id_b, id_c)
) WITHOUT ROWID;
表 AB 和 BC 具有复合键约束,可以使用代理键轻松解决,但表 ABC 具有复杂的键约束,不能直接在 Django 中实现。
这是一些测试数据
INSERT INTO A VALUES (1, "a1"), (2, "a2"), (3, "a3");
INSERT INTO B VALUES (1, "b1"), (2, "b2"), (3, "b3");
INSERT INTO C VALUES (1, "c1"), (2, "c2"), (3, "c3");
INSERT INTO AB VALUES (1,1), (1,2), (2,1), (2, 3);
INSERT INTO BC VALUES (1,3), (2,1), (3,1);
-- This should work because (1,1) is in AB and (1,3) is in BC.
INSERT INTO ABC VALUES (1,1,3,'should pass');
-- This should fail because although (1,2) is in AB, (2,3) is not in BC.
-- note that this should fail despite 1,2,3 are unique together
INSERT INTO ABC VALUES (1,2,3,'should fail');
尝试让它工作 Django 的明显第一步是使用代理键。隐藏的“rowid”列似乎是一个自然的选择,但这些列cannot be used 作为 sqlite 中的外键,其中外键必须映射到声明的列。但是,有一个解决方法,“INTEGER PRIMARY KEY AUTOINCREMENT”是一个special alias in sqlite,这将导致命名列引用“rowid”。所以这就是我们将尝试的。
*** 上关于 Django 和复合键的类似问题提到使用 NOT NULL 和 UNIQUE 约束,因此我们也可以这样做:
PRAGMA foreign_keys = ON;
CREATE TABLE A (
id_a INTEGER PRIMARY KEY AUTOINCREMENT,
name_a TEXT
);
CREATE TABLE B (
id_b INTEGER PRIMARY KEY AUTOINCREMENT,
name_b TEXT
);
CREATE TABLE C (
id_c INTEGER PRIMARY KEY AUTOINCREMENT,
name_c TEXT
);
CREATE TABLE AB (
id_ab INTEGER PRIMARY KEY AUTOINCREMENT,
id_a INT NOT NULL,
id_b INT NOT NULL,
UNIQUE (id_a, id_b)
FOREIGN KEY (id_a) REFERENCES A(id_a),
FOREIGN KEY (id_b) REFERENCES B(id_b)
);
CREATE TABLE BC (
id_bc INTEGER PRIMARY KEY AUTOINCREMENT,
id_b INT NOT NULL,
id_c INT NOT NULL,
UNIQUE (id_b,id_c)
FOREIGN KEY (id_b) REFERENCES B(id_b),
FOREIGN KEY (id_c) REFERENCES C(id_c)
);
CREATE TABLE ABC (
id_abc INTEGER PRIMARY KEY AUTOINCREMENT,
id_a INT NOT NULL,
id_b INT NOT NULL,
id_c INT NOT NULL,
blah TEXT,
UNIQUE (id_a, id_b, id_c)
FOREIGN KEY (id_a) REFERENCES A(id_a),
FOREIGN KEY (id_b) REFERENCES B(id_b),
FOREIGN KEY (id_c) REFERENCES C(id_c)
-- this table is under-constrained compared to the compound foreign key version previously given
);
如表所示,ABC 是欠约束的。下面是相同的测试数据来证明这一点(在插入时使用 NULL 来表示自增列):
INSERT INTO A VALUES (NULL, "a1"), (NULL, "a2"), (NULL, "a3");
INSERT INTO B VALUES (NULL, "b1"), (NULL, "b2"), (NULL, "b3");
INSERT INTO C VALUES (NULL, "c1"), (NULL, "c2"), (NULL, "c3");
INSERT INTO AB VALUES (NULL, 1,1), (NULL, 1,2), (NULL, 2,1), (NULL, 2, 3);
INSERT INTO BC VALUES (NULL, 1,3), (NULL, 2,1), (NULL, 3,1);
INSERT INTO ABC VALUES (NULL,1,1,3,'should pass');
INSERT INTO ABC VALUES (NULL,1,2,3,'should fail'); -- but does not
在插入触发器之前使用的唯一选项是测试否则会漏掉的值吗?使用 Django 约束,我发现无法在约束检查中引用表 AB 和 BC。
【问题讨论】:
【参考方案1】:不过,我不确定 SQL 部分。 我建议您不要使用 A、B 和 C 模型的外键,而是使用表 AB 和 BC 的外键。
所以你的模型类似于
Class ABC(models.Model):
....
ab = ForeignKey(AB, "insert_other_constraint_here")
bc = ForeignKey(BC, "insert_other_constraint_here")
但这里的问题是,每次你想创建 ABC 的对象时,你必须先得到 AB 和 BC:
ab = AB.objects.get(a=a,b=b)
bc = BC.objects.get(b=b,b=c)
ABC.objects.create(...,ab=ab,bc=bc)
这样,如果 AB 没有 (a,b) 的组合或 BC 没有 (b,c) 的组合,则会引发错误。
编辑:
但是,这样做会使INSERT INTO ABC VALUES (1,2,3,'should fail');
不可行,因为您需要 AB 和 BC 而不是 A、B、C 值。如果您仍想使用 A、B、C 的值来创建 ABC:
我猜另一种方法是覆盖 save() 方法。
def save(self, *args, **kwargs):
ab = AB.objects.filter(a=self.a,b=self.b)
bc = BC.objects.filter(b=self.b,b=self.c)
if ab is None or bc is None:
"Raise error here"
super(ABC, self).save(*args, **kwargs)
所以它在创建之前首先检查 AB 和 BC 对象是否存在。
【讨论】:
谢谢,阿尔维安多。我担心你可能是对的,在唯一的手段中的压倒一切的保存。我使用的所有模式都有这样的复杂关系。我不知道没有复合键的 Django 人是如何生活的。我认为这会将我推向 Flash,以便我可以使用 SQLAlchemy。谢谢。周末愉快。 我认为您需要if not ab or not bc
进行逻辑测试。显然使用exists()
方法可以加快测试速度。以上是关于在这种复杂情况下,如何解决 Django 缺少组合键的问题?的主要内容,如果未能解决你的问题,请参考以下文章
jQuery和Django:在这种情况下如何正确使用jquery
Django model 遇到查询条件组合比较多的情况下怎么写