I have the following scenario in a Postgres 9.3 database:
Tables B and C reference Table A.
Table C has an optional field that references table B.
I would like to ensure that for each row of table C that references table B, c.b.a = c.a. That is, if C has a reference to B, both rows should point at the same row in table A.
I could refactor table C so that if c.b is specified, c.a is null but that would make queries joining tables A and C awkward.
I might also be able to make table B's primary key include its reference to table A and then make table C's foreign key to table B include table C's reference to table A but I think this adjustment would be too awkward to justify the benefit.
I think this can be done with a trigger that runs before insert/update on table C and rejects operations that violate the specified constraint.
Is there a better way to enforce data integrity in this situation?
There is a very simple, bullet-proof solution. Works for Postgres 9.3 - when the original question was asked. Works for the current Postgres 13 - when the question in the bounty was added:
Would like information on if this is possible to achieve without database triggers
FOREIGN KEY constraints can span multiple columns. Just include the ID of table A in the FK constraint from table C to table B. This enforces that linked rows in B and C always point to the same row in A. Like:
CREATE TABLE a (
a_id int PRIMARY KEY
);
CREATE TABLE b (
b_id int PRIMARY KEY
, a_id int NOT NULL REFERENCES a
, UNIQUE (a_id, b_id) -- redundant, but required for FK
);
CREATE TABLE c (
c_id int PRIMARY KEY
, a_id int NOT NULL REFERENCES a
, b_id int
, CONSTRAINT fk_simple_and_safe_solution
FOREIGN KEY (a_id, b_id) REFERENCES b(a_id, b_id) -- THIS !
);
Minimal sample data:
INSERT INTO a(a_id) VALUES
(1)
, (2);
INSERT INTO b(b_id, a_id) VALUES
(1, 1)
, (2, 2);
INSERT INTO c(c_id, a_id, b_id) VALUES
(1, 1, NULL) -- allowed
, (2, 2, 2); -- allowed
Disallowed as requested:
INSERT INTO c(c_id, a_id, b_id) VALUES (3,2,1);
ERROR: insert or update on table "c" violates foreign key constraint "fk_simple_and_safe_solution"
DETAIL: Key (a_id, b_id)=(2, 1) is not present in table "b".
db<>fiddle here
The default MATCH SIMPLE behavior of FK constraints works like this (quoting the manual):
MATCH SIMPLE allows any of the foreign key columns to be null; if any of them are null, the row is not required to have a match in the referenced table.
So NULL values in c(b_id) are still allowed (as requested: "optional field"). The FK constraint is "disabled" for this special case.
We need the logically redundant UNIQUE constraint on b(a_id, b_id) to allow the FK reference to it. But by making it out to be on (a_id, b_id) instead of (b_id, a_id), it is also useful in its own right, providing a useful index on b(a_id) to support the other FK constraint, among other things. See:
Is a composite index also good for queries on the first field?
(An additional index on c(a_id) is typically useful accordingly.)
Further reading:
Differences between MATCH FULL, MATCH SIMPLE, and MATCH PARTIAL?
Enforcing constraints “two tables away”
I ended up creating a trigger as follows:
create function "check C.A = C.B.A"()
returns trigger
as $$
begin
if NEW.b is not null then
if NEW.a != (select a from B where id = NEW.b) then
raise exception 'a != b.a';
end if;
end if;
return NEW;
end;
$$
language plpgsql;
create trigger "ensure C.A = C.B.A"
before insert or update on C
for each row
execute procedure "check C.A = C.B.A"();
Would like information on if this is possible to achieve without database triggers
Yes, it is possible. The mechanism is called ASSERTION and it is defined in SQL-92 Standard(though it is not implemented by any major RDBMS).
In short it allows to create multiple-row constraints or multi-table check constraints.
As for PostgreSQL it could be emulated by using view with WITH CHECK OPTION and performing operation on view instead of base table.
WITH CHECK OPTION
This option controls the behavior of automatically updatable views. When this option is specified, INSERT and UPDATE commands on the view will be checked to ensure that new rows satisfy the view-defining condition (that is, the new rows are checked to ensure that they are visible through the view). If they are not, the update will be rejected.
Example:
CREATE TABLE a(id INT PRIMARY KEY, cola VARCHAR(10));
CREATE TABLE b(id INT PRIMARY KEY, colb VARCHAR(10), a_id INT REFERENCES a(id) NOT NULL);
CREATE TABLE c(id INT PRIMARY KEY, colc VARCHAR(10),
a_id INT REFERENCES a(id) NOT NULL,
b_id INT REFERENCES b(id));
Sample inserts:
INSERT INTO a(id, cola) VALUES (1, 'A');
INSERT INTO a(id, cola) VALUES (2, 'A2');
INSERT INTO b(id, colb, a_id) VALUES (12, 'B', 1);
INSERT INTO c(id, colc, a_id) VALUES (15, 'C', 2);
Violating the condition(connecting C with B different a_id on both tables)
UPDATE c SET b_id = 12 WHERE id = 15;;
-- no issues whatsover
Creating view:
CREATE VIEW view_c
AS
SELECT *
FROM c
WHERE NOT EXISTS(SELECT 1
FROM b
WHERE c.b_id = b.id
AND c.a_id != b.a_id) -- here is the clue, we want a_id to be the same
WITH CHECK OPTION ;
Trying update second time(error):
UPDATE view_c SET b_id = 12 WHERE id = 15;
--ERROR: new row violates check option for view "view_c"
--DETAIL: Failing row contains (15, C, 2, 12).
Trying brand new inserts with incorrect data(also errors)
INSERT INTO b(id, colb, a_id) VALUES (20, 'B2', 2);
INSERT INTO view_c(id, colc, a_id, b_id) VALUES (30, 'C2', 1, 20);
--ERROR: new row violates check option for view "view_c"
--DETAIL: Failing row contains (30, C2, 1, 20)
db<>fiddle demo
Related
Table Structure:
create table example (a_id integer, b_id integer, c_id integer, flag integer);
Partial Index:
create unique index u_idx on example (a_id, b_id, c_id) where flag = 0;
My Code for upsert:
with a_ins_upd as (
Insert into example (a_id, b_id, c_id, flag)
select x.a_id, x.b_id, x.c_id, x.flag
from <input_tableType> x
on conflict (a_id, b_id, c_id) where flag = 0
do update
set
a_id = excluded.a_id,
b_id = excluded.b_id,
c_id = excluded.c_id,
flag = excluded.flag
returning *
)
insert into <some_other_table>
Result Entries, as of now :
Entry_1 is the data already in the table. Entry_2 , is the new input. The new input should update the Entry_1, rather than Inserting a new entry.
Where I am going wrong. I want the second entry to get updated, not Inserted.
I tried to include the flag as a part of the index. Then also, records are getting inserted. Does partial index not work with on conflict() clause?
How do I achieve this - I want to ignore the records with flag = 1, existing in the table (like blind to flag = 1), and do the merge operations on other records (the records which are having flag = 0, unique constraint remains on (a_id, b_id, c_id)). I thought of doing partial constraint/index and do on conflict. Seems tricky now.
I have some numbers of tables with foreign key between them. Structure of table is not important. For example, table A is a top level table. Table B and C have foreign key on table A and tables D, E and F and G have foreign key on table B and C resp.
The question is: is there a way for clone one of row from table A
with all depeddencies (row in tables B - G) using SQL?
You didn't provide structure nor data, so you will have to work with my example. The basic idea is just selecting the appropriate FK columns to the respective PK columns as you progress from parent to child.
-- create base tables
create table a (a_id integer, a_col1 text, constraint a_pk primary key (a_id));
create table b (b_id integer, a_id integer, b_col1 text
, constraint b_pk primary key (b_id)
, constraint b_fk_a foreign key (a_id)
references a(a_id)
);
create table g (g_id integer, b_id integer, g_col1 text
, constraint g_pk primary key (g_id)
, constraint g_fk_b foreign key (b_id)
references b(b_id)
);
-------------------------------------------------------------------------------------------------------
-- populate
insert into a (a_id,a_col1) values (11,'ta1'), (12,'ta2');
insert into b (b_id,a_id,b_col1) values (21,11,'tb1'), (22,11,'tb2'), (23,12,'tb3'), (24,12,'tb4');
insert into g (g_id,b_id,g_col1) values (71,21,'tg1'), (72,21,'tg2'), (73,22,'tg3'), (74,22,'tg4')
, (75,23,'tg5'), (76,23,'tg6'), (77,24,'tg7'), (78,24,'tg8');
-------------------------------------------------------------------------------------------------------
-- Soution: clone a_id 12
create table a_clone as
select *
from a
where a_id = 12;
create table b_clone as
select *
from b
where a_id = 12;
create table g_clone as
select *
from g
where b_id in
(select b_id
from b
where a_id = 12
);
-- done
The 'comes to mind' suggestion from Sticky Bit is doable, but it's not straight forward. I believe the above it straight forward.
This is SQLite version 3.16.0 2016-11-04
Here's my test schema:
PRAGMA foreign_keys = ON;
CREATE TABLE A (a_id integer primary key, x int);
CREATE TABLE B (b_id integer primary key, a_id integer not null references A(a_id) ON DELETE RESTRICT ON UPDATE CASCADE, descr text);
CREATE TABLE C (id integer primary key, dt DATETIME DEFAULT CURRENT_TIMESTAMP);
CREATE TRIGGER Tb after update on B begin
replace into C (id) select NEW.b_id;
end;
Here's some test data (as per .dump):
INSERT INTO "A" VALUES(1, 5000);
INSERT INTO "B" VALUES(1, 1, 'none');
Now, if I manually update a row in table B, like so:
UPDATE B SET descr = "test1" WHERE b_id = 1;
I can see a new row (which gets updated every time I touch B) in table C:
1|2017-04-17 21:59:42
I can also manually "touch" C the same way it's done in the trigger:
replace into C (id) select 1;
As expected, the dt column gets updated. All good so far.
But suddenly, if I change a_id, referenced from B:
UPDATE A SET a_id = 2 WHERE a_id = 1;
I get Error: UNIQUE constraint failed: C.id
In my understanding, the foreign key from B.a_id to A.a_id updates B's row. That leads to the trigger trying to touch C. Which then for some reason fails, though it just worked perfectly fine in previous scenarios.
Why does that happen and how can I fix it?
You are using the REPLACE command, which
is an alias for the "INSERT OR REPLACE" variant of the INSERT command.
The trigger documentation says:
An ON CONFLICT clause may be specified as part of an UPDATE or INSERT action within the body of the trigger. However if an ON CONFLICT clause is specified as part of the statement causing the trigger to fire, then conflict handling policy of the outer statement is used instead.
So when the outer command is an UPDATE, your REPLACE also becomes an UPDATE.
As a workaround, replace the REPLACE with the appropriate DELETE and INSERT commands.
I want to create e temp table using select into syntax. Like:
select top 0 * into #AffectedRecord from MyTable
Mytable has a primary key. When I insert record using merge into syntax primary key be a problem. How could I drop pk constraint from temp table
The "SELECT TOP (0) INTO.." trick is clever but my recommendation is to script out the table yourself for reasons just like this. SELECT INTO when you're actually bringing in data, on the other hand, is often faster than creating the table and doing the insert. Especially on 2014+ systems.
The existence of a primary key has nothing to do with your problem. Key Constraints and indexes don't get created when using SELECT INTO from another table, the data type and NULLability does. Consider the following code and note my comments:
USE tempdb -- a good place for testing on non-prod servers.
GO
IF OBJECT_ID('dbo.t1') IS NOT NULL DROP TABLE dbo.t1;
IF OBJECT_ID('dbo.t2') IS NOT NULL DROP TABLE dbo.t2;
GO
CREATE TABLE dbo.t1
(
id int identity primary key clustered,
col1 varchar(10) NOT NULL,
col2 int NULL
);
GO
INSERT dbo.t1(col1) VALUES ('a'),('b');
SELECT TOP (0)
id, -- this create the column including the identity but NOT the primary key
CAST(id AS int) AS id2, -- this will create the column but it will be nullable. No identity
ISNULL(CAST(id AS int),0) AS id3, -- this this create the column and make it nullable. No identity.
col1,
col2
INTO dbo.t2
FROM t1;
Here's the (cleaned up for brevity) DDL for the new table I created:
-- New table
CREATE TABLE dbo.t2
(
id int IDENTITY(1,1) NOT NULL,
id2 int NULL,
id3 int NOT NULL,
col1 varchar(10) NOT NULL,
col2 int NULL
);
Notice that the primary key is gone. When I brought in id as-is it kept the identity. Casting the id column as an int (even though it already is an int) is how I got rid of the identity insert. Adding an ISNULL is how to make a column nullable.
By default, identity insert is set to off here to this query will fail:
INSERT dbo.t2 (id, id3, col1) VALUES (1, 1, 'x');
Msg 544, Level 16, State 1, Line 39
Cannot insert explicit value for identity column in table 't2' when IDENTITY_INSERT is set to OFF.
Setting identity insert on will fix the problem:
SET IDENTITY_INSERT dbo.t2 ON;
INSERT dbo.t2 (id, id3, col1) VALUES (1, 1, 'x');
But now you MUST provide a value for that column. Note the error here:
INSERT dbo.t2 (id3, col1) VALUES (1, 'x');
Msg 545, Level 16, State 1, Line 51
Explicit value must be specified for identity column in table 't2' either when IDENTITY_INSERT is set to ON
Hopefully this helps.
On a side-note: this is a good way to play around with and understand how select insert works. I used a perm table because it's easier to find.
I am trying to build my first Postgres database schema involving inheritance. I am aware of the foreign key problem as discussed in PostgreSQL foreign key not existing, issue of inheritance?. However, the answers on this question did not really give an example for the solution so I came up with my own (inspired by http://people.planetpostgresql.org/dfetter/index.php?/archives/51-Partitioning-Is-Such-Sweet-Sorrow.html):
CREATE TABLE a (
id SERIAL PRIMARY KEY INITIALLY DEFERRED,
foo TEXT
);
CREATE TABLE b (
PRIMARY KEY(id),
a_number REAL
) inherits(a);
CREATE TABLE c (
PRIMARY KEY(id),
a_number INTEGER
) inherits(a);
-- SELECT * FROM ONLY a; will always return an empty record.
CREATE TABLE x (
a_id INTEGER NOT NULL,
bar TEXT
);
CREATE TABLE xb (
FOREIGN KEY( a_id ) REFERENCES b INITIALLY DEFERRED
) inherits(x);
CREATE TABLE xc (
FOREIGN KEY( a_id ) REFERENCES c INITIALLY DEFERRED
) inherits(x);
In order to perform a working INSERT INTO x I defined the following trigger:
CREATE FUNCTION marshal_x()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
DECLARE
ref_table varchar;
BEGIN
SELECT INTO ref_table p.relname FROM a, pg_class p WHERE a.id = NEW.a_id AND a.tableoid = p.oid;
IF ref_table = 'b'
THEN INSERT INTO xb ( a_id, bar ) VALUES ( NEW.a_id, NEW.bar );
ELSIF ref_table = 'c'
THEN INSERT INTO xc ( a_id, bar ) VALUES ( NEW.a_id, NEW.bar );
END IF;
RETURN NULL;
END;
$$;
CREATE TRIGGER insert_x_trg
BEFORE INSERT ON x
FOR EACH ROW
EXECUTE PROCEDURE marshal_x();
EDIT: Does anyone see a problem with this definition, which are not obvious to my untrained eye?
Assume table c to be empty. Would the performance of
SELECT * FROM a JOIN x ON a.id= x.a_id;
be the same as
SELECT * FROM b JOIN x ON b.id= x.a_id;
? Many thanks in advance.
Isam
What's the purpose of using inheritance anyway? If you just want different columns depending on row type, like in OOP, you're better off using a single table and putting NULL in the columns that aren't applicable. Or split off additional columns to another table, which you join to the original.
The primary use of inheritance in PostgreSQL is table partitioning and there are a number of caveats with inheritance. PostgreSQL 9.1 introduces a "Merge Append" optimization that helps somewhat, but it's still suboptimal.
SELECT * FROM a JOIN x ON a.id= x.a_id;
Joining two large-ish inherited tables together sounds like it won't scale very well, particularly as you increase the number of child tables. PostgreSQL is not smart enough to follow the xb and xc foreign key constraints here, it would try to join the whole table a to the whole of x.
But then again, it could turn out not to be a problem, it all depends on your queries and performance expectations.