【问题标题】:`UNIQUE constraint failed` on composite primary key despite use of trigger尽管使用了触发器,但复合主键上的“唯一约束失败”
【发布时间】:2019-02-18 04:47:37
【问题描述】:

假设我们在尝试将新行插入到表中时收到UNIQUE constraint failed 错误。

该行由多个主键组成,这些主键被链接以创建一个复合键约束,即由idid_xid_yid_z

第一个 id 键必须是“自动递增”的 unique 键,而 id_xid_yid_zforeign keys

由于我们不能在 sqlite 中的 composite key 上使用 auto-increment 功能,因此我们使用以下触发器来查找每个触发器的最大整数字段并加 1 以满足 uniqueness 约束:

CREATE TRIGGER [autoincrement]
         AFTER INSERT
            ON table_main
          WHEN NEW.id IS NULL
BEGIN
    UPDATE table_main
        SET id = IFNULL((SELECT MAX(id) FROM table_main) + 1, 0),
        id_x = IFNULL((SELECT MAX(id_x) FROM table_main) + 1, 0),
        id_y = IFNULL((SELECT MAX(id_y) FROM table_main) + 1, 0),
        id_z = IFNULL((SELECT MAX(id_z) FROM table_main) + 1, 0);
END; 

尽管如此,我们仍然收到UNIQUE constraint failed 错误。

更新:id_z 有一个 forign key 约束。

【问题讨论】:

  • 请阅读minimal reproducible example并采取行动。这包括提供 DDL & 示例输入 & 所需输出 & 实际输出,包括错误消息。研究你的错误提示了什么? “链接的几个主键”是什么意思?如果您只想要列集的子行值的唯一性,那么您不应将各个列本身声明为 PK,因为这也表明每个列都是唯一的。您的意思是各个列都是 FK 吗?这就是 MCVE 的原因。另请阅读 NULL 在 UNIQUE 和 FK 中的作用。
  • 我觉得奇怪的是,在您在 table_main 中插入一行之前,您正在更新 table_main 中的所有行。因此,table_main 中可能只有一行,它是 product_inventory 键的下一个数字控制器,在这种情况下,您不想在插入 product_inventory 之前触发触发器吗?
  • 很抱歉造成混乱,但 product_inventory 实际上应该是 table_main

标签: python-3.x sqlite auto-increment composite-primary-key


【解决方案1】:

因为我们不能在复合键上使用自动增量功能

以下有一个复合键,其唯一 id 不断增加(您称之为自动增量):-

DROP TABLE IF EXISTS table_main;
DROP TABLE IF EXISTS table_fkx;
DROP TABLE IF EXISTS table_fky;
DROP TABLE IF EXISTS table_fkz;
DROP TRIGGER IF EXISTS [autoincrement];
CREATE TABLE IF NOT EXISTS table_fkx (id INTEGER PRIMARY KEY, datacol TEXT);
CREATE TABLE IF NOT EXISTS table_fky (id INTEGER PRIMARY KEY, datacol TEXT);
CREATE TABLE IF NOT EXISTS table_fkz (id INTEGER PRIMARY KEY, datacol TEXT);
CREATE TABLE IF NOT EXISTS table_main (
    id INTEGER PRIMARY KEY, 
  id_x INTEGER REFERENCES table_fkx(id), 
    id_y INTEGER REFERENCES table_fky(id), 
    id_z INTEGER REFERENCES table_fkz(id), 
    UNIQUE(id, id_x, id_y, id_z)
);
/*
CREATE TRIGGER [autoincrement]
         AFTER INSERT
            ON table_main
          WHEN NEW.id IS NULL
BEGIN
    UPDATE table_main
        SET id = IFNULL((SELECT MAX(id) FROM table_main) + 1, 0),
        id_x = IFNULL((SELECT MAX(id_x) FROM table_main) + 1, 0),
        id_y = IFNULL((SELECT MAX(id_y) FROM table_main) + 1, 0),
        id_z = IFNULL((SELECT MAX(id_z) FROM table_main) + 1, 0);
END;
*/

INSERT INTO table_fkx VALUES (10,'some data'),(33,'more data'),(56,'even more data');
INSERT INTO table_fky VALUES (73,'some data'),(1200,'more data'),(560,'even more data');
INSERT INTO table_fkz VALUES (15,'some data'),(1500,'more data'),(123456,'even more data');
INSERT INTO table_main (id_x,id_y,id_z) VALUES 
    (10,1200,15),(56,1200,15),(33,560,15),(10,73,15) -- etc
;
-- INSERT what could be a considered a duplicate but now is not as the autoincremnt(sic) makes it unique
INSERT INTO table_main (id_x,id_y,id_z) VALUES (33,560,15); -- i.e. same as 3rd
SELECT * FROM table_main 
    JOIN table_fkx ON id_x = table_fkx.id 
    JOIN table_fky ON id_y = table_fky.id
    JOIN table_fkz ON id_z = table_fkz.id
;

但是,如果组合键的一部分是 unqiue,则意味着您可以有效地插入引用相同外键的可能无用的行。

【讨论】:

  • 很抱歉造成混淆,但 product_inventory 实际上应该是 table_main
  • @AlexB 适用相同的修复程序(INSERT 或 REPLACE 以及 AFTER INSERT 而不是 BEFORE INSERT)。问题正在相应更新。
【解决方案2】:

也许不是尽管触发,而是因为触发。

这会将table_main 中的所有 行更新为相同的键(当NEW.id 为空时)。当table_main 中有 2 行(或更多)行时,这肯定会导致违反约束。

UPDATE table_main
        SET id = IFNULL((SELECT MAX(id) FROM table_main) + 1, 0),
        id_x = IFNULL((SELECT MAX(id_x) FROM table_main) + 1, 0),
        id_y = IFNULL((SELECT MAX(id_y) FROM table_main) + 1, 0),
        id_z = IFNULL((SELECT MAX(id_z) FROM table_main) + 1, 0);

如果NEW.id 不为空,那么问题出在其他地方。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-12-18
    • 2021-06-19
    • 2021-11-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多