Permutation des valeurs de colonne dans MySQL

127

J'ai une table MySQL avec des coordonnées, les noms de colonne sont X et Y. Maintenant, je veux échanger les valeurs de colonne dans cette table, de sorte que X devienne Y et Y devienne X. La solution la plus apparente serait de renommer les colonnes, mais je Je ne veux pas faire de changements de structure car je n'ai pas nécessairement les autorisations pour le faire.

Est-ce possible de faire avec UPDATE d'une manière ou d'une autre? UPDATE table SET X = Y, Y = X ne fera évidemment pas ce que je veux.


Edit: Veuillez noter que ma restriction sur les autorisations, mentionnée ci-dessus, empêche efficacement l'utilisation d'ALTER TABLE ou d'autres commandes qui modifient la structure de la table / base de données. Renommer les colonnes ou en ajouter de nouvelles ne sont malheureusement pas des options.

Liedman
la source
5
à noter, UPDATE table SET X = Y, Y = Xc'est la manière standard de le faire en SQL, seul MySQL se comporte mal.
Antti Haapala

Réponses:

204

J'ai juste eu à traiter de la même chose et je vais résumer mes conclusions.

  1. L' UPDATE table SET X=Y, Y=Xapproche ne fonctionne évidemment pas, car elle définira simplement les deux valeurs sur Y.

  2. Voici une méthode qui utilise une variable temporaire. Merci à Antony pour les commentaires de http://beerpla.net/2009/02/17/swapping-column-values-in-mysql/ pour le tweak "IS NOT NULL". Sans cela, la requête fonctionne de manière imprévisible. Voir le schéma du tableau à la fin de l'article. Cette méthode n'échange pas les valeurs si l'une d'elles est NULL. Utilisez la méthode n ° 3 qui n'a pas cette limitation.

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

  3. Cette méthode a été proposée par Dipin dans, encore une fois, les commentaires de http://beerpla.net/2009/02/17/swapping-column-values-in-mysql/ . Je pense que c'est la solution la plus élégante et la plus propre. Il fonctionne avec les valeurs NULL et non NULL.

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

  4. Une autre approche que j'ai proposée et qui semble fonctionner:

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

Essentiellement, la première table est celle qui est mise à jour et la deuxième est utilisée pour extraire les anciennes données.
Notez que cette approche nécessite la présence d'une clé primaire.

Voici mon schéma de 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);
Artem Russakovskii
la source
25
Comme indiqué dans la documentation MySQL, il n'est pas sûr d'attribuer et de lire des variables dans une seule instruction. L'ordre des opérations n'est pas garanti. La seule méthode sûre est donc la # 4
AMIB
L'option 4 a fonctionné pour moi. Vous pouvez évidemment ajouter plus de conditions à la clause where si vous devez échanger les colonnes pour seulement quelques lignes.
Brad Campbell
7
Vous savez, je n'ai jamais pensé qu'il y aurait une utilisation pratique pour cette stupide question d'entrevue demandant d'échanger deux variables sans utiliser de temporaire, mais la voici, et pour les entiers, cela fonctionnerait réellement: mettez à jour swap_test set x = x + y, y = xy, x = xy;
izak
La plupart de cette réponse est un copier / coller direct de beerpla.net/2009/02/17/swapping-column-values-in-mysql
17
@Jhawins C'est parce que beerpla.net est mon blog.
Artem Russakovskii
52

Vous pouvez prendre la somme et soustraire la valeur opposée en utilisant X et Y

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

Voici un exemple de test (et cela fonctionne avec des nombres négatifs)

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>

Voici le swap en cours

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>

Essaie !!!

RolandoMySQLDBA
la source
5
Pour les chiffres, c'est en effet un des plus nets.
Votre bon sens
Peut-être un problème si une valeur déborde lors de l'ajout?
ToolmakerSteve
@ToolmakerSteve peut-être pour TINYINTou d'énormes valeurs de INT, vous avez raison !!!
RolandoMySQLDBA
29

Le code suivant fonctionne pour tous les scénarios de mes tests rapides:

UPDATE swap_test
   SET x=(@temp:=x), x = y, y = @temp
Dipin
la source
UPDATE table swap_test? Cela ne devrait-il pas être le cas UPDATE swap_test?
Pang le
12

UPDATE table SET X = Y, Y = X fera exactement ce que vous voulez (éditer: dans PostgreSQL, pas MySQL, voir ci-dessous). Les valeurs sont extraites de l'ancienne ligne et affectées à une nouvelle copie de la même ligne, puis l'ancienne ligne est remplacée. Vous n'avez pas besoin d'utiliser une table temporaire, une colonne temporaire ou d'autres astuces d'échange.

@ D4V360: Je vois. C'est choquant et inattendu. J'utilise PostgreSQL et ma réponse y fonctionne correctement (je l'ai essayé). Consultez la documentation de PostgreSQL UPDATE (sous Paramètres, expression), où elle mentionne que les expressions à droite des clauses SET utilisent explicitement les anciennes valeurs des colonnes. Je vois que les documents MySQL UPDATE correspondants contiennent la déclaration "Les affectations UPDATE à table unique sont généralement évaluées de gauche à droite", ce qui implique le comportement que vous décrivez.

Bon à savoir.

Greg Hewgill
la source
Merci Greg et D4V360, c'est bon de connaître les différences entre PostgreSQL et MySQL sur le comportement des requêtes de mise à jour.
Vijay Dev
L'approche "x = y, y = x" fonctionne également dans Oracle, pour ce qu'elle vaut.
Burhan Ali
2
J'ai utilisé PostgreSQL et SET X = Y, Y = X m'a sauvé :)
Anonyme
4
À mon humble avis, cette réponse est un gâchis - de mauvais conseils avec "oups, peu importe" en annexe. La moitié devrait être un commentaire et la seule partie du reste qui soit pertinente pour la question est le lien vers la documentation MySQL ...
Air
6

