Scambio di valori di colonna in MySQL


127

Ho una tabella MySQL con coordinate, i nomi delle colonne sono X e Y. Ora voglio scambiare i valori delle colonne in questa tabella, in modo che X diventi Y e Y diventi X. La soluzione più evidente sarebbe rinominare le colonne, ma io non voglio apportare modifiche alla struttura poiché non ho necessariamente i permessi per farlo.

Questo è possibile con UPDATE in qualche modo? UPDATE table SET X = Y, Y = X ovviamente non farà quello che voglio.


Modifica: si noti che la mia limitazione delle autorizzazioni, menzionata sopra, impedisce efficacemente l'uso di ALTER TABLE o altri comandi che modificano la struttura della tabella / database. Purtroppo, rinominare le colonne o aggiungerne di nuove non è un'opzione.


5
come nota, UPDATE table SET X = Y, Y = Xè il modo standard di farlo in SQL, solo MySQL si comporta male.
Antti Haapala,

Risposte:


204

Ho dovuto fare i conti con lo stesso e riassumerò le mie scoperte.

  1. L' UPDATE table SET X=Y, Y=Xapproccio ovviamente non funziona, poiché imposterà entrambi i valori su Y.

  2. Ecco un metodo che utilizza una variabile temporanea. Grazie ad Antony dai commenti di http://beerpla.net/2009/02/17/swapping-column-values-in-mysql/ per il tweak "IS NOT NULL". Senza di essa, la query funziona in modo imprevedibile. Vedi lo schema della tabella alla fine del post. Questo metodo non scambia i valori se uno di essi è NULL. Utilizzare il metodo n. 3 che non presenta questa limitazione.

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

  3. Questo metodo è stato offerto da Dipin, ancora una volta, nei commenti di http://beerpla.net/2009/02/17/swapping-column-values-in-mysql/ . Penso che sia la soluzione più elegante e pulita. Funziona con valori NULL e non NULL.

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

  4. Un altro approccio che mi è venuto in mente sembra funzionare:

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

In sostanza, la prima tabella è quella che viene aggiornata e la seconda viene utilizzata per estrarre i vecchi dati.
Si noti che questo approccio richiede la presenza di una chiave primaria.

Questo è il mio schema di test:

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);

25
Come notato nei documenti MySQL, non è sicuro assegnare e leggere le variabili in una singola istruzione. L'ordine delle operazioni non è garantito. Quindi l'unico metodo sicuro è # 4
AMIB,

L'opzione 4 ha funzionato per me. È ovviamente possibile aggiungere più condizioni alla clausola where se è necessario scambiare le colonne solo con alcune righe.
Brad Campbell

7
Sai, non avrei mai pensato che ci sarebbe stato un uso pratico per quella stupida domanda di intervista che chiedeva di scambiare due variabili senza usare un temporaneo, ma eccolo qui, e per i numeri interi funzionerebbe davvero: update swap_test set x = x + y, y = xy, x = xy;
izak,

La maggior parte di questa risposta è diretta copia / incolla da beerpla.net/2009/02/17/swapping-column-values-in-mysql

17
@Jhawins Questo perché beerpla.net è il mio blog.
Artem Russakovskii,

52

Puoi prendere la somma e sottrarre il valore opposto usando X e Y

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

Ecco un test di esempio (e funziona con numeri negativi)

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>

Ecco lo swap in corso

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>

Provaci !!!


5
Per i numeri è davvero uno più pulito.
Il tuo senso comune

Potrebbe essere un problema se un valore trabocca quando si aggiunge?
ToolmakerSteve

@ToolmakerSteve forse per TINYINTo enormi valori di INT, hai ragione !!!
RolandoMySQLDBA

29

Il seguente codice funziona per tutti gli scenari nei miei test rapidi:

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

UPDATE table swap_test? Non dovrebbe essere UPDATE swap_test?
Pang

12

Tabella AGGIORNAMENTO SET X = Y, Y = X farà esattamente quello che vuoi (modifica: in PostgreSQL, non MySQL, vedi sotto). I valori vengono presi dalla vecchia riga e assegnati a una nuova copia della stessa riga, quindi la vecchia riga viene sostituita. Non è necessario ricorrere all'uso di una tabella temporanea, una colonna temporanea o altri trucchi di scambio.

@ D4V360: vedo. Questo è scioccante e inaspettato. Uso PostgreSQL e la mia risposta funziona correttamente lì (l'ho provato). Vedi i documenti PostgreSQL UPDATE (sotto Parametri, espressione), dove menziona che le espressioni sul lato destro delle clausole SET usano esplicitamente i vecchi valori delle colonne. Vedo che i corrispondenti documenti UPDATE di MySQL contengono l'istruzione "Le assegnazioni UPDATE a tabella singola vengono generalmente valutate da sinistra a destra", il che implica il comportamento descritto.

Buono a sapersi.


Grazie Greg e D4V360, è utile conoscere le differenze tra PostgreSQL e MySQL sul comportamento delle query di aggiornamento.
Vijay Dev,

L'approccio "x = y, y = x" funziona anche in Oracle, per quello che vale.
Burhan Ali,

2
Ho usato PostgreSQL e SET X = Y, Y = X mi ha salvato :)
Anonimo il

