如何在不违反唯一约束的情况下交换MySQL中两行的值?

时间:2012-06-26 12:42:01

标签: mysql

我有一个带有优先级列的“任务”表,该列具有唯一约束。

我正在尝试交换两行的优先级值,但我一直违反约束。我在类似的情况下看到了这个声明,但它不是MySQL。

UPDATE tasks 
SET priority = 
CASE
    WHEN priority=2 THEN 3 
    WHEN priority=3 THEN 2 
END 

WHERE priority IN (2,3);

这将导致错误:

Error Code: 1062. Duplicate entry '3' for key 'priority_UNIQUE'

是否可以在不使用虚假值和多个查询的情况下在MySQL中完成此操作?

编辑:

这是表结构:

CREATE TABLE `tasks` (
  `id` int(11) NOT NULL,
  `name` varchar(200) DEFAULT NULL,
  `priority` varchar(45) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `priority_UNIQUE` (`priority`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

6 个答案:

答案 0 :(得分:26)

  

是否可以在不使用虚假值和多个查询的情况下在MySQL中完成此操作?

不。(我无法想到)。

问题是MySQL如何处理更新。 MySQL(与正确实现UPDATE的其他DBMS不同),以破碎的方式处理更新。在整个UNIQUE语句完成后,它会在每次单行更新后强制检查UPDATE(和其他)约束,而不是 - 应该这样做 - 。这就是为什么你(大多数)其他DBMS没有这个问题。

对于某些更新(例如增加所有或某些ID,id=id+1),可以使用 - 另一个非标准功能 - 更新中的ORDER BY来解决此问题。

为了交换两行中的值,这个技巧无济于事。您将不得不使用NULL或伪造的值(不存在但在您的专栏中允许)和2或3个陈述。

您也可以暂时删除唯一约束,但我认为这不是一个好主意。


因此,如果唯一列是有符号整数且没有负值,则可以使用事务中包含的2个语句:

START TRANSACTION ;
    UPDATE tasks 
    SET priority = 
      CASE
        WHEN priority = 2 THEN -3 
        WHEN priority = 3 THEN -2 
      END 
    WHERE priority IN (2,3) ;

    UPDATE tasks 
    SET priority = - priority
    WHERE priority IN (-2,-3) ;
COMMIT ;

答案 1 :(得分:2)

我碰到了同样的问题。曾经使用CASE WHEN和TRANSACTION尝试了所有可能的单语句查询 - 没有任何运气。我提出了三种替代解决方案。你需要决定哪一个对你的情况更有意义。 在我的情况下,我处理从前端返回的重组的小对象集合(数组),新订单是不可预测的(这不是交换两项交易),并且,除了所有内容之外,更改顺序(通常以英文版本制作)必须传播到其他15种语言。

  1. 第一种方法:完全删除现有记录并使用新数据重新填充整个集合。显然,只有当您从前端接收恢复刚删除内容所需的所有内容时,此功能才有效。

  2. 第二种方法:此解决方案类似于使用虚假值。在我的情况下,我重新订购的集合还包括移动之前的原始项目位置。此外,在UPDATE运行时,我必须以某种方式保留原始索引值。诀窍是操纵索引列的第15位,在我的情况下是UNSIGNED SMALLINT。如果您有(签名)INT / SMALLINT数据类型,则只能反转索引的值而不是按位操作。

  3. 首次UPDATE每次调用只能运行一次。此查询引发当前index字段的第15位(我有unsigned smallint)。之前的14位仍然反映原始指数值,它永远不会接近32K范围。

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

    然后迭代您的集合,提取原始索引值和新索引值,并单独更新每条记录。

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

    最后一次UPDATE每次调用也必须只运行一次。此查询清除index字段的第15位,有效地恢复原始索引值,以显示尚未更改的记录(如果有)。

    UPDATE *table* SET `index`=(`index` & 32767) WHERE *same_condition* AND `index` > 32767;
    
    1. 第三种方法是移动相关记录到没有主键的临时表中,更新所有索引,然后移动所有记录回到第一个表

答案 2 :(得分:0)

虚假值选项:

好的,所以我的查询类似,我找到了一种更新“one”查询的方法。我的id列是PRIMARY,position是UNIQUE组的一部分。这是我原来的查询,不适用于交换:

INSERT INTO `table` (`id`, `position`)
  VALUES (1, 2), (2, 1)
  ON DUPLICATE KEY UPDATE `position` = VALUES(`position`);

..但是position是无符号整数,它从不为0,所以我将查询更改为以下内容:

INSERT INTO `table` (`id`, `position`)
  VALUES (2, 0), (1, 2), (2, 1)
  ON DUPLICATE KEY UPDATE `position` = VALUES(`position`);

..现在它有效!显然,MYSQL按顺序处理值组。

也许这对你有用(没有经过测试,我对MYSQL几乎一无所知):

UPDATE tasks 
SET priority = 
CASE
    WHEN priority=3 THEN 0 
    WHEN priority=2 THEN 3 
    WHEN priority=0 THEN 2 
END 

WHERE priority IN (2,3,0);
祝你好运。

答案 3 :(得分:0)

有类似的问题。

我想交换2个唯一的ID,并且是另一个表中的FK。

我交换两个唯一条目的最快解决方案是:

  1. 在我的FK表中创建一个幻像条目。
  2. 回到我要在其中切换ID的表中。
  3. 启用FK支票SET FOREIGN_KEY_CHECKS=0;
  4. 将我的第一个(A)ID设置为ghost(X)fk(免费的A)
  5. 将我的第二个(B)ID设置为A(免费的B)
  6. 将A设置为B(免费X)
  7. 删除幽灵记录并重新打开检查。 SET FOREIGN_KEY_CHECKS=1;

答案 4 :(得分:-2)

不确定这是否会违反约束,但我一直试图做类似的事情,并最终通过结合我找到的一些答案提出了这个问题:

UPDATE tasks as T1,tasks as T2 SET T1.priority=T2.priority,T2.priority=T1.priority WHERE (T1.task_id,T2.task_id)=($T1_id, $T2_id)

我交换的专栏没有使用独特的,所以我不确定这是否会有所帮助......

答案 5 :(得分:-3)

您可以使用上面提到的更新语句来交换您的值,只需稍微更改您的键索引。

CREATE TABLE `tasks` (   `id` int(11) NOT NULL,   `name` varchar(200) DEFAULT NULL,   `priority` varchar(45) DEFAULT NULL,   PRIMARY KEY (`id`,`priority`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

这将主键索引作为id和priority的组合。你cna然后交换价值。

UPDATE tasks 
SET priority = 
CASE
    WHEN priority=2 THEN 3 
    WHEN priority=3 THEN 2 
END 

WHERE priority IN (2,3);

我在这里看不到任何用户变量或临时变量的需要。 希望这能解决你的问题:)