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

127

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

Возможно ли это каким-то образом сделать с помощью UPDATE ? UPDATE table SET X = Y, Y = X, очевидно, не будет делать то, что я хочу.


Изменить: обратите внимание, что мое ограничение разрешений, упомянутое выше, эффективно предотвращает использование ALTER TABLE или других команд, которые изменяют структуру таблицы / базы данных. К сожалению, переименование столбцов или добавление новых невозможно.

Liedman
источник
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/ за настройку «IS NOT NULL». Без него запрос работает непредсказуемо. См. Схему таблицы в конце сообщения. Этот метод не меняет местами значения, если одно из них равно 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 у меня сработал. Очевидно, вы можете добавить дополнительные условия в предложение where, если вам нужно поменять местами столбцы только для некоторых строк.
Брэд Кэмпбелл
7
Знаешь, я никогда не думал, что этот глупый вопрос на собеседовании с просьбой поменять местами две переменные без использования временной может иметь практическое применение, но вот он, и для целых чисел это действительно сработает: обновить swap_test set x = x + y, у = х, х = х;
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>

Попробуйте !!!

RolandoMySQLDBA
источник
5
Для цифр это действительно лучший вариант.
Ваш здравый смысл
Может быть проблема, если значение переполнится при добавлении?
ToolmakerSteve
@ToolmakerSteve, возможно, за TINYINTогромные деньги INT, вы правы !!!
RolandoMySQLDBA
29

Следующий код работает для всех сценариев моего быстрого тестирования:

UPDATE swap_test
   SET x=(@temp:=x), x = y, y = @temp
Dipin
источник
UPDATE table swap_test? Не должно быть UPDATE swap_test?
Pang
12

ОБНОВИТЬ таблицу SET X = Y, Y = X будет делать именно то, что вы хотите (отредактируйте: в PostgreSQL, а не в MySQL, см. Ниже). Значения берутся из старой строки и присваиваются новой копии той же строки, затем старая строка заменяется. Вам не нужно прибегать к использованию временной таблицы, временного столбца или других уловок подкачки.

@ D4V360: Понятно. Это шокирует и неожиданно. Я использую PostgreSQL, и мой ответ там работает правильно (я пробовал). См. Документацию PostgreSQL UPDATE (в разделе «Параметры», выражение), где упоминается, что выражения в правой части предложений SET явно используют старые значения столбцов. Я вижу, что соответствующие документы MySQL UPDATE содержат утверждение «Назначения UPDATE для одной таблицы обычно оцениваются слева направо», что подразумевает описанное вами поведение.

Хорошо знать.

Грег Хьюгилл
источник
Спасибо Грегу и D4V360, хорошо знать различия в PostgreSQL и MySQL в поведении запросов на обновление.
Виджай Дев,
Подход «x = y, y = x» также работает в Oracle, чего стоит.
Бурхан Али
2
Я использовал PostgreSQL и SET X = Y, Y = X спас меня :)
Anonymous
4
ИМХО этот ответ беспорядок - плохой совет с добавлением «упс, неважно». Половина его должна быть комментарием, а единственная часть оставшейся части, имеющая отношение к вопросу, - это ссылка на документы MySQL ...
Air
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)
workplaylifecycle
источник
+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)

fijter
источник
Только для записи: Это делает работу в PostgreSQL , пока он не работает в MySQL.
str
2

Это определенно работает! Мне просто нужно было поменять местами столбцы цен в евро и SKK. :)

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

Вышеуказанное не будет работать (ОШИБКА 1064 (42000): у вас есть ошибка в синтаксисе SQL)

Навфал
источник
1

Предполагая, что у вас есть целые числа со знаком в столбцах, вам может потребоваться использовать CAST (a ^ b AS SIGNED), поскольку результатом оператора ^ является 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 - результат первого запроса.

Artelius
источник
1

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

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

Могу сделать это.

отметка

MarkR
источник
1

Вы можете изменить имена столбцов, но это скорее уловка. Но будьте осторожны с любыми индексами, которые могут быть в этих столбцах.

SeanDowney
источник
1

Имя таблицы - заказчик. поля - a и b, поменяйте значение на b ;.

ОБНОВЛЕНИЕ НАБОР клиента 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

Обмен значений столбцов с помощью одного запроса

ОБНОВЛЕНИЕ my_table SET a = @ tmp: = a, a = b, b = @ tmp;

ура ...!

webizon
источник
1
Это просто повторение №3 принятого ответа .
Pang
0

Мне пришлось просто переместить значение из одного столбца в другой (например, при архивировании) и сбросить значение исходного столбца.
Ниже (ссылка № 3 из принятого ответа выше) сработало для меня.

Update MyTable set X= (@temp:= X), X = 0, Y = @temp WHERE ID= 999;
Archer1974
источник
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 при серьезной перезаписи я обнаружил, что некоторые даты начала позже, чем даты их окончания . Вниз, плохие программисты!).

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

PK были на всех 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;
Эндрю Фостер
источник
К вашему сведению: этот код обновил 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;
Танумай Саха
источник