Spaltenwerte in MySQL austauschen

127

Ich habe eine MySQL-Tabelle mit Koordinaten, die Spaltennamen sind X und Y. Jetzt möchte ich die Spaltenwerte in dieser Tabelle vertauschen, sodass X zu Y und Y zu X wird. Die naheliegendste Lösung wäre, die Spalten umzubenennen, aber ich Ich möchte keine Strukturänderungen vornehmen, da ich nicht unbedingt die Berechtigung dazu habe.

Ist dies in irgendeiner Weise mit UPDATE möglich ? UPDATE-Tabelle SET X = Y, Y = X macht offensichtlich nicht das, was ich will.


Bearbeiten: Bitte beachten Sie, dass meine oben erwähnte Einschränkung der Berechtigungen die Verwendung von ALTER TABLE oder anderen Befehlen, die die Tabellen- / Datenbankstruktur ändern, effektiv verhindert. Das Umbenennen oder Hinzufügen neuer Spalten ist leider keine Option.

Liedman
quelle
5
Hinweis: UPDATE table SET X = Y, Y = XIst die Standardmethode in SQL, nur MySQL verhält sich schlecht.
Antti Haapala

Antworten:

204

Ich musste mich nur damit befassen und werde meine Ergebnisse zusammenfassen.

  1. Der UPDATE table SET X=Y, Y=XAnsatz funktioniert offensichtlich nicht, da nur beide Werte auf Y gesetzt werden.

  2. Hier ist eine Methode, die eine temporäre Variable verwendet. Vielen Dank an Antony aus den Kommentaren von http://beerpla.net/2009/02/17/swapping-column-values-in-mysql/ für die Optimierung "IS NOT NULL". Ohne sie funktioniert die Abfrage unvorhersehbar. Siehe das Tabellenschema am Ende des Beitrags. Diese Methode tauscht die Werte nicht aus, wenn einer von ihnen NULL ist. Verwenden Sie Methode 3, die diese Einschränkung nicht aufweist.

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

  3. Diese Methode wurde von Dipin erneut in den Kommentaren von http://beerpla.net/2009/02/17/swapping-column-values-in-mysql/ angeboten . Ich denke, es ist die eleganteste und sauberste Lösung. Es funktioniert sowohl mit NULL- als auch mit Nicht-NULL-Werten.

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

  4. Ein anderer Ansatz, den ich mir ausgedacht habe, scheint zu funktionieren:

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

Im Wesentlichen wird die 1. Tabelle aktualisiert und die 2. Tabelle wird zum Abrufen der alten Daten verwendet.
Beachten Sie, dass für diesen Ansatz ein Primärschlüssel erforderlich sein muss.

Dies ist mein Testschema:

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
quelle
25
Wie in MySQL-Dokumenten erwähnt, ist es nicht sicher, Variablen in einer einzigen Anweisung zuzuweisen und zu lesen. Die Reihenfolge der Operationen ist nicht garantiert. Die einzig sichere Methode ist also # 4
AMIB
Option 4 hat bei mir funktioniert. Sie können der where-Klausel natürlich weitere Bedingungen hinzufügen, wenn Sie die Spalten nur für einige Zeilen austauschen müssen.
Brad Campbell
7
Weißt du, ich hätte nie gedacht, dass es eine praktische Verwendung für diese dumme Interviewfrage geben würde, bei der zwei Variablen ausgetauscht werden sollen, ohne eine temporäre zu verwenden, aber hier ist es, und für ganze Zahlen würde dies tatsächlich funktionieren: update swap_test set x = x + y, y = xy, x = xy;
Izak
Der größte Teil dieser Antwort ist direktes Kopieren / Einfügen von winepla.net/2009/02/17/swapping-column-values-in-mysql
17
@Jhawins Das liegt daran, dass Beerpla.net mein Blog ist.
Artem Russakovskii
52

Sie können die Summe nehmen und den entgegengesetzten Wert mit X und Y subtrahieren

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

Hier ist ein Beispieltest (und er funktioniert mit negativen Zahlen)

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>

