unique index violation during update - postgresql

I have run into a unique index violation in a bigger db. The original problem occurs in a stored pl/pgsql function.
I have simplified everything to show my problem. I can reproduce it in a rather simple table:
CREATE TABLE public.test
(
id integer NOT NULL DEFAULT nextval('test_id_seq'::regclass),
pos integer,
text text,
CONSTRAINT text_pkey PRIMARY KEY (id)
)
WITH (
OIDS=FALSE
);
ALTER TABLE public.test
OWNER TO root;
GRANT ALL ON TABLE public.test TO root;
I define a unique index on 'pos':
CREATE UNIQUE INDEX test_idx_pos
ON public.test
USING btree
(pos);
Before the UPDATE the data in the table looks like this:
testdb=# SELECT * FROM test;
id | pos | text
----+-----+----------
2 | 1 | testpos1
3 | 2 | testpos2
1 | 5 | testpos4
4 | 4 | testpos3
(4 Zeilen)
tr: (4 rows)
Now I want to decrement all 'pos' values by 1 that are bigger than 2 and get an error (tr are my translations from German to English):
testdb=# UPDATE test SET pos = pos - 1 WHERE pos > 2;
FEHLER: doppelter Schlüsselwert verletzt Unique-Constraint »test_idx_pos«
tr: ERROR: duplicate key violates unique constraint »test_idx_pos«
DETAIL: Schlüssel »(pos)=(4)« existiert bereits.
tr: key »(pos)=(4) already exists.
If the UPDATE had run complete the table would look like this and be unique again:
testdb=# SELECT * FROM test;
id | pos | text
----+-----+----------
2 | 1 | testpos1
3 | 2 | testpos2
1 | 4 | testpos4
4 | 3 | testpos3
(4 Zeilen)
tr: (4 rows)
How can I avoid such situation? I learned that stored pl/pgsql functions are embedded into transactions, so this problem shouldn't appear?

