Skip to content
Advertisement

Postgresql constraint violated at restore

I have a problem with a constraint that is being violated by some rows when restoring my DB, but has never been violated at usage.

I have 3 tables :

CREATE TABLE IF NOT EXISTS "as" (
  "id" bigserial NOT NULL,
  PRIMARY KEY ("id")
);

CREATE TABLE IF NOT EXISTS "bs" (
  "id" bigserial NOT NULL,
  "some_id" bigint,
  PRIMARY KEY ("id")
  FOREIGN KEY ("some_id") REFERENCES "some_table" ("id") 
);

CREATE TABLE IF NOT EXISTS "as_bs" (
    "a_id" bigint NOT NULL,
    "b_id" bigint NOT NULL,
    UNIQUE ("a_id", "b_id"),
    FOREIGN KEY ("a_id") REFERENCES "as" ("id"),
    FOREIGN KEY ("b_id") REFERENCES "bs" ("id")
);

some_table here is another table, which I think is not relevant for this problem.

Now what I want is to have a unicity constraint between as.id and bs.some_id through the relation table as_bs. E.g:

INSERT INTO some_table (id) VALUES(1),(2);
INSERT INTO "as" (id) VALUES(1),(2);
INSERT INTO bs (id,some_id) VALUES(1,1),(2,1);
INSERT INTO as_bs (a_id,b_id) VALUES(1,1);
INSERT INTO as_bs (a_id,b_id) VALUES(1,2); -- <<-- Offending row !!!

(Thanks @wildplasser for formatting)

This brings me to the following constraint:

CREATE OR REPLACE FUNCTION check_a_b_some_table_unicity(a_id bigint, b_id bigint)
RETURNS boolean AS
$body$
    BEGIN
        return (SELECT(COUNT(*) = 0)
        FROM as_bs ab
        JOIN bs o1 ON o1.id = ab.b_id
        JOIN bs o2 ON o2.some_id = o1.some_id
        WHERE ab.a_id = $1 AND o2.id = $2);
    END;
$body$
LANGUAGE plpgsql;

ALTER TABLE as_bs ADD CONSTRAINT check_a_b_some_table_unicity CHECK (check_a_b_some_table_unicity(a_id, b_id));

Then my program has lived its life, but when I want to restore a recent backup I got an error because of this constraint.

In my backup, I removed the constraint function and the check and I can restore my backup with no problem. Of course then if I’m trying to reapply the check, I got:

ERROR:  check constraint "check_a_b_some_table_unicity" is violated by some row

So I went to the idea of finding what rows are faulty.

To do that, I joined as and bs to as_bs and grouped by the unicity group (a_id, b_id, some_id):

SELECT a_id, b_id, bs.some_id, COUNT(*) occurrences FROM as_bs
JOIN bs ON as_bs.b_id = bs.id
JOIN as ON as_bs.a_id = as.id
GROUP BY a_id, b_id, bs.some_id HAVING COUNT(*) > 1;

and I got the surprise that it did not return any row…

Now I’m just wondering what’s the problem between

  • My constraint is wrong for what I want to do
  • My unicity check just above does not check what I want to check

and anyway, I’m also wondering how a constraint that could be registered once can’t be restored now because it was violated.

Advertisement

Answer

CREATE TABLE IF NOT EXISTS "as_bs" (
    "a_id" bigint NOT NULL,
    "b_id" bigint NOT NULL,
    UNIQUE ("a_id", "b_id"),
    FOREIGN KEY ("a_id") REFERENCES "as" ("id"),
    FOREIGN KEY ("b_id") REFERENCES "bs" ("id")
);

In your current schema, (as_bs.a_id, as_bs.b_id) is unique Adding dependant columns from as or bs won’t make it more unique.


UPDATE:


i tmp.sql

CREATE TABLE IF NOT EXISTS "as" (
  "id" bigserial NOT NULL,
  PRIMARY KEY ("id")
);

CREATE TABLE IF NOT EXISTS "some_table" (
  "id" bigserial NOT NULL,
  PRIMARY KEY ("id")
);

CREATE TABLE IF NOT EXISTS "bs" (
  "id" bigserial NOT NULL,
  "some_id" bigint,
  PRIMARY KEY ("id"),
  FOREIGN KEY ("some_id") REFERENCES "some_table" ("id")
);

CREATE TABLE IF NOT EXISTS "as_bs" (
    "a_id" bigint NOT NULL,
    "b_id" bigint NOT NULL,
    UNIQUE ("a_id", "b_id"),
    FOREIGN KEY ("a_id") REFERENCES "as" ("id"),
    FOREIGN KEY ("b_id") REFERENCES "bs" ("id")
);

INSERT INTO some_table (id) VALUES(1),(2);
INSERT INTO "as" (id) VALUES(1),(2);
INSERT INTO bs (id,some_id) VALUES(1,1),(2,1);
INSERT INTO as_bs (a_id,b_id) VALUES(1,1);

CREATE OR REPLACE FUNCTION check_a_b_some_table_unicity(a_id bigint, b_id bigint)
RETURNS boolean AS
$body$
    BEGIN
      return NOT EXISTS ( -- Prefer NOT EXISTS to COUNT(*) < 1
        SELECT *
        FROM as_bs ab
        JOIN bs o1 ON o1.id = ab.b_id
        JOIN bs o2 ON o2.some_id = o1.some_id AND o2.id <> o1.id
        WHERE ab.a_id = $1 AND o2.id = $2
        );
    END;
$body$
LANGUAGE plpgsql;

ALTER TABLE as_bs ADD CONSTRAINT check_a_b_some_table_unicity CHECK (check_a_b_some_table_unicity(a_id, b_id));

INSERT INTO as_bs (a_id,b_id) VALUES(1,2); -- <<-- Offending row !!!
                                                                                                                                                                             

Output:


DROP SCHEMA
CREATE SCHEMA
SET
CREATE TABLE
CREATE TABLE
CREATE TABLE
CREATE TABLE
INSERT 0 2
INSERT 0 2
INSERT 0 2
INSERT 0 1
CREATE FUNCTION
ALTER TABLE
ERROR:  new row for relation "as_bs" violates check constraint "check_a_b_some_table_unicity"
DETAIL:  Failing row contains (1, 2).
User contributions licensed under: CC BY-SA
2 People found this is helpful
Advertisement