Swapping values in some table's records for a particular column that has a UNIQUE
constraint.
Considering the following table:
CREATE TABLE records (id numeric, name text);
ALTER TABLE records ADD CONSTRAINT uniq UNIQUE (id);
INSERT INTO records VALUES
(1, 'First record'),
(3, 'Second record'),
(2, 'Third record'),
(4, 'Fourth record');
We get, from querying for SELECT id, name FROM records ORDER BY id;
:
| id | name |
|----|---------------|
| 1 | First record |
| 2 | Third record |
| 3 | Second record |
| 4 | Fourth record |
I need the id
s for the Second record
and Third record
swapped. That is, I want the following output:
| id | name |
|----|---------------|
| 1 | First record |
| 2 | Second record | <-- this record previously had the id 3
| 3 | Third record | <-- this record previously had the id 2
| 4 | Fourth record |
name
s for these records, my actual database is a tad bigger than this:
id
column is actually a PRIMARY KEY
that is referenced by other tables.id
s that aren't assigned to any record yet to execute this swapping.id
will always be unique after each bulk reassignment operation andid
s from a previous assignment will overlap with those of the new assignment map.I have tried swapping my data in a single query by generating a temporary reassignment table and using it in an UPDATE ... SET ... FROM ... WHERE
query.
Here's my temporary reassignment table, which will swap the id
s 2
and 3
:
(VALUES
(3, 2),
(2, 3)
) AS swap(id, new_id)
And here's how I use it:
UPDATE records AS record SET
id = swap.new_id
FROM (VALUES
(3, 2),
(2, 3)
) AS swap(id, new_id)
WHERE record.id = swap.id;
SELECT id, name FROM records ORDER BY id;
As you can see in this SQLFiddle link, it works pretty well... until you add the UNIQUE
constraint on the id
column.
How could I get this working under all the conditions listed above?
The syntax for creating a unique constraint using an ALTER TABLE statement in PostgreSQL is: ALTER TABLE table_name ADD CONSTRAINT constraint_name UNIQUE (column1, column2, ... column_n);
PostgreSQL provides you with the UNIQUE constraint that maintains the uniqueness of the data correctly. When a UNIQUE constraint is in place, every time you insert a new row, it checks if the value is already in the table. It rejects the change and issues an error if the value already exists.
Postgres currently defines column order based on the attnum column of the pg_attribute table. The only way to change column order is either by recreating the table, or by adding columns and rotating data until you reach the desired layout.
The issue is with the CONSTRAINT
's definition.
PosgreSQL's CONSTRAINTS
can be DEFERRABLE
. From their documentation, we gather the following:
IMMEDIATE constraints are checked at the end of each statement. DEFERRED constraints are not checked until transaction commit. Each constraint has its own IMMEDIATE or DEFERRED mode.
In the case we're facing, the id
s will still be UNIQUE
s AFTER the transaction, while indeed that CONSTRAINT
cannot be satisfied DURING the reassignment of id
s.
Simply making the relevant CONSTRAINT
DEFERRABLE
will solve the problem at hand.
CREATE TABLE records (id numeric, name text);
ALTER TABLE records ADD CONSTRAINT uniq UNIQUE (id) DEFERRABLE INITIALLY IMMEDIATE;
INSERT INTO records VALUES -- ^
(1, 'First record'), -- HERE
(3, 'Second record'),
(2, 'Third record'),
(4, 'Fourth record');
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With