Обмін значеннями стовпців у MySQL


127

У мене є таблиця MySQL з координатами, назви стовпців - X і Y. Тепер я хочу змінити значення стовпців у цій таблиці, щоб X став Y і Y став X. Найбільш очевидним рішенням було б перейменування стовпців, але я не хочу вносити зміни в структуру, оскільки я не обов'язково маю на це дозволи.

Чи можливо це якимось чином зробити з UPDATE ? ОНОВЛЕНА таблиця SET X = Y, Y = X очевидно не буде робити те, що я хочу.


Редагувати: Зауважте, що моє обмеження на дозволи, згадане вище, ефективно перешкоджає використанню ALTER TABLE або інших команд, що змінюють структуру таблиці / бази даних. Перейменування стовпців або додавання нових, на жаль, не є варіантами.


5
як примітка, UPDATE table SET X = Y, Y = Xце стандартний спосіб робити це в SQL, тільки в MySQL поводиться погано.
Антті Хаапала

Відповіді:


204

Мені просто довелося розібратися з тим же, і я підсумую свої висновки.

  1. UPDATE table SET X=Y, Y=XПідхід , очевидно , не працює, так як це буде просто встановити обидва значення в Y.

  2. Ось метод, який використовує тимчасову змінну. Дякую Ентоні за коментарі http://beerpla.net/2009/02/17/swapping-column-values-in-mysql/ за налаштування "НЕ НУЛЬ". Без нього запит працює непередбачувано. Дивіться схему таблиці в кінці публікації. Цей метод не замінює значення, якщо одне з них NULL. Використовуйте метод №3, який не має цього обмеження.

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

  3. Цей метод запропонував Діппін ще в коментарях http://beerpla.net/2009/02/17/swapping-column-values-in-mysql/ . Я думаю, що це найелегантніше і найчистіше рішення. Він працює як із значеннями NULL, так і не NULL.

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

  4. Ще один підхід, який я придумав, що працює:

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

По суті, перша таблиця - це та, яка оновлюється, а друга - використовується для витягування старих даних.
Зауважте, що цей підхід вимагає наявності первинного ключа.

Це моя тестова схема:

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
Як зазначається в документах MySQL, не можна безпечно призначати та читати змінні в одному операторі. Порядок операцій не гарантується. Тож єдиним безпечним методом є №4
AMIB

Варіант 4 працював на мене. Очевидно, ви можете додати більше умов до пункту де, якщо вам потрібно поміняти стовпці лише на кілька рядків.
Бред Кемпбелл

7
Знаєте, я ніколи не думав, що для цього дурного питання про інтерв'ю практичне використання буде просити поміняти дві змінні без використання тимчасової, але ось це, і для цілих чисел це насправді спрацює: оновіть swap_test set x = x + y, y = xy, x = xy;
izak

Більшість цієї відповіді - це пряма копія / вставка з beerpla.net/2009/02/17/swapping-column-values-in-mysql

17
@Jhawins Це тому, що beerpla.net - це мій блог.
Артем Русаковський

52

Ви можете взяти суму і відняти протилежне значення, використовуючи X і Y

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

Ось зразок тесту (і він працює з від’ємними числами)

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>

Ось виконується своп

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>

Спробувати !!!


5
Щодо чисел, це справді найновіший.
Твій здоровий глузд

Можливо, це буде проблемою, якщо значення переповнюється при додаванні?
ToolmakerSteve

@ToolmakerSteve, можливо, для TINYINTвеличезних цінностей INT, ви праві!
RolandoMySQLDBA

29

Наступний код працює для всіх сценаріїв мого швидкого тестування:

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

UPDATE table swap_test? Чи не повинно бути UPDATE swap_test?
Панг

12

ОНОВЛІННЯ таблиця SET X = Y, Y = X зробить саме те, що ви хочете (редагуйте: у PostgreSQL, а не в MySQL, див. Нижче). Значення беруться зі старого рядка і присвоюються новій копії цього ж рядка, потім старий рядок замінюється. Не потрібно вдаватися до використання тимчасової таблиці, тимчасової колонки чи інших прийомів заміни.

@ D4V360: Бачу. Це шокує і несподівано. Я використовую PostgreSQL, і моя відповідь там правильно працює (я спробував це). Див. Документи оновлення PostgreSQL (під Параметри, вираз), де згадується, що в виразах у правій частині пунктів SET явно використовуються старі значення стовпців. Я бачу, що відповідні документи MySQL UPDATE містять вислів "Призначення UPDATE для однієї таблиці, як правило, оцінюються зліва направо", що передбачає поведінку, яку ви описуєте.