Ok, donc juste pour le plaisir, tu peux faire ça! (en supposant que vous permutez les valeurs de chaîne)

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 peu de plaisir en abusant du processus d'évaluation de gauche à droite dans MySQL.

Sinon, utilisez simplement XOR s'il s'agit de nombres. Vous avez mentionné les coordonnées, alors avez-vous de jolies valeurs entières ou des chaînes complexes?

Edit: Le truc XOR fonctionne comme ça au fait:

update swapper set foo = foo ^ bar, bar = foo ^ bar, foo = foo ^ bar;
mercutio
la source
5

Je pense qu'avoir une variable d'échange intermédiaire est la meilleure pratique de cette manière:

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

Premièrement, cela fonctionne toujours; deuxièmement, cela fonctionne quel que soit le type de données.

Malgré les deux

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

et

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

fonctionnent généralement, uniquement pour le type de données numériques en passant, et il est de votre responsabilité d'éviter le débordement, vous ne pouvez pas utiliser XOR entre signé et non signé, vous ne pouvez pas non plus utiliser la somme pour la possibilité de débordement.

Et

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

ne fonctionne pas si c1 est 0 ou NULL ou une chaîne de longueur nulle ou juste des espaces.

Nous devons le changer pour

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

Voici les 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)
cycle de travail
la source
+1 pour enfin trouver une bonne utilisation de la question d'entrevue stupide où vous devez échanger deux variables sans temporaire ;-)
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;
Quelque chose comme ça?

Edit: À propos du commentaire de Greg: Non, cela ne fonctionne pas:

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
la source
Pour mémoire: cela fonctionne dans PostgreSQL alors que cela ne fonctionne pas dans MySQL.
str
2

Cela fonctionne sûrement! J'en ai juste besoin pour échanger les colonnes de prix Euro et SKK. :)

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

Ce qui précède ne fonctionnera pas (ERREUR 1064 (42000): vous avez une erreur dans votre syntaxe SQL)

nawfal
la source
1

En supposant que vous ayez des entiers signés dans vos colonnes, vous devrez peut-être utiliser CAST (a ^ b AS SIGNED), car le résultat de l'opérateur ^ est un entier 64 bits non signé dans MySQL.

Au cas où cela aiderait quelqu'un, voici la méthode que j'ai utilisée pour permuter la même colonne entre deux lignes données:

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

où $ 1 et $ 2 sont les clés de deux lignes et $ 3 est le résultat de la première requête.

Artelius
la source
1

Je n'ai pas essayé mais

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

Pourrait le faire.

marque

MarkR
la source
1

Vous pouvez changer les noms de colonne, mais c'est plus un hack. Mais méfiez-vous des index qui peuvent être sur ces colonnes

SeanDowney
la source
1

Le nom de la table est client. les champs sont a et b, permutez une valeur à b ;.

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

J'ai vérifié que cela fonctionnait bien.

Raman Singh
la source
1

Dans SQL Server, vous pouvez utiliser cette requête:

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

Échange de valeurs de colonne à l'aide d'une seule requête

METTRE À JOUR ma_table SET a = @ tmp: = a, a = b, b = @ tmp;

à votre santé...!

webizon
la source
1
Ceci est juste une répétition de # 3 de la réponse acceptée .
Pang le
0

Je devais simplement déplacer la valeur d'une colonne à l'autre (comme l'archivage) et réinitialiser la valeur de la colonne d'origine.
Le ci-dessous (référence de # 3 de la réponse acceptée ci-dessus) a fonctionné pour moi.

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

Cet exemple permute start_date et end_date pour les enregistrements où les dates sont dans le mauvais sens (lors de l'exécution d'ETL dans une réécriture majeure, j'ai trouvé des dates de début postérieures à leurs dates de fin . Down, mauvais programmeurs!).

In situ, j'utilise des MEDIUMINT pour des raisons de performances (comme les jours juliens, mais avec une racine 0 de 1900-01-01), donc j'étais d'accord pour faire une condition de WHERE mdu.start_date> mdu.end_date .

Les PK se trouvaient sur les 3 colonnes individuellement (pour des raisons opérationnelles / d'indexation).

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
la source
FYI: Ce code a mis à jour 145/108 456 enregistrements en 0,203 secondes. C'était une tâche ponctuelle et donc la performance n'était pas critique.
Andrew Foster
0

Supposons que vous souhaitiez échanger la valeur du prénom et du nom dans tb_user.

Le plus sûr serait:

  1. Copiez tb_user. Vous aurez donc 2 tables: tb_user et tb_user_copy
  2. Utiliser la requête 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
Félix Labayen
la source
0

Vous pouvez postuler ci-dessous, cela a fonctionné parfaitement pour moi.

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
la source