Question

I'm creating an IDs table in PostgreSQL to represent the relationship between multiple foreign IDs - call them a, b and c.

I happen to know that the foreign IDs have one-to-many relationships between them:

a 1..* b b 1..* c

I'd like to represent them as a single table (see below), but enforce the one-to-many invariant between a & b, & between b and c.

Is that possible?

Examples: The following insertions would be illegal:

pk a b c reason
p15 a1 b11 c111 c111 already exists
p16 a1 b21 c213 b21 already exists for a2
p17 a2 b11 c214 b11 already exists for a1

Given the following legal table:

pk a b c
p1 a1 b11 c111
p2 a1 b11 c112
p3 a1 b12 c121
p4 a1 b12 c122
p5 a2 b21 c211
p6 a2 b21 c212
p7 a2 b22 c221
p8 a2 b22 c222
p9 null b31 c311
p10 a3 null c312
p11 a3 b31 null
p12 null null c314
p13 null b31 null
p14 a3 null null
Was it helpful?

Solution

This has to be done by TRIGGER, because it is too complex for CHEKCconstraint

For updating you need a similar trigger/function

CREATE TABLE tabl1
    ("pk" varchar(5), "a" varchar(6), "b" varchar(6), "c" varchar(6))
;
    
INSERT INTO tabl1
    ("pk", "a", "b", "c")
VALUES
    ('p1', 'a1', 'b11', 'c111'),
    ('p2', 'a1', 'b11', 'c112'),
    ('p3', 'a1', 'b12', 'c121'),
    ('p4', 'a1', 'b12', 'c122'),
    ('p5', 'a2', 'b21', 'c211'),
    ('p6', 'a2', 'b21', 'c212'),
    ('p7', 'a2', 'b22', 'c221'),
    ('p8', 'a2', 'b22', 'c222'),
    ('p9', NULL, 'b31', 'c311'),
    ('p10', 'a3', NULL, 'c312'),
    ('p11', 'a3', 'b31', NULL),
    ('p12', NULL, NULL, 'c314'),
    ('p13', NULL, 'b31', NULL),
    ('p14', 'a3', NULL, NULL)
;
CREATE OR REPLACE FUNCTION check_insert()
RETURNS TRIGGER AS $$
   BEGIN
   IF EXISTS(SELECT 1 FROM tabl1 WHERE "c" = NEW."c" AND "b" = NEW."b" ) then
       raise EXCEPTION 'The column %  already  with %',NEW."c",NEW."b";
   ELSIF EXISTS(SELECT 1 FROM tabl1 WHERE "a" <> NEW."a" AND "b" = NEW."b" ) then
           raise EXCEPTION 'The column %  already  has already a column',NEW."b";
   END IF;
   RETURN NEW;
   END;
$$ LANGUAGE plpgsql;
CREATE  TRIGGER trigger_name BEFORE
 INSERT
ON tabl1
FOR EACH ROW
EXECUTE PROCEDURE check_insert();
INSERT INTO tabl1
    ("pk", "a", "b", "c")
VALUES
    ('p15', 'a1', 'b11', 'c111')
ERROR:  The column c111  already  with b11

CONTEXT: PL/pgSQL function check_insert() line 4 at RAISE

INSERT INTO tabl1
    ("pk", "a", "b", "c")
VALUES
('p16' , 'a1'   , 'b21'  , 'c213' )
ERROR:  The column b21  already  has already a column

CONTEXT: PL/pgSQL function check_insert() line 6 at RAISE

INSERT INTO tabl1
    ("pk", "a", "b", "c")
VALUES
('p17',   'a2',   'b11'   ,'c214')
ERROR:  The column b11  already  has already a column

CONTEXT: PL/pgSQL function check_insert() line 6 at RAISE

SELECT * FROM tabl1
pk  | a    | b    | c   
:-- | :--- | :--- | :---
p1  | a1   | b11  | c111
p2  | a1   | b11  | c112
p3  | a1   | b12  | c121
p4  | a1   | b12  | c122
p5  | a2   | b21  | c211
p6  | a2   | b21  | c212
p7  | a2   | b22  | c221
p8  | a2   | b22  | c222
p9  | null | b31  | c311
p10 | a3   | null | c312
p11 | a3   | b31  | null
p12 | null | null | c314
p13 | null | b31  | null
p14 | a3   | null | null

db<>fiddle here

Licensed under: CC-BY-SA with attribution
Not affiliated with dba.stackexchange
scroll top