【问题标题】:Swapping column values in MySQL在 MySQL 中交换列值
【发布时间】:2010-09-07 10:46:58
【问题描述】:

我有一个带有坐标的 MySQL 表,列名是 X 和 Y。现在我想交换该表中的列值,使 X 变为 Y,Y 变为 X。最明显的解决方案是重命名列,但我不想更改结构,因为我不一定有权这样做。

这可能与 UPDATE 以某种方式有关吗? UPDATE table SET X=Y, Y=X 显然不会做我想做的事。


编辑:请注意,上面提到的我对权限的限制有效地阻止了使用 ALTER TABLE 或其他更改表/数据库结构的命令。不幸的是,重命名列或添加新列不是选项。

【问题讨论】:

  • 请注意,UPDATE table SET X = Y, Y = X 是在 SQL 中执行此操作的标准方式,只有 MySQL 行为不端。

标签: mysql database


【解决方案1】:

我只需要处理同样的问题,然后我会总结一下我的发现。

  1. UPDATE table SET X=Y, Y=X 方法显然不起作用,因为它只会将两个值都设置为 Y。

  2. 这是一个使用临时变量的方法。感谢 http://beerpla.net/2009/02/17/swapping-column-values-in-mysql/ 的 cmets 的 Antony 进行了“IS NOT NULL”调整。没有它,查询将无法预测。请参阅文章末尾的表架构。如果其中一个为 NULL,则此方法不会交换值。使用没有此限制的方法#3。

    UPDATE swap_test SET x=y, y=@temp WHERE (@temp:=x) IS NOT NULL;

  3. 此方法由 Dipin 在 http://beerpla.net/2009/02/17/swapping-column-values-in-mysql/ 的 cmets 中再次提供。我认为这是最优雅、最干净的解决方案。它适用于 NULL 和非 NULL 值。

    UPDATE swap_test SET x=(@temp:=x), x = y, y = @temp;

  4. 我想出的另一种方法似乎可行:

    UPDATE swap_test s1, swap_test s2 SET s1.x=s1.y, s1.y=s2.x WHERE s1.id=s2.id;

本质上,第一个表是要更新的表,第二个表用于从中提取旧数据。
请注意,此方法需要存在主键。

这是我的测试架构:

CREATE TABLE `swap_test` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `x` varchar(255) DEFAULT NULL,
  `y` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

INSERT INTO `swap_test` VALUES ('1', 'a', '10');
INSERT INTO `swap_test` VALUES ('2', NULL, '20');
INSERT INTO `swap_test` VALUES ('3', 'c', NULL);

【讨论】:

  • 如 MySQL 文档中所述,在单个语句中分配和读取变量是不安全的。不保证操作的顺序。所以唯一安全的方法是#4
  • 你知道,我从没想过这个愚蠢的面试问题会有实际用途,要求在不使用临时变量的情况下交换两个变量,但它就是这样,对于整数,这实际上会起作用:更新swap_test 设置 x=x+y,y=xy,x=xy;
  • @Jhawins 那是因为 beerpla.net 是我的博客。
  • 您的回答让我知道这是可行的,您的方法 #4 正是我所需要的。这比 mysql 手册更全面。
  • 现在是 22:30,我刚刚在服务于 100K+ 用户的实时数据库上运行了批量产品插入。我错误地交换了列名,对我现在不得不坐下来想办法解决这个问题感到不高兴,所以我来了。你刚刚给了我我想要的东西。谢谢你,真诚地感谢你。
【解决方案2】:

您可以取和并使用 X 和 Y 减去相反的值

UPDATE swaptest SET X=X+Y,Y=X-Y,X=X-Y;

这是一个示例测试(它适用于负数)

mysql> use test
Database changed
mysql> drop table if exists swaptest;
Query OK, 0 rows affected (0.03 sec)

mysql> create table swaptest (X int,Y int);
Query OK, 0 rows affected (0.12 sec)

mysql> INSERT INTO swaptest VALUES (1,2),(3,4),(-5,-8),(-13,27);
Query OK, 4 rows affected (0.08 sec)
Records: 4  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM swaptest;
+------+------+
| X    | Y    |
+------+------+
|    1 |    2 |
|    3 |    4 |
|   -5 |   -8 |
|  -13 |   27 |
+------+------+
4 rows in set (0.00 sec)

mysql>

这是正在执行的交换

mysql> UPDATE swaptest SET X=X+Y,Y=X-Y,X=X-Y;
Query OK, 4 rows affected (0.07 sec)
Rows matched: 4  Changed: 4  Warnings: 0

mysql> SELECT * FROM swaptest;
+------+------+
| X    | Y    |
+------+------+
|    2 |    1 |
|    4 |    3 |
|   -8 |   -5 |
|   27 |  -13 |
+------+------+
4 rows in set (0.00 sec)

