Trocando valores de coluna no MySQL

127

Eu tenho uma tabela MySQL com coordenadas, os nomes das colunas são X e Y. Agora eu quero trocar os valores das colunas nesta tabela, para que X se torne Y e Y se torne X. A solução mais aparente seria renomear as colunas, mas eu não quero fazer alterações na estrutura, pois não tenho necessariamente permissões para fazer isso.

Isso é possível de alguma forma com UPDATE ? Tabela UPDATE SET X = Y, Y = X obviamente não fará o que eu quero.


Editar: Observe que minha restrição de permissões, mencionada acima, impede efetivamente o uso de ALTER TABLE ou outros comandos que alteram a estrutura da tabela / banco de dados. Renomear colunas ou adicionar novas infelizmente não são opções.

Liedman
fonte
5
como uma observação, UPDATE table SET X = Y, Y = Xé a maneira padrão de fazer isso no SQL, apenas o MySQL se comporta mal.
Antti Haapala

Respostas:

204

Eu apenas tive que lidar com o mesmo e vou resumir minhas descobertas.

  1. A UPDATE table SET X=Y, Y=Xabordagem obviamente não funciona, pois definirá os dois valores como Y.

  2. Aqui está um método que usa uma variável temporária. Agradecimentos a Antony pelos comentários de http://beerpla.net/2009/02/17/swapping-column-values-in-mysql/ pelo ajuste "IS NOT NULL". Sem ele, a consulta funciona de forma imprevisível. Veja o esquema da tabela no final da postagem. Este método não troca os valores se um deles for NULL. Use o método nº 3 que não tem essa limitação.

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

  3. Esse método foi oferecido por Dipin nos comentários de http://beerpla.net/2009/02/17/swapping-column-values-in-mysql/ . Eu acho que é a solução mais elegante e limpa. Ele funciona com valores NULL e não NULL.

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

  4. Outra abordagem que criei parece funcionar:

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

Essencialmente, a 1ª tabela é a que está sendo atualizada e a 2ª é usada para extrair os dados antigos.
Observe que essa abordagem exige que uma chave primária esteja presente.

Este é o meu esquema de teste:

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
fonte
25
Como observado nos documentos do MySQL, não é seguro atribuir e ler variáveis ​​em uma única instrução. A ordem das operações não é garantida. Portanto, o único método seguro é # 4
AMIB
A opção 4 funcionou para mim. Obviamente, você pode adicionar mais condições à cláusula where se precisar trocar as colunas por apenas algumas linhas.
Brad Campbell
7
Sabe, eu nunca pensei que houvesse um uso prático para essa pergunta estúpida da entrevista pedindo para trocar duas variáveis ​​sem usar um temporário, mas aqui está, e para números inteiros isso realmente funcionaria: update swap_test set x = x + y, y = xy, x = xy;
Izak 15/09/14
A maioria desta resposta é copiar / colar diretamente
17
@Jhawins Isso porque beerpla.net é o meu blog.
Artem Russakovskii
52

Você pode pegar a soma e subtrair o valor oposto usando X e Y

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

Aqui está um teste de amostra (e funciona com 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>

Aqui está a troca sendo realizada

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>

De uma chance !!!

RolandoMySQLDBA
fonte
5
Para os números, é realmente um dos mais legais.
Seu senso comum
Pode ser um problema se um valor exceder o limite quando adicionar?
ToolmakerSteve
@ToolmakerSteve talvez por TINYINTou enormes válvulas de INT, você está certo !!!
RolandoMySQLDBA
29

O código a seguir funciona para todos os cenários em meus testes rápidos:

UPDATE swap_test
   SET x=(@temp:=x), x = y, y = @temp
Dipin
fonte
UPDATE table swap_test? Não deveria ser UPDATE swap_test?
Pang
12

Tabela UPDATE SET X = Y, Y = X fará exatamente o que você deseja (edite: no PostgreSQL, não no MySQL, veja abaixo). Os valores são obtidos da linha antiga e atribuídos a uma nova cópia da mesma linha e, em seguida, a linha antiga é substituída. Você não precisa usar uma tabela temporária, uma coluna temporária ou outros truques de troca.

@ D4V360: Entendo. Isso é chocante e inesperado. Eu uso o PostgreSQL e minha resposta funciona corretamente lá (eu tentei). Veja os documentos UPDATE do PostgreSQL (em Parâmetros, expressão), onde ele menciona que as expressões do lado direito das cláusulas SET usam explicitamente os valores antigos das colunas. Vejo que os documentos correspondentes do MySQL UPDATE contêm a instrução "Atribuições de tabela única UPDATE geralmente são avaliadas da esquerda para a direita", o que implica o comportamento que você descreve.

Bom saber.

Greg Hewgill
fonte
Obrigado Greg e D4V360, é bom saber as diferenças no PostgreSQL e MySQL sobre o comportamento das consultas de atualização.
Vijay Dev
A abordagem "x = y, y = x" também funciona no Oracle, pelo que vale a pena.
Burhan Ali
2
Eu costumava PostgreSQL e SET X = Y, Y = X me salvou :)
Anônimo
4
IMHO esta resposta é uma bagunça - maus conselhos com "oops esquece" anexado. Metade do que deve ser um comentário e a única parte do restante que é relevante para a questão está o link para docs MySQL ...
Air
6

