Обмен значениями столбца в MySQL - PullRequest
115 голосов
/ 01 сентября 2008

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

Возможно ли это как-то сделать с ОБНОВЛЕНИЕМ ? ОБНОВЛЕНИЕ таблицы SET X = Y, Y = X , очевидно, не будет делать то, что я хочу.


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

Ответы [ 17 ]

178 голосов
/ 18 февраля 2009

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

  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);
46 голосов
/ 01 августа 2012

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

Дай попробовать !!!

20 голосов
/ 18 февраля 2009

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

UPDATE table swap_test
   SET x=(@temp:=x), x = y, y = @temp
10 голосов
/ 01 сентября 2008

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

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

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

5 голосов
/ 01 сентября 2008

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

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;
4 голосов
/ 01 сентября 2008

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

4 голосов
/ 01 сентября 2008

<pre> 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; Как то так?

Редактировать: О комментарии Грега: Нет, это не работает:

<pre> mysql> select * from test; +------+------+ | x | y | +------+------+ | 1 | 2 | | 3 | 4 | +------+------+ 2 rows in set (0.00 sec)</p> <p>mysql> update test set x=y, y=x; Query OK, 2 rows affected (0.00 sec) Rows matched: 2 Changed: 2 Warnings: 0</p> <p>mysql> select * from test; +------+------+ | x | y | +------+------+ | 2 | 2 | | 4 | 4 | +------+------+ 2 rows in set (0.00 sec)

4 голосов
/ 22 августа 2014

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

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)
2 голосов
/ 27 декабря 2008

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

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

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

1 голос
/ 22 декабря 2008

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...