mysql>

试试看!!!

【讨论】:

  • 就数字而言,它确实是一个最整洁的数字。
  • add时值溢出会不会有问题?
  • @ToolmakerSteve 可能是 TINYINTINT 的巨大价值,你是对的!!!
【解决方案3】:

以下代码适用于我的快速测试中的所有场景:

UPDATE swap_test
   SET x=(@temp:=x), x = y, y = @temp

【讨论】:

  • UPDATE table swap_test?不应该是UPDATE swap_test吗?
  • 谢谢。这在 MySql 8.0.23 和 MySql 5.7.31 上就像一个魅力
【解决方案4】:

UPDATE table SET X=Y, Y=X 将精确地执行您想要的操作(编辑:在 PostgreSQL 中,而不是 MySQL,见下文)。这些值取自旧行并分配给同一行的新副本,然后替换旧行。您不必求助于使用临时表、临时列或其他交换技巧。

@D4V360:我明白了。这是令人震惊和意想不到的。我使用 PostgreSQL,我的答案在那里正常工作(我试过了)。请参阅PostgreSQL UPDATE docs(在参数,表达式下),其中提到 SET 子句右侧的表达式显式使用列的旧值。我看到相应的MySQL UPDATE docs 包含语句“单表更新分配通常从左到右进行评估”,这意味着您描述的行为。

很高兴知道。

【讨论】:

  • 感谢 Greg 和 D4V360,很高兴了解 PostgreSQL 和 MySQL 在更新查询行为方面的差异。
  • “x=y, y=x” 方法也适用于 Oracle,因为它物有所值。
  • 我使用了 PostgreSQL 并且 SET X=Y, Y=X 救了我 :)
  • 恕我直言,这个答案是一团糟 - 附加了“哎呀没关系”的坏建议。其中一半应该是评论,其余与问题相关的唯一部分是指向 MySQL 文档的链接...
【解决方案5】:

好的,只是为了好玩,你可以这样做! (假设您正在交换字符串值)

mysql> select * from swapper;
+------+------+
| foo  | bar  |
+------+------+
| 6    | 1    | 
| 5    | 2    | 
| 4    | 3    | 
+------+------+
3 rows in set (0.00 sec)

mysql> update swapper set 
    -> foo = concat(foo, "###", bar),
    -> bar = replace(foo, concat("###", bar), ""),
    -> foo = replace(foo, concat(bar, "###"), "");

Query OK, 3 rows affected (0.00 sec)
Rows matched: 3  Changed: 3  Warnings: 0

mysql> select * from swapper;
+------+------+
| foo  | bar  |
+------+------+
| 1    | 6    | 
| 2    | 5    | 
| 3    | 4    | 
+------+------+
3 rows in set (0.00 sec)

在 MySQL 中滥用从左到右的评估过程很有趣。

或者,如果它们是数字,则只需使用 XOR。你提到了坐标,那么你有可爱的整数值还是复杂的字符串?

编辑:顺便说一下,XOR 的工作原理是这样的:

update swapper set foo = foo ^ bar, bar = foo ^ bar, foo = foo ^ bar;

