I have a table of labels that are displayed in a ranked order. To ensure that no two rows can have the same rank, their values are unique:
create table label (
id_label serial not null,
rank integer not null,
title text not null,
constraint pri primary key (id_label),
constraint unq unique (rank)
)
Doesn't matter if it's PostgreSQL or MySQL, they exhibit the same behaviour. A query might look like select title from label order by rank
. Assume the table contains:
id_label rank title
1 10 Cow
2 20 Apple
3 45 Horse
4 60 Beer
Now suppose I want to reorder two labels, e.g. have Apple ranked before Cow. The easiest way is to swap their rank values:
update label
set rank = case when rank = 20 then 10 else 20 end
where id_label in (1,2)
Nope. Nor:
update label
set rank = case when rank = 20 then rank - 10 else rank + 10 end
where id_label in (1,2)
Nor even:
update label
set rank = 30 - rank
where id_label in (1,2)
Each time, the unique constraint fires on the first row update and aborts the operation. If I could defer the check until the end of the statement I would be fine. This happens on both PostgreSQL and MySQL.
An ACID-safe workaround is to:
That's just unspeakably ugly. Worse is to drop the constraint, update, and then recreate the constraint. Granting such privileges to an operational role is asking for trouble. So my question is this: is there a simple technique I have overlooked that solves this problem, or am I SOL?
With PostgreSQL this can only be solved in a "nice" way using Version 9.0 because you can define unique constraints to be deferrable there.
With PostgreSQL 9.0 you'd simply do:
create table label (
id_label serial not null,
rank integer not null,
title text not null,
constraint pri primary key (id_label)
);
alter table label add constraint unique_rank unique (rank)
deferrable initially immediate;
Then the update is as simple as this:
begin;
set constraints unique_rank DEFERRED;
update rank
set rank = case when rank = 20 then 10 else 20 end
where id_label in (1,2);
commit;
Edit:
If you don't want to bother setting the constraint to deferred inside your transaction, you can simply define the constraint as initially deferred
.
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