更新多行时MySQL重复条目

时间:2019-04-14 19:56:51

标签: mysql

我有一个项目表,其中每个项目都有一个itemOrderitemOrderPRIMARY KEY的一部分。我正在尝试写一个UPDATE来重新排列项目。

例如,如果我想将项目3移至项目1,则项目3的新itemOrder将为1,项目1的将为2,项目2的将为3。此查询在SQL Server中有效,因为UPDATE语句是事务性的。

UPDATE tbl_items
SET itemOrder = CASE WHEN itemOrder = 3 THEN 1 ELSE itemOrder + 1 END
WHERE itemOrder BETWEEN 1 and 3;

在MySQL中,似乎一次执行一次更新,并且如果在任何时候都存在重复的PK,则会引发错误。必须有某种解决方法,对吧?我尝试了sql swap primary key values的答案,但无济于事。我还尝试将语句包装在START TRANSACTION; / COMMIT;中,但是它具有相同的错误。

我猜想MySQL解决方案与ON DUPLICATE KEY有关系,但是我无法全神贯注于它,也无法在我的场景中使用它。

2 个答案:

答案 0 :(得分:0)

默认情况下,MySQL在“自动提交”模式下运行;您需要手动创建交易:

<input id="txtfname" type="text" name="Your_Name" placeholder="First Name">
<input id="txtlname" type="text" name="lastname" placeholder="Last Name">

<script>
function checkName()
{
    if(document.getElementById('txtfname').value == ""){
        alert("Enter first name");
        return false;
    } else if (document.getElementById('txtfname').value <> "" && document.getElementById('txtlname').value == ""){
        alert("Enter last name");
        return false;
    }
    else{
    return true
    }
}
</script>

或者,您可以在更新之前关闭自动提交。

begin;
update ...;
update ...;
commit;

,并(可选)在更新后将其重新打开:

set autocommit = 0;

答案 1 :(得分:0)

这是一个非常棘手的解决方法,如果有人得到“正确”的答案,我会很乐意将其标记为这样,但这是我的解决方案。

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 ;

此方法的性能极差,因为它需要重新排序索引2 * (@high - @low)次。表中的行越多,即使是@high - @low = 1(一个简单的交换),对我来说,它的性能影响也更大。

如果表上只有一个索引并且您只进行1位交换,那么一种更快的方法(尽管在代码方面非常丑陋)会复制将要移动的行的每个列值复制到变量中,用另一受影响的行覆盖该行的值,然后将该行更新为变量的值,就像交换数组中的值一样。

注意:此代码假定itemOrder是连续的,没有缺失值,尽管我认为仍然可以使用,即使不是这种情况。不过,我还没想好,所以您的行程可能会有所不同。