Updating multiple rows with SQL and avoiding collisions

I ran into an interesting problem with SQL the other day: how do you update multiple rows while maintaining an uniqueness constraint?

I have a table where each row describes an item in an ordered list. Each row has a position value. They are integers, not necessarily contiguous but each unique. A larger position means the row is further down the list. For reordering the rows, I sometimes need to make space between two positions.

How do you do that? Well, the obvious answer is a simple update statement, assuming you want to make space for one item before position 5:

UPDATE table
SET position = position + 1
WHERE position >= 5

Despite having used SQL since the 90s I don't think I've ever needed to do this before. It seemed simple enough, but I found out the solution I went with is not only obvious but also wrong. When you have contiguous position values, that statement causes a unique constraint violation, in both SQLite and PostgreSQL. Having transaction isolation doesn't prevent collisions during updates, even if the end state would be valid.

A helpful LLM tried to suggest the broken solution, then a solution that caused a syntax error and then a solution that involved creating a temporary table. After that I went back to searching the web and finally found a mention about negating the values temporarily which sounded like way less hassle than temporary tables.

That worked great. So, to add space for N items before position X:

  1. Begin transaction.
  2. Instead of incrementing the value on each row greater than or equal to X by N, multiply the value by -1 and decrement it by N.
  3. Multiply each value smaller than zero by -1.
  4. Commit.
BEGIN TRANSACTION;

UPDATE table
SET position = -position - 1
WHERE position >= 5;

UPDATE table
SET position = -position
WHERE position < 0;

COMMIT;

© Juri Pakaste 2025