2008-09-01 16 views
92

Mam tabelę MySQL ze współrzędnymi, nazwy kolumn to X i Y. Teraz chcę zamienić wartości kolumn w tej tabeli, tak, że X staje się Y i Y staje się X. Najbardziej oczywiste rozwiązanie będzie zmieniać nazwy kolumn, ale nie chcę wprowadzać zmian w strukturze, ponieważ nie mam do tego uprawnień.Zamiana wartości kolumn w MySQL

Czy jest to możliwe w jakiś sposób z pomocą UPDATE? Tabela UPDATE SET X = Y, Y = X oczywiście nie zrobi tego, co chcę.


Edit: Należy pamiętać, że moje ograniczenie uprawnień, o których mowa powyżej, skutecznie uniemożliwia wykorzystanie ALTER TABLE lub innych poleceń, które zmieniają strukturę tabeli/bazy danych. Zmiana nazwy kolumn lub dodawanie nowych niestety nie jest opcją.

+1

na marginesie, 'tablica UPDATE SET X = Y, Y = X' jest standardowym sposobem wykonywania w SQL, tylko MySQL źle reaguje. –

Odpowiedz

135

I just had to deal with the same and I'll summarize my findings.

  1. The UPDATE table SET X=Y, Y=X oczywiście nie działa, jak to będzie po prostu ustawić obie wartości Y.

  2. Oto metoda używająca zmiennej tymczasowej. Podziękowania dla Antoniego z komentarzy http://beerpla.net/2009/02/17/swapping-column-values-in-mysql/ za modyfikację "IS NOT NULL". Bez niego zapytanie działa nieprzewidywalnie. Zobacz schemat tabeli na końcu postu. Ta metoda nie zamienia wartości, jeśli jedna z nich ma wartość NULL. Użyj metody nr 3, która nie ma tego ograniczenia.

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

  3. Metoda ta była oferowana przez Dipin w, jeszcze raz, komentarze http://beerpla.net/2009/02/17/swapping-column-values-in-mysql/. Myślę, że to najbardziej eleganckie i czyste rozwiązanie. Działa z wartościami NULL i wartościami innymi niż NULL.

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

  4. Innym podejściem wymyśliłem, że wydaje się działać:

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

Zasadniczo 1st tabeli jest jeden uzyskiwanie aktualizowane i 2nd jeden jest używany do wyciągnij stare dane z.
Należy zauważyć, że to podejście wymaga obecności klucza podstawowego.

To jest mój schemat testowy:

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

Jak wspomniano w dokumentach MySQL, nie jest bezpiecznie przypisywać i odczytywać zmienne w pojedynczej instrukcji. Kolejność operacji nie jest gwarantowana. Więc jedyną bezpieczną metodą jest # 4 – AMIB

+0

Opcja 4 dla mnie. Oczywiście możesz dodać więcej warunków do klauzuli where, jeśli chcesz zamienić kolumny tylko na niektóre wiersze. –

+5

Wiesz, nigdy nie myślałem, że będzie praktyczne zastosowanie tego głupiego pytania do wywiadu, w którym chciałbym zamienić dwie zmienne bez użycia tymczasowego, ale tutaj jest, a dla liczb całkowitych to by faktycznie działało: update swap_test set x = x + y , y = xy, x = xy; – 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; 
Something like this?

Edit: About Greg's comment: No, this doesn't work:

 
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)

+0

Tylko dla zapisu: To * działa * w PostgreSQL, podczas gdy * nie * działa w MySQL. – str

8

UPDATE table SET X=Y, Y=X will do precisely what you want (edit: in PostgreSQL, not MySQL, see below). The values are taken from the old row and assigned to a new copy of the same row, then the old row is replaced. You do not have to resort to using a temporary table, a temporary column, or other swap tricks.

@D4V360: I see. That is shocking and unexpected. I use PostgreSQL and my answer works correctly there (I tried it). See the PostgreSQL UPDATE docs (under Parameters, expression), where it mentions that expressions on the right hand side of SET clauses explicitly use the old values of columns. I see that the corresponding MySQL UPDATE docs contain the statement "Single-table UPDATE assignments are generally evaluated from left to right" which implies the behaviour you describe.

Good to know.

+0

Dziękuję Gregowi i D4V360, dobrze znać różnice w PostgreSQL i MySQL dotyczące zachowania zapytań aktualizacji. –

+0

Podejście "x = y, y = x" działa również w Oracle, bo to jest warte. –

+2

Użyłem PostgreSQL i SET X = Y, Y = X mnie uratował :) – Anonymous

5

Ok, so just for fun, you could do this! (assuming you're swapping string values)

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) 

A nice bit of fun abusing the left-to-right evaluation process in MySQL.

Alternatively, just use XOR if they're numbers. You mentioned coordinates, so do you have lovely integer values, or complex strings?

Edit: The XOR stuff works like this by the way:

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

Assuming you have signed integers in your columns, you may need to use CAST(a^b AS SIGNED), since the result of the^operator is an unsigned 64-bit integer in MySQL.

In case it helps anyone, here's the method I used to swap the same column between two given rows:

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 

where $1 and $2 are the keys of two rows and $3 is the result of the first query.

2

This surely works! I've just needed it to swap Euro and SKK price columns. :)

UPDATE tbl SET X=Y, [email protected] where @temp:=X; 

The above will not work (ERROR 1064 (42000): You have an error in your SQL syntax)

18

Poniższy kod działa dla wszystkich scenariuszy w moim szybkiego testowania:

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

'UPDATE table swap_test'? Czy nie powinien to być 'UPDATE swap_test'? – Pang

1

Ty mógłby nazw zmiana kolumn, ale to jest bardziej włamać się.Ale bądź ostrożny żadnej indeksów, które mogą być na tych kolumnach

30

można wziąć sumę i odjąć wartość przeciwną używając X i Y

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

Oto test próbki (i to działa z liczbami ujemnymi)

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> 

Oto wymiany wykonywane

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> 

spróbować! !!

+3

Dla liczb jest to naprawdę najmodniejszy numer. –

+0

@ YourCommonSense Twoja nagroda mówi Ronaldo, kiedy w rzeczywistości jest Rolando. – Albzi

0

zamiana wartości kolumnowej, stosując jedno zapytanie

UPDATE my_table zbiór A = @ TMP = A, A = B, B = @ tmp;

Pozdrawiam ...!

+0

To jest tylko powtórzenie nr 3 [zaakceptowanej odpowiedzi] (http://stackoverflow.com/a/559291/1402846). – Pang

4

wierzę mieć zmienna pośrednia jest wymiana najlepszych praktyk w taki sposób:

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

pierwsze, działa zawsze; po drugie, działa niezależnie od typu danych.

Pomimo Zarówno

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

i

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

pracują zwykle, tylko dla liczb typu danych w drodze, i jest odpowiedzialny, aby zapobiec przepełnieniu, nie można używać XOR między podpisane i niepodpisane, nie można również użyć sumy na przepełnienie możliwości.

I

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

nie działa jeśli c1 jest 0 lub NULL lub zerowej długości ciąg czy tylko przestrzenie.

Musimy go zmienić na

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

Oto skrypty:

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) 
+0

+1 za znalezienie w końcu dobrego zastosowania do głupiego wywiadu, w którym trzeba wymienić dwie zmienne bez tymczasowego ;-) – izak

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

musiałem po prostu przenieść wartości z jednej kolumny do drugiej (jak archiwizacji) i wyzerować wartość oryginalnej kolumny.
Poniższe (nr referencyjny 3 z zaakceptowanej odpowiedzi powyżej) sprawdziło się dla mnie.

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