Intercambiar valores de columna en MySQL

127

Tengo una tabla MySQL con coordenadas, los nombres de columna son X e Y. Ahora quiero intercambiar los valores de columna en esta tabla, de modo que X se convierta en Y e Y se convierta en X. La solución más aparente sería renombrar las columnas, pero yo no quiero hacer cambios en la estructura ya que no necesariamente tengo permisos para hacerlo.

¿Es esto posible hacer con ACTUALIZAR de alguna manera? La tabla ACTUALIZAR SET X = Y, Y = X obviamente no hará lo que quiero.


Editar: Tenga en cuenta que mi restricción de permisos, mencionada anteriormente, impide efectivamente el uso de ALTER TABLE u otros comandos que cambian la estructura de la tabla / base de datos. Desafortunadamente, cambiar el nombre de las columnas o agregar nuevas no son opciones.

Liedman
fuente
55
como nota, UPDATE table SET X = Y, Y = Xes la forma estándar de hacerlo en SQL, solo MySQL se porta mal.
Antti Haapala

Respuestas:

204

Solo tuve que lidiar con lo mismo y resumiré mis hallazgos.

  1. El UPDATE table SET X=Y, Y=Xenfoque obviamente no funciona, ya que solo establecerá ambos valores en Y.

  2. Aquí hay un método que usa una variable temporal. Gracias a Antony por los comentarios de http://beerpla.net/2009/02/17/swapping-column-values-in-mysql/ por el ajuste "NO ES NULO". Sin ella, la consulta funciona de forma impredecible. Vea el esquema de la tabla al final de la publicación. Este método no intercambia los valores si uno de ellos es NULL. Use el método # 3 que no tiene esta limitación.

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

  3. Dipin ofreció este método, una vez más, en los comentarios de http://beerpla.net/2009/2009/02/17/swapping-column-values-in-mysql/ . Creo que es la solución más elegante y limpia. Funciona con valores NULL y no NULL.

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

  4. Otro enfoque que se me ocurrió que parece funcionar:

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

Esencialmente, la primera tabla es la que se actualiza y la segunda se usa para extraer los datos antiguos.
Tenga en cuenta que este enfoque requiere una clave principal para estar presente.

Este es mi esquema de prueba:

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);
Artem Russakovskii
fuente
25
Como se señala en los documentos de MySQL, no es seguro asignar y leer variables en una sola declaración. El orden de las operaciones no está garantizado. Entonces, el único método seguro es # 4
AMIB
La opción 4 funcionó para mí. Obviamente, puede agregar más condiciones a la cláusula where si necesita intercambiar las columnas solo por algunas filas.
Brad Campbell
77
Sabes, nunca pensé que habría un uso práctico para esa estúpida pregunta de entrevista que pedía intercambiar dos variables sin usar una temporal, pero aquí está, y para los enteros esto realmente funcionaría: actualizar swap_test set x = x + y, y = xy, x = xy;
izak
La mayor parte de esta respuesta es copiar / pegar directamente
17
@Jhawins Eso es porque beerpla.net es mi blog.
Artem Russakovskii
52

Puedes tomar la suma y restar el valor opuesto usando X e Y

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

Aquí hay una prueba de muestra (y funciona con números negativos)

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>

Aquí está el intercambio que se realiza

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>

Darle una oportunidad !!!

RolandoMySQLDBA
fuente
55
Para los números, de hecho, es el más bonito.
Su sentido común
¿Podría ser un problema si un valor se desborda cuando se agrega?
ToolmakerSteve
@ToolmakerSteve quizás por TINYINTo grandes valores de INT, ¡tienes razón!
RolandoMySQLDBA
29

El siguiente código funciona para todos los escenarios en mis pruebas rápidas:

UPDATE swap_test
   SET x=(@temp:=x), x = y, y = @temp
Dipin
fuente
UPDATE table swap_test? ¿No debería ser UPDATE swap_test?
Pang
12

ACTUALIZAR tabla SET X = Y, Y = X hará exactamente lo que desea (editar: en PostgreSQL, no MySQL, ver más abajo) Los valores se toman de la fila anterior y se asignan a una nueva copia de la misma fila, luego se reemplaza la fila anterior. No tiene que recurrir al uso de una tabla temporal, una columna temporal u otros trucos de intercambio.

@ D4V360: Ya veo. Eso es impactante e inesperado. Uso PostgreSQL y mi respuesta funciona correctamente allí (lo probé). Consulte los documentos de ACTUALIZACIÓN de PostgreSQL (en Parámetros, expresión), donde menciona que las expresiones en el lado derecho de las cláusulas SET usan explícitamente los valores antiguos de las columnas. Veo que los documentos de ACTUALIZACIÓN de MySQL correspondientes contienen la declaración "Las asignaciones de ACTUALIZACIÓN de tabla única generalmente se evalúan de izquierda a derecha", lo que implica el comportamiento que usted describe.

Bueno saber.

Greg Hewgill
fuente
Gracias Greg y D4V360, es bueno saber las diferencias en PostgreSQL y MySQL sobre el comportamiento de las consultas de actualización.
Vijay Dev
El enfoque "x = y, y = x" también funciona en Oracle, por lo que vale.
Burhan Ali
2
Usé PostgreSQL y SET X = Y, Y = X me salvó :)
Anónimo
44
En mi humilde opinión, esta respuesta es un desastre: malos consejos con "¡Vaya!" La mitad de ella debe ser un comentario y la única parte del resto que es relevante para la cuestión es el enlace a documentos de MySQL ...
Aire
6