Ok, apenas por diversão, você poderia fazer isso! (supondo que você esteja trocando valores de sequência)

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)

Foi divertido abusar do processo de avaliação da esquerda para a direita no MySQL.

Como alternativa, basta usar o XOR se forem números. Você mencionou coordenadas, então você tem valores inteiros adoráveis ​​ou sequências complexas?

Edit: O material XOR funciona assim a propósito:

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

Eu acredito que ter uma variável de troca intermediária é a melhor prática da seguinte maneira:

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

Primeiro, funciona sempre; segundo, funciona independentemente do tipo de dados.

Apesar de ambos

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

e

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

estão funcionando normalmente, apenas para o tipo de dados numéricos, a propósito, e é sua responsabilidade evitar o estouro, você não pode usar o XOR entre assinado e não assinado, também não pode usar soma para a possibilidade de estouro.

E

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

não funcionará se c1 for 0 ou NULL ou cadeia de comprimento zero ou apenas espaços.

Precisamos mudar para

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

Aqui estão os scripts:

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
fonte
+1 para finalmente encontrar um bom uso para a pergunta da entrevista estúpida onde você tem que trocar duas variáveis sem uma temporária ;-)
Izak
4

Duas alternativas 1. Use uma tabela temporária 2. Investigue o algoritmo XOR

Sem fatias
fonte
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 assim?

Editar: Sobre o comentário de Greg: Não, isso não 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
fonte
Apenas para o registro: Este faz o trabalho no PostgreSQL, enquanto ele não trabalhar em MySQL.
str
2

Isso certamente funciona! Eu só precisava trocar as colunas de preço em euros e SKK. :)

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

O procedimento acima não funcionará (ERRO 1064 (42000): você possui um erro na sintaxe do SQL)

nawfal
fonte
1

Supondo que você tenha assinado números inteiros em suas colunas, pode ser necessário usar CAST (a ^ b AS SIGNED), pois o resultado do operador ^ é um número inteiro de 64 bits não assinado no MySQL.

Caso isso ajude alguém, aqui está o método que eu usei para trocar a mesma coluna entre duas linhas:

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

onde $ 1 e $ 2 são as chaves de duas linhas e $ 3 é o resultado da primeira consulta.

Artelius
fonte
1

Eu não tentei, mas

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

Pode fazer isso.

Marca

MarkR
fonte
1

Você pode alterar os nomes das colunas, mas isso é mais um truque. Mas tenha cuidado com os índices que possam estar nessas colunas

SeanDowney
fonte
1

O nome da tabela é cliente. os campos são aeb, troque um valor por b ;.

UPDATE customer SET a = (@ temp: = a), a = b, b = @temp

Eu verifiquei que está funcionando bem.

Raman Singh
fonte
1

No SQL Server, você pode usar esta consulta:

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

Troca de valores de coluna usando consulta única

ATUALIZAÇÃO my_table SET a = @ tmp: = a, a = b, b = @ tmp;

Felicidades...!

webizon
fonte
1
Esta é apenas uma repetição do nº 3 da resposta aceita .
Pang
0

Eu tive que apenas mover o valor de uma coluna para outra (como arquivar) e redefinir o valor da coluna original.
O abaixo (referência nº 3 da resposta aceita acima) funcionou para mim.

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

Este exemplo troca start_date e end_date por registros em que as datas são inversas (ao executar ETL em uma reescrita principal, encontrei algumas datas de início posteriores ao final datas de . Inoperantes, maus programadores!).

No local, estou usando MEDIUMINTs por motivos de desempenho (como dias Julianos, mas com uma raiz 0 de 1900-01-01), então eu estava bem fazendo uma condição de WHERE mdu.start_date> mdu.end_date .

As PKs estavam em todas as 3 colunas individualmente (por razões operacionais / de indexação).

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
fonte
FYI: Este código atualizou 145 / 108.456 registros em 0,203 segundos. Era uma tarefa única e, portanto, o desempenho não era crítico.
Andrew Foster
0

Digamos que você queira trocar o valor do nome e do sobrenome em tb_user.

O mais seguro seria:

  1. Copie tb_user. Então você terá 2 tabelas: tb_user e tb_user_copy
  2. Use a 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
fonte
0

Você pode aplicar a consulta abaixo, funcionou perfeito para mim.

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
fonte