Замена значений столбцов в MySQL

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

возможно ли это сделать с обновление каким-то образом? обновить набор таблиц X=Y, Y=x очевидно, не будет делать то, что я хотеть.


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

16 ответов


мне просто пришлось иметь дело с тем же самым, и я обобщу свои выводы.

  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. этот метод был предложен Dipin in, еще раз, комментарии 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;

по существу, 1-я таблица обновляется, а 2-я используется для извлечения старых данных.
Обратите внимание, что этот подход требует наличия первичного ключа.

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

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

вы можете взять сумму и вычесть противоположное значение, используя 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>

дайте ему попробовать !!!


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

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

обновить набор таблиц X=Y, Y=x будет делать именно то, что вы хотите (edit: in PostgreSQL, а не MySQL, см. ниже). Значения берутся из старой строки и назначаются новой копии той же строки, затем старая строка заменяется. Вам не нужно прибегать к использованию временной таблицы, временного столбца или других трюков подкачки.

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

полезно знать.


хорошо, так что просто для удовольствия, вы могли бы сделать это! (предполагая, что вы меняете строковые значения)

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, если это числа. Вы упомянули координаты, так у вас есть прекрасные целочисленные значения или сложные строки?

Edit: материал XOR работает следующим образом:

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

две альтернативы 1. Использовать временную таблицу 2. Исследовать the алгоритм XOR


Что-то вроде этого?

Edit: о комментарии Грега: Нет, это не работает:

mysql> обновить тестовый набор x=y, y=x; Запрос OK, затронуты 2 строки (0.00 сек) Совпадение строк: 2 изменено: 2 предупреждения: 0

mysql> выбрать * из теста; +------+------+ / x / y | +------+------+ | 2 | 2 | | 4 | 4 | +------+------+ 2 строк в наборе (0.00 секунд)


Я считаю, что промежуточная переменная exchange является лучшей практикой таким образом:

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)

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

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

вышеуказанное не будет работать (ошибка 1064 (42000): у вас есть ошибка в синтаксисе SQL)


предполагая, что вы подписали целые числа в своих столбцах, вам может потребоваться использовать CAST(a ^ b как подписанный), так как результат оператора ^ является беззнаковым 64-разрядным целым числом в MySQL.

если это кому-то помогает, вот метод, который я использовал для замены одного столбца между двумя заданными строками:

SELECT BIT_XOR(foo) FROM table WHERE key =  OR key = 

UPDATE table SET foo = CAST(foo ^  AS SIGNED) WHERE key =  OR key = 

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


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

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

может сделать это.

Марк


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


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

обновить набор my_table a=@tmp:=a, a=b,b= @ tmp;

ура...!


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

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

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;

этот пример swaps начальная_дата и конечная_дата для записей, где даты неправильный путь (при выполнении ETL в основной переписать, я нашел некоторые старт даты позже, чем их конец даты. Долой плохих программистов!).

in situ я использую MEDIUMINTs по соображениям производительности (например, Julian days, но с корнем 0 1900-01-01), поэтому я был в порядке, выполняя условие где МДУ.дата начала> МДУ.конечная_дата.

PKs были на всех 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;