Ok, solo por diversión, ¡podrías hacer esto! (suponiendo que está intercambiando valores de cadena)

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)

Un poco divertido abusando del proceso de evaluación de izquierda a derecha en MySQL.

Alternativamente, solo use XOR si son números. Usted mencionó las coordenadas, ¿tiene valores enteros encantadores o cadenas complejas?

Editar: Por cierto, las cosas XOR funcionan así:

update swapper set foo = foo ^ bar, bar = foo ^ bar, foo = foo ^ bar;
mercutio
fuente
5

Creo que tener una variable de intercambio intermedia es la mejor práctica de esta manera:

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

Primero, funciona siempre; segundo, funciona independientemente del tipo de datos.

A pesar de ambos

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

y

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

generalmente funcionan, solo para el tipo de datos numéricos, y es su responsabilidad evitar el desbordamiento, no puede usar XOR entre firmado y sin firmar, tampoco puede usar la suma para la posibilidad de desbordamiento.

Y

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

no funciona si c1 es 0 o NULL o cadena de longitud cero o solo espacios.

Necesitamos cambiarlo a

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

Aquí están los guiones:

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)
workplaylifecycle
fuente
+1 para finalmente encontrar un buen uso para la estúpida pregunta de la entrevista donde tienes que intercambiar dos variables sin una temporal ;-)
izak
4

Dos alternativas 1. Usar una tabla temporal 2. Investigar el algoritmo XOR

Sin cortar
fuente
4

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;
¿Algo como esto?

Editar: Sobre el comentario de Greg: No, esto no funciona:

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

mysql> update test set x=y, y=x; Query OK, 2 rows affected (0.00 sec) Rows matched: 2 Changed: 2 Warnings: 0

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

fijter
fuente
Sólo para que conste: Esto hace el trabajo en PostgreSQL pesar de que hace no funciona en MySQL.
str
2

¡Esto seguramente funciona! Solo lo necesitaba para intercambiar columnas de precios Euro y SKK. :)

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

Lo anterior no funcionará (ERROR 1064 (42000): tiene un error en su sintaxis SQL)

nawfal
fuente
1

Suponiendo que tiene enteros firmados en sus columnas, es posible que necesite usar CAST (a ^ b COMO SE FIRMÓ), ya que el resultado del operador ^ es un entero de 64 bits sin signo en MySQL.

En caso de que ayude a alguien, este es el método que utilicé para intercambiar la misma columna entre dos filas dadas:

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

donde $ 1 y $ 2 son las claves de dos filas y $ 3 es el resultado de la primera consulta.

Artelius
fuente
1

No lo he intentado pero

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

Podría hacerlo

marca

MarkR
fuente
1

Usted podría cambiar los nombres de las columnas, pero esto es más de un truco. Pero tenga cuidado con cualquier índice que pueda estar en estas columnas

SeanDowney
fuente
1

El nombre de la tabla es cliente. los campos son ayb, intercambian un valor por b ;.

ACTUALIZAR SET de cliente a = (@ temp: = a), a = b, b = @temp

Verifiqué que esto funciona bien.

Raman Singh
fuente
1

En SQL Server, puede usar esta consulta:

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

Intercambio de valores de columna mediante una sola consulta

ACTUALIZAR my_table SET a = @ tmp: = a, a = b, b = @ tmp;

salud...!

webizon
fuente
1
Esto es solo una repetición del # 3 de la respuesta aceptada .
Pang
0

Solo tenía que mover el valor de una columna a la otra (como archivar) y restablecer el valor de la columna original.
El siguiente (referencia del # 3 de la respuesta aceptada arriba) funcionó para mí.

Update MyTable set X= (@temp:= X), X = 0, Y = @temp WHERE ID= 999;
Arquero1974
fuente
0
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;
Ashutosh SIngh
fuente
0

Este ejemplo intercambia start_date y end_date por registros en los que las fechas son incorrectas (al realizar ETL en una reescritura importante, encontré algunas fechas de inicio posteriores a sus fechas de finalización . ¡Abajo, malos programadores!).

In situ, estoy usando MEDIUMINTs por razones de rendimiento (como los días julianos, pero con una raíz 0 de 1900-01-01), así que estaba bien haciendo una condición de WHERE mdu.start_date> mdu.end_date .

Las PK estaban en las 3 columnas individualmente (por razones operativas / de indexación).

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;
Andrew Foster
fuente
FYI: Este código actualizó 145 / 108,456 registros en 0.203 segundos. Era una tarea única y, por lo tanto, el rendimiento no era crítico.
Andrew Foster
0

Supongamos que desea intercambiar el valor de nombre y apellido en tb_user.

Lo más seguro sería:

  1. Copiar tb_user. Entonces tendrá 2 tablas: tb_user y tb_user_copy
  2. Utilice la consulta 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
Felix Labayen
fuente
0

Puede aplicar a continuación la consulta, funcionó perfecto para mí.

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;
Tanumay Saha
fuente