Hier wird der Tausch durchgeführt

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>

Versuche es !!!

RolandoMySQLDBA
quelle
5
Für die Zahlen ist es in der Tat eine ordentliche.
Ihr gesunder Menschenverstand
Könnte ein Problem sein, wenn ein Wert beim Hinzufügen überläuft?
ToolmakerSteve
@ToolmakerSteve vielleicht für TINYINToder riesige Valules von INT, du hast recht !!!
RolandoMySQLDBA
29

Der folgende Code funktioniert für alle Szenarien in meinen Schnelltests:

UPDATE swap_test
   SET x=(@temp:=x), x = y, y = @temp
Eintauchen
quelle
UPDATE table swap_test? Sollte es nicht sein UPDATE swap_test?
Pang
12

UPDATE-Tabelle SET X = Y, Y = X. macht genau das, was Sie wollen (bearbeiten: in PostgreSQL, nicht in MySQL, siehe unten). Die Werte werden aus der alten Zeile übernommen und einer neuen Kopie derselben Zeile zugewiesen. Anschließend wird die alte Zeile ersetzt. Sie müssen nicht auf eine temporäre Tabelle, eine temporäre Spalte oder andere Swap-Tricks zurückgreifen.

@ D4V360: Ich verstehe. Das ist schockierend und unerwartet. Ich benutze PostgreSQL und meine Antwort funktioniert dort korrekt (ich habe es versucht). In den PostgreSQL UPDATE-Dokumenten (unter Parameter, Ausdruck) wird erwähnt, dass Ausdrücke auf der rechten Seite der SET-Klauseln explizit die alten Werte von Spalten verwenden. Ich sehe, dass die entsprechenden MySQL UPDATE-Dokumente die Anweisung "UPDATE-Zuweisungen für einzelne Tabellen werden im Allgemeinen von links nach rechts ausgewertet" enthalten, die das von Ihnen beschriebene Verhalten impliziert.

Gut zu wissen.

Greg Hewgill
quelle
Vielen Dank an Greg und D4V360, gut zu wissen, welche Unterschiede in PostgreSQL und MySQL hinsichtlich des Verhaltens der Update-Abfragen bestehen.
Vijay Dev
Der Ansatz "x = y, y = x" funktioniert auch in Oracle, was es wert ist.
Burhan Ali
2
Ich habe PostgreSQL verwendet und SET X = Y, Y = X hat mich gerettet :)
Anonym
4
IMHO ist diese Antwort ein Chaos - schlechter Rat mit "oops egal" angehängt. Die Hälfte davon sollte ein Kommentar sein und der einzige Teil des Restes, der für die Frage relevant ist, ist der Link zu MySQL-Dokumenten ...
Air
6

Ok, nur zum Spaß kannst du das machen! (vorausgesetzt, Sie tauschen Zeichenfolgenwerte aus)

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)

Ein bisschen Spaß beim Missbrauch des Evaluierungsprozesses von links nach rechts in MySQL.

Alternativ können Sie auch einfach XOR verwenden, wenn es sich um Zahlen handelt. Sie haben Koordinaten erwähnt. Haben Sie also schöne ganzzahlige Werte oder komplexe Zeichenfolgen?

Edit: Das XOR-Zeug funktioniert übrigens so:

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

Ich glaube, eine Zwischenaustauschvariable ist die beste Vorgehensweise auf diese Weise:

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

Erstens funktioniert es immer; Zweitens funktioniert es unabhängig vom Datentyp.

Trotz beidem

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

und

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

arbeiten normalerweise nur für den Datentyp Nummer, und es liegt in Ihrer Verantwortung, einen Überlauf zu verhindern. Sie können XOR nicht zwischen vorzeichenbehaftet und vorzeichenlos verwenden. Sie können auch keine Summe für die Überlaufmöglichkeit verwenden.

Und

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

funktioniert nicht, wenn c1 0 oder NULL oder eine Zeichenfolge mit der Länge Null oder nur Leerzeichen ist.

