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:
- begin transaction
- select ranks of first, second record, and highest (max) rank in table (which offhand will probably require a union)
- update first record to rank = max + 1
- update second record to rank of first
- update first record to rank of second
- commit
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?