4
IMHO questa risposta è un casino - cattivi consigli con "oops non importa" aggiunto. La metà dovrebbe essere un commento e l'unica parte del resto rilevante per la domanda è il collegamento ai documenti MySQL ...
Air

6

Ok, quindi solo per divertimento, potresti farlo! (supponendo che stai scambiando i valori di stringa)

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 bel po 'di divertimento abusando del processo di valutazione da sinistra a destra in MySQL.

In alternativa, usa XOR solo se sono numeri. Hai citato le coordinate, quindi hai valori interi adorabili o stringhe complesse?

Modifica: Le cose XOR funzionano così a proposito:

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

5

Credo che avere una variabile di scambio intermedia sia la migliore pratica in questo modo:

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

Innanzitutto funziona sempre; secondo, funziona indipendentemente dal tipo di dati.

Nonostante entrambi

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

di solito funzionano, solo per tipo di dati numerici ed è responsabilità dell'utente impedire l'overflow, non è possibile utilizzare XOR tra firmato e non firmato, inoltre non è possibile utilizzare la somma per possibilità di overflow.

E

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

non funziona se c1 è 0 o NULL o stringa di lunghezza zero o solo spazi.

Dobbiamo cambiarlo in

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

Ecco gli script:

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 per trovare finalmente un buon uso per la stupida domanda di intervista in cui devi scambiare due variabili senza un temporaneo ;-)
izak


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;
Qualcosa come questo?

Modifica: Informazioni sul commento di Greg: No, questo non funziona:

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)


Solo per la cronaca: Questo fa il lavoro in PostgreSQL mentre non non funziona in MySQL.
str

2

Questo sicuramente funziona! Ne ho solo bisogno per scambiare le colonne dei prezzi Euro e SKK. :)

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

Quanto sopra non funzionerà (ERRORE 1064 (42000): si è verificato un errore nella sintassi SQL)


1

Supponendo di avere numeri interi con segno nelle colonne, potrebbe essere necessario utilizzare CAST (a ^ b COME FIRMATO), poiché il risultato dell'operatore ^ è un numero intero a 64 bit senza segno in MySQL.

Nel caso in cui aiuti qualcuno, ecco il metodo che ho usato per scambiare la stessa colonna tra due righe date:

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

dove $ 1 e $ 2 sono le chiavi di due righe e $ 3 è il risultato della prima query.


1

Non l'ho provato ma

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

Potrebbe farlo.

marchio


1

Si potrebbe cambiare i nomi delle colonne, ma questo è più di un hack. Ma fai attenzione agli indici che potrebbero trovarsi su queste colonne


1

Il nome della tabella è cliente. i campi sono aeb, scambiare un valore in b ;.

AGGIORNA cliente SET a = (@ temp: = a), a = b, b = @temp

Ho verificato che funzioni correttamente.


1

In SQL Server, è possibile utilizzare questa query:

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

0

Scambio di valori di colonna usando una singola query

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

Saluti...!


1
Questa è solo una ripetizione del numero 3 della risposta accettata .
Pang

0

Ho dovuto spostare il valore da una colonna all'altra (come l'archiviazione) e ripristinare il valore della colonna originale.
Di seguito (riferimento al n. 3 della risposta accettata sopra) ha funzionato per me.

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

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;

0

Questo esempio swap START_DATE e END_DATE per i record in cui sono le date del round modo sbagliato (durante l'esecuzione di ETL in una grande riscrittura, ho trovato alcune inizio date più tardi la loro fine date. Giù, cattivi programmatori!).

In situ, sto usando MEDIUMINTs per motivi di performance (come i giorni di Julian, ma con una radice 0 di 1900-01-01), quindi stavo bene facendo una condizione WHERE mdu.start_date> mdu.end_date .

I PK erano su tutte e 3 le colonne individualmente (per motivi operativi / di indicizzazione).

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;

FYI: questo codice ha aggiornato 145 / 108.456 record in 0,203 secondi. Era un compito unico e quindi le prestazioni non erano critiche.
Andrew Foster,

0

Supponiamo che tu voglia scambiare il valore di nome e cognome in tb_user.

Il più sicuro sarebbe:

  1. Copia tb_user. Quindi avrai 2 tabelle: tb_user e tb_user_copy
  2. Utilizzare la query 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

0

È possibile applicare sotto query, ha funzionato perfettamente per me.

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;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.