How to swap values of two rows in MySQL without violating unique constraint?

前端 未结 6 796
-上瘾入骨i
-上瘾入骨i 2020-12-01 07:29

I have a \"tasks\" table with a priority column, which has a unique constraint.

I\'m trying to swap the priority value of two rows, but I keep violating the constrai

6条回答
  •  暗喜
    暗喜 (楼主)
    2020-12-01 07:53

    I bumped into the same issue. Had tried every possible single-statement query using CASE WHEN and TRANSACTION - no luck whatsoever. I came up with three alternative solutions. You need to decide which one makes more sense for your situation. In my case, I'm processing a reorganized collection (array) of small objects returned from the front-end, new order is unpredictable (this is not a swap-two-items deal), and, on top of everything, change of order (usually made in English version) must propagate to 15 other languages.

    1. 1st method: Completely DELETE existing records and repopulate entire collection using the new data. Obviously this can work only if you're receiving from the front-end everything that you need to restore what you just deleted.

    2. 2st method: This solution is similar to using bogus values. In my situation, my reordered collection also includes original item position before it moved. Also, I had to preserve original index value in some way while UPDATEs are running. The trick was to manipulate bit-15 of the index column which is UNSIGNED SMALLINT in my case. If you have (signed) INT/SMALLINT data type you can just invert the value of the index instead of bitwise operations.

    First UPDATE must run only once per call. This query raises 15th bit of the current index fields (I have unsigned smallint). Previous 14 bits still reflect original index value which is never going to come close to 32K range.

    UPDATE *table* SET `index`=(`index` | 32768) WHERE *condition*;
    

    Then iterate your collection extracting original and new index values, and UPDATE each record individually.

    foreach( ... ) {
        UPDATE *table* SET `index`=$newIndex WHERE *same_condition* AND `index`=($originalIndex | 32768);
    }
    

    This last UPDATE must also run only once per call. This query clears 15th bit of the index fields effectively restoring original index value for records where it hasn't changed, if any.

    UPDATE *table* SET `index`=(`index` & 32767) WHERE *same_condition* AND `index` > 32767;
    
    1. Third method would be to move relevant records into temporary table that doesn't have a primary key, UPDATE all indexes, then move all records back to first table.

提交回复
热议问题