It's a very hacky work-around, and if someone has the "correct" answer, I'll gladly mark it as such, but here's my solution until then.
DELIMITER //
CREATE PROCEDURE `sp_move_item` (
fromOrder INT,
toOrder INT
)
BEGIN
-- @shift is the amount each other item needs to move to make space
-- for the target item: -1 for moving "up" and 1 for moving "down"
SELECT @low := CASE WHEN fromOrder > toOrder THEN toOrder ELSE fromOrder END,
@high := CASE WHEN fromOrder < toOrder THEN toOrder ELSE fromOrder END,
@shift := CASE WHEN fromOrder > toOrder THEN 1 ELSE -1 END;
START TRANSACTION;
-- Get the itemOrder of the item at the bottom of the list and add 1.
-- Setting items' itemOrder to values ≥ @offset guarantees no overlap
-- with the unaffected items of the table.
SELECT @offset := MAX(itemOrder) + 1
FROM tbl_items;
-- Set the itemOrder of all affected items to be at the end of the
-- table in the same order they are already in. Guarantees no overlap
-- with each other.
UPDATE tbl_items
SET itemOrder = itemOrder + @offset
WHERE itemOrder BETWEEN @low AND @high;
-- Move the affected items back into place in the desired order.
UPDATE tbl_items
SET itemOrder = CASE
WHEN itemOrder = fromOrder + @offset THEN toOrder
ELSE itemOrder + @shift - @offset END
WHERE itemOrder >= @offset;
COMMIT;
END //
DELIMITER ;
This method has terribly poor performance as it needs to reorder the index 2 * (@high - @low)
times. The more rows in the table, the greater the performance hit, even if @high - @low = 1
(a simple swap) as it currently is in my case.
A faster method (though terribly ugly code-wise) if there is only one index on the table and you're only doing a 1-position swap, would be to copy every column value of the row being moved into variables, overwrite that row's values with the other affected row, and then update that row to the variables' values, just as you would do for swapping values in an array.
Note: This code assumes that itemOrder
s are sequential with no missing values, though I think it will still work even if that's not the case. I haven't thought it through, though, so your mileage may vary.