Wir müssen es ändern

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

Hier sind die Skripte:

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
quelle
+1 für endlich eine gute Verwendung für die dumme Interviewfrage zu finden, wo Sie zwei Variablen ohne eine temporäre
austauschen müssen ;-)
4

Zwei Alternativen 1. Verwenden Sie eine temporäre Tabelle. 2. Untersuchen Sie den XOR-Algorithmus

Ungeschnitten
quelle
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;
Etwas wie das?

Edit: Über Gregs Kommentar: Nein, das funktioniert nicht:

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
quelle
Nur zur Veranschaulichung: Dies funktioniert in PostgreSQL, während es in MySQL nicht funktioniert.
Str
2

Das funktioniert sicher! Ich habe es nur gebraucht, um Euro- und SKK-Preisspalten zu tauschen. :) :)

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

Dies funktioniert nicht (FEHLER 1064 (42000): Sie haben einen Fehler in Ihrer SQL-Syntax).

nawfal
quelle
1

Angenommen, Sie haben Ganzzahlen in Ihren Spalten signiert, müssen Sie möglicherweise CAST (a ^ b AS SIGNED) verwenden, da das Ergebnis des Operators ^ eine vorzeichenlose 64-Bit-Ganzzahl in MySQL ist.

Falls es jemandem hilft, hier ist die Methode, mit der ich dieselbe Spalte zwischen zwei angegebenen Zeilen ausgetauscht habe:

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

Dabei sind $ 1 und $ 2 die Schlüssel zweier Zeilen und $ 3 das Ergebnis der ersten Abfrage.

Artelius
quelle
1

Ich habe es aber nicht versucht

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

Könnte es tun.

Kennzeichen

MarkR
quelle
1

Sie könnten Spaltennamen ändern, aber dies ist eher ein Hack. Seien Sie jedoch vorsichtig mit Indizes, die sich möglicherweise in diesen Spalten befinden

SeanDowney
quelle
1

Tabellenname ist Kunde. Felder sind a und b, tauschen Sie einen Wert gegen b;.

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

Ich habe überprüft, ob dies gut funktioniert.

Raman Singh
quelle
1

In SQL Server können Sie diese Abfrage verwenden:

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

Vertauschen von Spaltenwerten mit einer einzelnen Abfrage

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

Prost...!

webizon
quelle
1
Dies ist nur eine Wiederholung von # 3 der akzeptierten Antwort .
Pang
0

Ich musste nur den Wert von einer Spalte in die andere verschieben (wie beim Archivieren) und den Wert der ursprünglichen Spalte zurücksetzen.
Das Folgende (Referenz von # 3 aus der oben akzeptierten Antwort) hat für mich funktioniert.

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

In diesem Beispiel werden start_date und end_date gegen Datensätze ausgetauscht, bei denen die Daten falsch herum sind (bei der Durchführung einer ETL-Umschreibung wurden einige Startdaten später als am Ende gefunden Termine. Runter, schlechte Programmierer!).

In situ verwende ich MEDIUMINTs aus Leistungsgründen (wie julianische Tage, aber mit einer 0-Wurzel von 1900-01-01), daher war es in Ordnung, eine Bedingung von WHERE mdu.start_date> mdu.end_date auszuführen .

Die PKs befanden sich in allen drei Spalten einzeln (aus betrieblichen / indizierenden Gründen).

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
quelle
Zu Ihrer Information: Dieser Code hat 145 / 108.456 Datensätze in 0,203 Sekunden aktualisiert. Es war eine einmalige Aufgabe und daher war die Leistung nicht kritisch.
Andrew Foster
0

Angenommen, Sie möchten den Wert von Vor- und Nachname in tb_user austauschen.

Das sicherste wäre:

  1. Kopieren Sie tb_user. Sie haben also 2 Tabellen: tb_user und tb_user_copy
  2. Verwenden Sie die Abfrage 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
quelle
0

Sie können unten Abfrage anwenden, es hat perfekt für mich funktioniert.

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
quelle