对于以下情况,我似乎无法弄清楚如何解决 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”列似乎是一个自然的选择,但这些列不能用作sqlite 中的外键,其中外键必须映射到声明的列。但是,有一种解决方法,“INTEGER PRIMARY KEY AUTOINCREMENT”是sqlite 中的一个特殊别名,它会导致命名列引用“rowid”。所以这就是我们将尝试的。
StackOverflow 上关于 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。