【讨论】:

    【解决方案6】:

    我相信有一个中间交换变量是最好的做法:

    update z set c1 = @c := c1, c1 = c2, c2 = @c
    

    首先,它始终有效;其次,无论数据类型如何,它都能正常工作。

    尽管两者兼而有之

    update z set c1 = c1 ^ c2, c2 = c1 ^ c2, c1 = c1 ^ c2
    

    update z set c1 = c1 + c2, c2 = c1 - c2, c1 = c1 - c2
    

    正常工作,顺便只针对数字数据类型,防止溢出是你的责任,你不能在有符号和无符号之间使用XOR,你也不能使用sum来溢出可能性。

    update z set c1 = c2, c2 = @c where @c := c1
    

    不工作 如果 c1 是 0 或 NULL 或零长度字符串或只是空格。

    我们需要把它改成

    update z set c1 = c2, c2 = @c where if((@c := c1), true, true)
    

    这是脚本:

    mysql> create table z (c1 int, c2 int)
        -> ;
    Query OK, 0 rows affected (0.02 sec)
    
    mysql> insert into z values(0, 1), (-1, 1), (pow(2, 31) - 1, pow(2, 31) - 2)
        -> ;
    Query OK, 3 rows affected (0.00 sec)
    Records: 3  Duplicates: 0  Warnings: 0
    
    mysql> select * from z;
    +------------+------------+
    | c1         | c2         |
    +------------+------------+
    |          0 |          1 |
    |         -1 |          1 |
    | 2147483647 | 2147483646 |
    +------------+------------+
    3 rows in set (0.02 sec)
    
    mysql> update z set c1 = c1 ^ c2, c2 = c1 ^ c2, c1 = c1 ^ c2;
    ERROR 1264 (22003): Out of range value for column 'c1' at row 2
    mysql> update z set c1 = c1 + c2, c2 = c1 - c2, c1 = c1 - c2;
    ERROR 1264 (22003): Out of range value for column 'c1' at row 3
    
    mysql> select * from z;
    +------------+------------+
    | c1         | c2         |
    +------------+------------+
    |          0 |          1 |
    |          1 |         -1 |
    | 2147483646 | 2147483647 |
    +------------+------------+
    3 rows in set (0.02 sec)
    
    mysql> update z set c1 = c2, c2 = @c where @c := c1;
    Query OK, 2 rows affected (0.00 sec)
    Rows matched: 2  Changed: 2  Warnings: 0
    
    mysql> select * from z;
    +------------+------------+
    | c1         | c2         |
    +------------+------------+
    |          0 |          1 |
    |         -1 |          1 |
    | 2147483647 | 2147483646 |
    +------------+------------+
    3 rows in set (0.00 sec)
    
    mysql> select * from z;
    +------------+------------+
    | c1         | c2         |
    +------------+------------+
    |          1 |          0 |
    |          1 |         -1 |
    | 2147483646 | 2147483647 |
    +------------+------------+
    3 rows in set (0.00 sec)
    
    mysql> update z set c1 = @c := c1, c1 = c2, c2 = @c;
    Query OK, 3 rows affected (0.02 sec)
    Rows matched: 3  Changed: 3  Warnings: 0
    
    mysql> select * from z;
    +------------+------------+
    | c1         | c2         |
    +------------+------------+
    |          0 |          1 |
    |         -1 |          1 |
    | 2147483647 | 2147483646 |
    +------------+------------+
    3 rows in set (0.00 sec)
    
    mysql>update z set c1 = c2, c2 = @c where if((@c := c1), true, true);
    Query OK, 3 rows affected (0.02 sec)
    Rows matched: 3  Changed: 3  Warnings: 0
    
    mysql> select * from z;
    +------------+------------+
    | c1         | c2         |
    +------------+------------+
    |          1 |          0 |
    |          1 |         -1 |
    | 2147483646 | 2147483647 |
    +------------+------------+
    3 rows in set (0.00 sec)
    

    【讨论】:

    • +1 终于找到了一个愚蠢的面试问题的好用处,你必须在没有临时的情况下交换两个变量;-)
    【解决方案7】:

    两种选择 1.使用临时表 2. 调查 XOR algorithm

    【讨论】:

      【解决方案8】:

      ALTER TABLE table ADD COLUMN tmp;
      UPDATE table SET tmp = X;
      UPDATE table SET X = Y;
      UPDATE table SET Y = tmp;
      ALTER TABLE table DROP COLUMN tmp;
      
      像这样?

      编辑:关于格雷格的评论: 不,这不起作用:

      mysql> select * from test;
      +------+------+
      | x    | y    |
      +------+------+
      |    1 |    2 |
      |    3 |    4 |
      +------+------+
      2 rows in set (0.00 sec)

      mysql> 更新测试集 x=y, y=x; 查询正常,2 行受影响(0.00 秒) 匹配行:2 更改:2 警告:0

      mysql> select * from test; +------+------+ | x |是 | +------+------+ | 2 | 2 | | 4 | 4 | +------+------+ 2 行(0.00 秒)

      【讨论】:

      • 仅作记录:这确实在 PostgreSQL 中工作,而在 MySQL 中工作。
      【解决方案9】:

      这肯定行得通!我只需要它来交换欧元和 SKK 价格列。 :)

      UPDATE tbl SET X=Y, Y=@temp where @temp:=X;
      

      上述方法不起作用(ERROR 1064 (42000): You have an error in your SQL syntax)

      【讨论】:

        【解决方案10】:

        在 SQL Server 中,您可以使用以下查询:

        update swaptable 
        set col1 = t2.col2,
        col2 = t2.col1
        from swaptable t2
        where id = t2.id
        

        【讨论】:

          【解决方案11】:

          假设您的列中有带符号整数,您可能需要使用 CAST(a ^ b AS SIGNED),因为 ^ 运算符的结果是 MySQL 中的无符号 64 位整数。

          如果它对任何人有帮助,这是我用来在两个给定行之间交换同一列的方法:

          SELECT BIT_XOR(foo) FROM table WHERE key = $1 OR key = $2
          
          UPDATE table SET foo = CAST(foo ^ $3 AS SIGNED) WHERE key = $1 OR key = $2
          

          其中 $1 和 $2 是两行的键,$3 是第一个查询的结果。

          【讨论】:

            【解决方案12】:

            我没试过,但是

            UPDATE tbl SET @temp=X, X=Y, Y=@temp
            

            可能会。

            标记

            【讨论】:

              【解决方案13】:

              可以更改列名,但这更像是一种 hack。但要小心这些列上可能存在的任何索引

              【讨论】:

                【解决方案14】:

                表名是 customer。 字段为 a 和 b,将 a 值交换为 b;。

                更新客户 SET a=(@temp:=a), a = b, b = @temp

                我检查了它工作正常。

                【讨论】:

                  【解决方案15】:

                  使用单个查询交换列值

                  更新 my_table SET a=@tmp:=a, a=b, b=@tmp;

                  干杯...!

                  【讨论】:

                  【解决方案16】:

                  我必须将值从一列移动到另一列(如归档)并重置原始列的值。
                  以下(上面接受的答案中#3的参考)对我有用。

                  Update MyTable set X= (@temp:= X), X = 0, Y = @temp WHERE ID= 999;
                  

                  【讨论】:

                    【解决方案17】:
                    CREATE TABLE Names
                    (
                    F_NAME VARCHAR(22),
                    L_NAME VARCHAR(22)
                    );
                    
                    INSERT INTO Names VALUES('Ashutosh', 'Singh'),('Anshuman','Singh'),('Manu', 'Singh');
                    
                    UPDATE Names N1 , Names N2 SET N1.F_NAME = N2.L_NAME , N1.L_NAME = N2.F_NAME 
                    WHERE N1.F_NAME = N2.F_NAME;
                    
                    SELECT * FROM Names;
                    

                    【讨论】:

                      【解决方案18】:

                      此示例将 start_dateend_date 交换为日期错误的记录(在将 ETL 执行为主要重写时,我发现了一些 start 日期晚于它们的结束日期。糟糕,糟糕的程序员!)。

                      在原地,出于性能原因,我使用 MEDIUMINTs(如 Julian days,但根为 1900-01-01),所以我可以做 WHERE mdu.start_date > mdu 的条件。结束日期

                      PK 分别位于所有 3 列(出于操作/索引原因)。

                      UPDATE monitor_date mdu
                      INNER JOIN monitor_date mdc
                          ON mdu.register_id = mdc.register_id
                          AND mdu.start_date = mdc.start_date
                          AND mdu.end_date = mdc.end_date
                      SET mdu.start_date = mdu.end_date, mdu.end_date = mdc.start_date
                      WHERE mdu.start_date > mdu.end_date;
                      

                      【讨论】:

                      • 仅供参考:此代码在 0.203 秒内更新了 145 / 108,456 条记录。这是一项一次性任务,因此性能并不重要。
                      【解决方案19】:

                      假设您想交换 tb_user 中名字和姓氏的值。

                      最安全的是:

                      1. 复制 tb_user。所以你将有 2 个表:tb_user 和 tb_user_copy
                      2. 使用 UPDATE INNER JOIN 查询
                      UPDATE tb_user a
                      INNER JOIN tb_user_copy b
                      ON a.id = b.id
                      SET a.first_name = b.last_name, a.last_name = b.first_name
                      

                      【讨论】:

                        【解决方案20】:

                        您可以申请以下查询,它对我来说非常适合。

                        Table name: studentname
                        only single column available: name
                        
                        
                        update studentnames 
                        set names = case names 
                        when "Tanu" then "dipan"
                        when "dipan" then "Tanu"
                        end;
                        
                        or
                        
                        update studentnames 
                        set names = case names 
                        when "Tanu" then "dipan"
                        else "Tanu"
                        end;
                        

                        【讨论】:

                          【解决方案21】:

                          如果你想将 x 为 y 和 y 的所有列交换为 x;使用这个查询。

                          UPDATE table_name SET column_name = CASE column_name WHERE 'value of col is x' THEN 'swap it to y' ELSE 'swap it to x' END;

                          【讨论】:

                            【解决方案22】:

                            让我们想象一下这张表,让我们尝试交换“sex”表中的 m 和 f:

                            id name sex salary
                            1 A m 2500
                            2 B f 1500
                            3 C m 5500
                            4 D f 500
                            UPDATE sex
                            SET sex = CASE sex
                            WHEN 'm' THEN 'f'
                            ELSE 'm'
                            END;
                            

                            所以更新后的表格变成:

                            id name sex salary
                            1 A f 2500
                            2 B m 1500
                            3 C f 5500
                            4 D m 500

                            【讨论】:

                            • 这允许在列中交换值。对于需要跨列交换它们的问题没有帮助。
                            猜你喜欢
                            • 2014-03-16
                            • 2019-06-22
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 2015-11-23
                            相关资源
                            最近更新 更多