Locking during Insert into Partitioned table postgres - postgresql

Is constraint exclusion possible during inserts?
create table parent(id int)
create table c1 ( constraint c1_constarint CHECK (id = 1)) inherits parent
create table c2 ( constraint c2_constarint CHECK (id = 2)) inherits parent
create table c3 ( constraint c3_constarint CHECK (id = 3)) inherits parent
when doing
insert into c1 values(1) it is acquiring RowExclusive locks on all children(c2, c3)
Is there a way can we do constraint exclusion during insert? Or any hints available to perform this action

Related

Updating a PostgreSQL table with a unique constraint

I have a table with a unique constraint
CREATE temp TABLE tmp (
c1 int,
c2 text,
c3 int,
UNIQUE (c2, c3)
);
insert into tmp (c1, c2, c3)
values (1, 'a', 2),
(2, 'a', 1)
Is it possible to run the update below without getting a unique contraint violation and without modifying table tmp?
update tmp
set c3 = c1
where 1=1
ERROR: duplicate key value violates unique constraint "tmp_c2_c3_key". Detail: Key (c2, c3)=(a, 1) already exists.
You can do 2 updates, one that will change your c3 values with other value than then previous ones like
UPDATE tmp
SET c3 = -c1
WHERE 1 = 1;
and then update your c3 value with the good one
UPDATE tmp
SET c3 = c1
WHERE 1 = 1;
It will work if all your values in c1 and c3 are positive
Is it possible to run the update below without getting a unique contraint violation and without modifying table tmp?
No, this is not possible. You would have to make the UNIQUE constraint DEFERRABLE:
...
UNIQUE (c2, c3) DEFERRABLE
...
Then you could set it to deferred like this
SET CONSTRAINTS tmp_c2_c3_key DEFERRED;
and run your update command without a violation error. Note that you must use a transaction for SET CONSTRAINTS to work.
Drop the unique constraint, do your update, reinstate the constraint.

postgresql, deferred constraint not working as I expect

I'm struggling to understand the way DEFERRED constraints work in postgres (v13 if that matters).
I have the following schema and data:
CREATE TEMP TABLE t1 (
id int,
CONSTRAINT unique_id PRIMARY KEY (id)
);
CREATE TEMP TABLE t2 (
id int,
ref int,
CONSTRAINT fk FOREIGN KEY (ref) REFERENCES t1 (id) ON DELETE SET NULL DEFERRABLE INITIALLY IMMEDIATE
);
INSERT INTO t1 VALUES (1);
INSERT INTO t2 (id,ref) VALUES (1,1);
Then I execute the instructions below:
BEGIN;
SET CONSTRAINTS ALL DEFERRED;
DELETE FROM t1;
INSERT INTO t1 VALUES (1);
COMMIT;
Because constraints are deferred, I would expect the ON DELETE SET NULL to trigger at the end of the transaction and preserve the link from t1 to t2. Yet it does not.
SELECT * FROM t2;
id | ref
----+-----
1 |
(1 row)
What am I missing here ?
DEFERRABLE only means that the check if no row violates the constraint is postponed until the transaction is committed. It does not mean that the effect of the DML that you run, is postponed until the end of the transaction.
In your example at the end of the transaction no row in t2 violates the foreign key constraint. So the COMMIT is successful.
Additionally: the DELETE FROM t1 will set all ref columns to NULL in the table t2 that contained the deleted IDs. If you then insert a new row into t1, how should Postgres know which of the (possibly millions) of ref columns that are NULL to reset to the initial value?

How do copy row from one table to another with dependencies?

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.

inserting data from parent table to child table in postgres

I want to insert a data from parent table to child table if it isn't already in the child table.
create table parents(
c1 varchar (100) primary key,
c2 varchar (100),
);
CREATE TABLE child
(
c1 varchar (100) PRIMARY KEY,
c3 varchar (100),
FOREIGN KEY (c1) REFERENCES parents(c1)
);
Assuming, by data, you mean column c1 only, you can insert into child using a select from parent where you can filter the row to those which aren't present in the child table.
insert into child (c1, c3)
select c1, c2
from parents
where c1 not in (
select c1 from child
);

Foreign key constraints involving multiple tables

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