Unique indexes are evaluated per row not per statement (which is e.g. different to Oracle's implementation)
The solution to this problem is to use a unique constraint which can be deferred and thus is evaluated at the end of the transaction.
So instead of the unique index, define a constraint:
alter table test add constraint test_idx_pos unique (pos)
deferrable initially deferred;

Related

postgres: temporary column default that is unique and not nullable, without relying on a sequence?

Hi, I want to add a unique, non-nullable column to a table.
It
already has data. I would therefore like to instantly populate the
new column with unique values, eg 'ABC123', 'ABC124', 'ABC125', etc.
The data will eventually be wiped and
replaced with proper data, so i don't want to introduce a sequence
just to populate the default value.
Is it possible to generate a default value for the existing rows, based on something like rownumber()? I realise the use case is ridiculous but is it possible to achieve... if so how?
...
foo text not null unique default 'ABC'||rownumber()' -- or something similar?
...
can be applied generate_series?
select 'ABC' || generate_series(123,130)::text;
ABC123
ABC124
ABC125
ABC126
ABC127
ABC128
ABC129
ABC130
Variant 2 add column UNIQUE and not null
begin;
alter table test_table add column foo text not null default 'ABC';
with s as (select id,(row_number() over(order by id))::text t from test_table) update test_table set foo=foo || s.t from s where test_table.id=s.id;
alter table test_table add CONSTRAINT unique_foo1 UNIQUE(foo);
commit;
results
select * from test_table;
id | foo
----+------
1 | ABC1
2 | ABC2
3 | ABC3
4 | ABC4
5 | ABC5
6 | ABC6

Postgres exclusion constraint on insert/update

I have a table defined like so
Table "public.foo"
Column | Type | Collation | Nullable | Default
----------+---------+-----------+----------+-------------------------------------
foo_id | integer | | not null | nextval('foo_foo_id_seq'::regclass)
bar_id | integer | | |
approved | boolean | | |
Indexes:
"foo_pkey" PRIMARY KEY, btree (foo_id)
Foreign-key constraints:
"foo_bar_id_fkey" FOREIGN KEY (bar_id) REFERENCES bar(bar_id)
How would I define an exclusion constraint, such that only one row of foo with a specific bar_id would be able to set approved to true?
For example with the following data:
foo_id | bar_id | approved
--------+--------+----------
1 | 1 | t
2 | 1 |
3 | 2 |
(3 rows)
I would be able to set approved to row 3 to true, because no other row with foo_id 3 has true for approved.
However updating row 2 's approved to true would fail, because row 1 also has foo_id 1 and is already approved.
You don't need an exclusion constraint, a filtered unique index will do:
create unique index only_one_approved_bar
on foo (bar_id)
where approved;
I would also recommend to define approved as not null. Boolean columns that allow null values are typically a source of constant confusion.
try this
ALTER TABLE public.foo
ADD CONSTRAINT uniq_approved UNIQUE (bar_id, approved)
or you can create unique index
CREATE UNIQUE INDEX uniq_approved ON public.foo
USING btree (bar_id, approved)
WHERE approved

Add row unique inside of group

I have schema
id | company_id | tag
So I trying to add new unique tag for every company_id.
What would be the best way to achieve it? Select by company_id first and refuse to add tag? Or postgres is able to do it in one shot?
So if I try to add tag tag1 - nothing would happen
id | company_id | tag
1 | 1 | tag1
2 | 1 | tag2
3 | 1 | tag3
Add an UNIQUE constraint to column tag and Postgres will do it (refuse duplicates) for you .
ALTER TABLE my_table ADD CONSTRAINT tag_un UNIQUE (tag);
This makes tag unique across the table. If you need that tag be unique per company_id then
ALTER TABLE my_table ADD CONSTRAINT tag_company_un UNIQUE (tag, company_id);
demo:db<>fiddle
You can create an UNIQUE constraint using both columns. So the combination of both would be unique. Using the same tag on another company_id would be ok:
ALTER TABLE my_table ADD CONSTRAINT tag_unique UNIQUE (company_id, tag);

Postgresql remove values from foreign key that has a cyclic reference and also is referenced in a primary table

There are 2 tables:
the first one is the Father Table
create table win_folder_principal(
id_folder_principal serial primary key not null,
folder_name varchar(300)not null
);
and the table that has a cyclic reference
create table win_folder_dependency(
id_folder_dependency serial primary key not null,
id_folder_father int not null,
id_folder_son int not null,
foreign key(id_folder_father)references win_folder_principal(id_folder_principal),
foreign key(id_folder_son)references win_folder_principal(id_folder_principal)
);
however i found a very interesting situation, if i wanna remove a value from the table father that has a kid and that kid has more kids, is there any way to remove the values from the last to the first but also those values be removed from the Father table?
**WIN_FOLDER_PRINCIPAL**
| Id | Folder_Name|
| 23 | new2 |
| 24 | new3 |
| 13 | new0 |
| 22 | new1 |
| 12 | nFol |
And this are the value stored in the Win_Folder_Dependency
**WIN_FOLDER_DEPENDENCY**
| Id_Father | Id_Son |
| 12 | 13 |
| 13 | 22 |
| 22 | 23 |
| 23 | 24 |
and this is the query that i use to know the values in the dependency and principal table.
SELECT m2.id_folder_principal AS "Principal",
m.folder_name AS "Dependency",
m2.id_folder_principal AS id_principal,
m.id_folder_principal AS id_dependency
FROM ((win_folder_dependency md
JOIN win_folder_principal m ON ((m.id_folder_principal = md.id_folder_son)))
JOIN win_folder_principal m2 ON ((m2.id_folder_principal = md.id_folder_father)))
If i wanna remove the folder with the Id_Principal 13 i need to remove the other relations that exists in the Folder_Dependency table, but also remove the value from the Folder_Principal
is there any way to achieve that cyclic delete?
This anonymous code block will accumulate all the principles rooted with ID 13 searching down the dependency tree in an array parameter named l_Principles. It then deletes all the dependency records where either the father or son (or both) are contained in l_Principles, and then deletes all the principle records identified in l_Principles:
DO $$DECLARE
l_principles int[];
BEGIN
with recursive t1(root, child, pinciples) as (
select id_folder_father
, id_folder_son
, array[id_folder_father, id_folder_son]
from win_folder_dependency
where id_folder_father = 13
union all
select root
, id_folder_son
, pinciples||id_folder_son
from win_folder_dependency
join t1
on id_folder_father = child
and not id_folder_son = any(pinciples) -- Avoid cycles
)
select max(pinciples) into l_principles from t1 group by root;
delete from win_folder_dependency
where id_folder_father = any(l_principles)
or id_folder_son = any(l_principles);
delete from win_folder_principal
where id_folder_principal = any(l_principles);
end$$;
/
With your provided sample data, the end result will be only one record remaining in the win_folder_principal and no records in the win_folder_dependency table.
If you wan to delete a record from win_folder_principal you must first remove the references to it in win_folder_dependency like so:
delete from win_folder_dependency where 13 in (id_folder_father, id_folder_son);
before you delete the record from win_folder_principal like so:
delete from win_folder_principal where id_folder_principal = 13;
Alternatively if you build your second table like this:
create table win_folder_dependency(
id_folder_dependency serial primary key not null,
id_folder_father int not null,
id_folder_son int not null,
foreign key(id_folder_father)references win_folder_principal(id_folder_principal) on delete cascade,
foreign key(id_folder_son)references win_folder_principal(id_folder_principal) on delete cascade
);
Note the on delete cascade directives, then you can just delete from the principal table, and the references in the dependency table will be deleted as well.

Postgresql - `serial` column & inheritence (sequence sharing policy)

In postgresql, when inherit a serial column from parent table, the sequence is shared by parent & child table.
Is it possible to inherit the serial column, while let the 2 table have separated sequence values, e.g both table's column could have value 1.
Is this possible & reasonable, and if yes, how to do that?
#Update
The reasons that I want to avoid sequence sharing are:
Sharing a single int range by multiple table might use up the
MAX_INT, using bigint could improve this, but it takes more space
too.
There is a kind of resource locking when multiple table doing insert concurrently, so it's a performance issue I guess.
The id jump from 1 to 5 then might to 1000 don't look as beautiful as it could.
#Summary
solutions:
If want child table have its own sequence, while still keep the global sequence among parent & child table. (As described in #wildplasser 's answer.)
Then could add a sub_id serial column for each child table.
If want child table have its own sequence, while don't need a global sequence among parent & child table,
There there are 2 ways:
Using int instead of serial. (As described in #lsilva 's answer.)
Steps:
define type as int or bigint in parent table,
for each parent & child table, create a individual sequence,
specify default value for int type for each table using nextval of their own sequence,
don't forget to maintain/reset the sequence, when re-create table,
Define id serial directly in child table, and not in parent table.
DROP schema tmp CASCADE;
CREATE schema tmp;
set search_path = tmp, pg_catalog;
CREATE TABLE common
( seq SERIAL NOT NULL PRIMARY KEY
);
CREATE TABLE one
( subseq SERIAL NOT NULL
, payload integer NOT NULL
)
INHERITS (tmp.common)
;
CREATE TABLE two
( subseq SERIAL NOT NULL
, payload integer NOT NULL
)
INHERITS (tmp.common)
;
/**
\d common
\d one
\d two
\q
***/
INSERT INTO one(payload)
SELECT gs FROM generate_series(1,5) gs
;
INSERT INTO two(payload)
SELECT gs FROM generate_series(101,105) gs
;
SELECT * FROM common;
SELECT * FROM one;
SELECT * FROM two;
Results:
NOTICE: drop cascades to table tmp.common
DROP SCHEMA
CREATE SCHEMA
SET
CREATE TABLE
CREATE TABLE
CREATE TABLE
INSERT 0 5
INSERT 0 5
seq
-----
1
2
3
4
5
6
7
8
9
10
(10 rows)
seq | subseq | payload
-----+--------+---------
1 | 1 | 1
2 | 2 | 2
3 | 3 | 3
4 | 4 | 4
5 | 5 | 5
(5 rows)
seq | subseq | payload
-----+--------+---------
6 | 1 | 101
7 | 2 | 102
8 | 3 | 103
9 | 4 | 104
10 | 5 | 105
(5 rows)
But: in fact you don't need the subseq columns, since you can always enumerate them by means of row_number():
CREATE VIEW vw_one AS
SELECT seq
, row_number() OVER (ORDER BY seq) as subseq
, payload
FROM one;
CREATE VIEW vw_two AS
SELECT seq
, row_number() OVER (ORDER BY seq) as subseq
, payload
FROM two;
[results are identical]
And, you could add UNIQUE AND PRIMARY KEY constraints to the child tables, like:
CREATE TABLE one
( subseq SERIAL NOT NULL UNIQUE
, payload integer NOT NULL
)
INHERITS (tmp.common)
;
ALTER TABLE one ADD PRIMARY KEY (seq);
[similar for table two]
I use this :
Parent table definition:
CREATE TABLE parent_table (
id bigint NOT NULL,
Child table definition:
CREATE TABLE cild_schema.child_table
(
id bigint NOT NULL DEFAULT nextval('child_schema.child_table_id_seq'::regclass),
I am emulating the serial by using a sequence number as a default.