MySQLでの列値の交換


127

私はMySQLテーブルに座標があり、列名はXとYです。次に、このテーブルの列の値を交換して、XがYになり、YがXになるようにします。最も明白な解決策は列の名前を変更することですが、必ずしも構造を変更する権限がないため、構造を変更したくありません。

これは何らかの方法でUPDATEで実行できますか?UPDATEテーブルSET X = Y、Y = X明らかに私が望むことをしません。


編集:上記のパーミッションの制限により、ALTER TABLEまたはテーブル/データベース構造を変更する他のコマンドの使用が事実上妨げられることに注意してください。列の名前を変更するか、新しい列を追加することは、残念ながらオプションではありません。


5
注意として、UPDATE table SET X = Y, Y = XSQLでこれを行う標準的な方法であり、MySQLのみが誤動作します。
Antti Haapala 2016

回答:


204

私は同じことに対処しなければならなかったので、私の発見を要約します。

  1. UPDATE table SET X=Y, Y=X両方の値をYに設定するだけなので、このアプローチは明らかに機能しません。

  2. 一時変数を使用するメソッドは次のとおりです。「IS NOT NULL」の微調整についてhttp://beerpla.net/2009/02/17/swapping-column-values-in-mysql/のコメントからAntonyに感謝します。これがないと、クエリは予期せず動作します。投稿の最後にあるテーブルスキーマを参照してください。このメソッドは、値の1つがNULLの場合、値を交換しません。この制限のない方法#3を使用してください。

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

  3. この方法は、Dipinによって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;

基本的に、最初のテーブルは更新されるテーブルであり、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);

25
MySQLのドキュメントに記載されているように、単一のステートメントで変数を割り当てて読み取るのは安全ではありません。操作の順序は保証されません。したがって、唯一の安全な方法は#4です
AMIB 2013年

オプション4がうまくいきました。一部の行だけの列を交換する必要がある場合は、where句に条件を追加できます。
Brad Campbell

7
一時的なものを使わずに2つの変数を入れ替えるという愚かなインタビューの質問が実用的だとは思っていませんでしたが、ここではそうです。整数の場合、これは実際に機能します。更新swap_test set x = x + y、 y = xy、x = xy;
izak 2014

この回答のほとんどは、beerpla.net

17
@Jhawins beerpla.netが私のブログだからです。
Artem Russakovskii

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
数字に関しては、それは確かに最も近いものです。
常識14

追加時に値がオーバーフローする場合、問題になる可能性がありますか?
ToolmakerSteve

@ToolmakerSteveのためのTINYINTまたはの巨大なvalles INT、あなたは正しいです!!!
RolandoMySQLDBA

29

次のコードは、私のクイックテストのすべてのシナリオで機能します。

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

UPDATE table swap_test?そうじゃないのUPDATE swap_test
2016

12

UPDATEテーブルSET X = Y、Y = Xは、あなたが望むものを正確に実行します(編集:MySQLではなくPostgreSQLで。以下を参照)。値は古い行から取得され、同じ行の新しいコピーに割り当てられてから、古い行が置き換えられます。一時テーブル、一時列、またはその他のスワップトリックを使用する必要はありません。

@ D4V360:なるほど。それは衝撃的で予想外です。私はPostgreSQLを使用していますが、私の答えはそこで正しく機能します(試してみました)。PostgreSQL UPDATEのドキュメント(「パラメーター、式」の下)を参照してください。SET句の右側の式では、列の古い値が明示的に使用されていることが記載されています。対応するMySQL UPDATEドキュメントには、「単一テーブルのUPDATE割り当ては通常、左から右に評価される」というステートメントが含まれていることがわかります。

知ってよかった。


GregとD4V360に感謝します。更新クエリの動作に関するPostgreSQLとMySQLの違いを知ってよかったです。
Vijay Dev

"x = y、y = x"アプローチは、Oracleでも機能します。
Burhan Ali、

2
私はPostgreSQLを使用し、SET X = Y、Y = Xが私を救いました:)
匿名

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、長さ0の文字列、またはスペースのみの場合は機能しません。

に変更する必要があります

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は、一時的にせずに2つの変数を交換する必要がある愚かなインタビューの質問の良い使用法を最終的に見つけるために;-)
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 では機能しませ
str

2

これはきっとうまくいきます!EuroとSKKの価格列を入れ替えるのに必要なだけです。:)

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

上記は機能しません(エラー1064(42000):SQL構文にエラーがあります)


1

列に符号付き整数があると仮定すると、^演算子の結果はMySQLの符号なし64ビット整数であるため、CAST(a ^ b AS SIGNED)を使用する必要がある場合があります。

誰かを助けるために、ここに私が与えられた2つの行の間で同じ列を交換するために使用した方法があります:

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は2つの行のキーで、$ 3は最初のクエリの結果です。


1

試したことはないけど

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

それを行うかもしれません。

マーク


1

列名を変更することもできますが、これはハックのようなものです。ただし、これらの列にある可能性のあるインデックスには注意してください


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

ある列から別の列に値を移動し(アーカイブなど)、元の列の値をリセットするだけでした。
以下(上記の受け入れられた回答からの#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_dateend_dateを入れ替えています(ETLをメジャーリライトに実行すると、いくつかの開始日が終了日よりも遅くなったことがわかりました。

その場では、パフォーマンス上の理由(ユリウス日など、ただし0900のルートが1900-01-01)のためにMEDIUMINTを使用しているため、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;

参考までに:このコードは0.203秒で145 / 108,456レコードを更新しました。これは1回限りのタスクなので、パフォーマンスは重要ではありませんでした。
アンドリューフォスター

0

tb_userの姓と名の値を交換したいとします。

最も安全なのは:

  1. tb_userをコピーします。したがって、tb_userとtb_user_copyの2つのテーブルがあります。
  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;
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.