Search code examples
python-3.xsqliteauto-incrementcomposite-primary-key

`UNIQUE constraint failed` on composite primary key despite use of trigger


Consider that we are getting a UNIQUE constraint failed error when trying to insert a new row into a table.

The row is made up of several primary keys which are linked to create a composite key constraint, namely made up of a combination of 4 fields id, id_x, id_y and id_z.

The first id key needs to be a unique key which is "auto-incremented" while id_x, id_y and id_z are foreign keys.

Since we cannot use the auto-increment feature on a composite key in sqlite we are resorting to the use of the following trigger where we lookup the highest integer for each field and add 1 in order to satisfy the uniqueness constraint:

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; 

Despite this we are still getting UNIQUE constraint failed error.

UPDATE: id_z has a forign key constraint.


Solution

  • Since we cannot use the auto-increment feature on a composite key

    The following has a composite key with an increasing unique id (what you have termed as auto-increment):-

    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
    ;
    

    However, having part of the composite key unqiue, then means that you can effectively insert potentially useless rows that reference the same foreign keys.