Добре знати.


Дякую Грегу та D4V360, добре знати відмінності в PostgreSQL та MySQL щодо поведінки запитів оновлення.
Vijay Dev

Підхід "x = y, y = x" також працює в Oracle, для чого це варто.
Бурхан Алі

2
Я використав PostgreSQL і SET X = Y, Y = X врятував мене :)
Анонімний

4
ІМХО ця відповідь є безладом - додається погана порада із написом "Ой, не зважай". Половина має бути коментарем, і єдина частина решти, що стосується питання, - це посилання на документи MySQL ...
Повітря

6

Гаразд, просто для розваги, ви могли це зробити! (припустимо, що ви змінюєте рядкові значення)

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)

Приємне задоволення, зловживаючи процесом оцінки зліва направо в MySQL.

Крім того, просто використовуйте XOR, якщо вони номери. Ви згадали координати, тож у вас є прекрасні цілі значення чи складні рядки?

Редагувати: XOR речі працює так, до речі:

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

5

Я вважаю, що найкраща практика такого рівня є проміжною змінною обміну:

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

По-перше, це працює завжди; по-друге, він працює незалежно від типу даних.

Попри обидва

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

і

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

працюєте зазвичай, лише для типу даних про номер, до речі, і ви несете відповідальність за запобігання переповнення, ви не можете використовувати XOR між підписаними та неподписаними, ви також не можете використовувати суму для можливості переповнення.

І

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

не працює, якщо c1 дорівнює 0 або NULL або рядок нульової довжини або просто пробіли.

Нам потрібно це змінити

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

Ось сценарії:

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 для того, щоб нарешті знайти корисне використання для дурного питання інтерв'ю, де вам потрібно поміняти дві змінні без тимчасового ;-)
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;
Щось на зразок цього?

Редагувати: Про коментар Грега: Ні, це не працює:

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)


Тільки для запису: Це робить роботу в PostgreSQL , поки він НЕ працює в MySQL.
вул.

2

Це, безумовно, працює! Мені просто знадобилося це, щоб обміняти цінові колонки Євро та SKK. :)

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

Вищезазначене не працюватиме (ПОМИЛКА 1064 (42000): у вашому синтаксисі SQL помилка)


1

Якщо припустити, що ви підписали цілі числа у своїх стовпцях, можливо, вам доведеться використовувати CAST (a ^ b AS ЗНАЧЕНО), оскільки результатом оператора ^ є 64-бітове ціле число, яке не підписане в MySQL.

Якщо це допомагає комусь, ось метод, який я використовував для того, щоб поміняти той самий стовпець між двома заданими рядками:

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

де $ 1 і $ 2 - це ключі двох рядків, а $ 3 - результат першого запиту.


1

Я не пробував, але

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

Може це зробити.

Позначити


1

Ви можете змінити назви стовпців, але це більше хак. Але будьте обережні до будь-яких індексів, які можуть бути в цих стовпцях


1

Назва таблиці - замовник. поля a і b, поміняйте значення b на b.

ОНОВЛЕННЯ Клієнта SET a = (@ temp: = a), a = b, b = @temp

Я перевірив, що це працює нормально.


1

У SQL Server ви можете використовувати цей запит:

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


0

Мені довелося просто перемістити значення з одного стовпця в інший (як архівування) і скинути значення вихідного стовпця.
Нижче (посилання №3 із прийнятої відповіді вище) працювало для мене.

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

Цей приклад міняє start_date та end_date для записів, де дати неправильно зворотні (під час виконання ETL в основному перезаписі я знайшов деякі дати початку пізніше, ніж їх кінцеві дати. Вниз, погані програмісти!).

In situ я використовую MEDIUMINT з міркувань продуктивності (як, наприклад, Джуліанські дні, але маючи 0 кореня 1900-01-01), тому мені було добре, коли я робив умову WHERE mdu.start_date> mdu.end_date .

ПК були у всіх 3 стовпцях окремо (з експлуатаційних / індексування).

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: Цей код оновив 145/108 456 записів за 0,203 секунди. Це було одноразове завдання, тому виконання не було критичним.
Ендрю Фостер

0

Скажімо, ви хочете поміняти значення імені та прізвища в tb_user.

Найбезпечнішим було б:

  1. Скопіюйте tb_user. Так у вас буде 2 таблиці: tb_user і tb_user_copy
  2. Використовуйте запит 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

Ви можете застосувати нижче запит, він працював ідеально для мене.

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;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.