การสลับค่าคอลัมน์ใน 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 ที่ทำงานผิดปกติ
Antti Haapala

คำตอบ:


204

ฉันต้องจัดการกับสิ่งเดียวกันและฉันจะสรุปสิ่งที่ค้นพบ

  1. UPDATE table SET X=Y, Y=Xวิธีการอย่างเห็นได้ชัดไม่ได้ทำงานตามที่มันเพิ่งจะตั้งค่าทั้งวาย

  2. นี่คือวิธีการที่ใช้ตัวแปรชั่วคราว ขอบคุณ Antony จากความคิดเห็นของ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 ในอีกความคิดเห็นของ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);

25
ตามที่ระบุไว้ในเอกสาร MySQL การกำหนดและอ่านตัวแปรในคำสั่งเดียวไม่ปลอดภัย ไม่รับประกันลำดับการดำเนินการ ดังนั้นวิธีเดียวที่ปลอดภัยคือ # 4
AMIB

ตัวเลือกที่ 4 ใช้ได้ผลสำหรับฉัน เห็นได้ชัดว่าคุณสามารถเพิ่มเงื่อนไขเพิ่มเติมใน where clause ได้ถ้าคุณต้องการสลับคอลัมน์สำหรับบางแถวเท่านั้น
Brad Campbell

7
คุณรู้ไหมฉันไม่เคยคิดว่าจะมีการใช้งานจริงสำหรับคำถามสัมภาษณ์โง่ ๆ ที่ขอให้สลับสองตัวแปรโดยไม่ใช้ชั่วคราว แต่นี่คือและสำหรับจำนวนเต็มสิ่งนี้จะใช้งานได้จริง: update 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 เป็นบล็อกของฉัน
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
สำหรับตัวเลขมันเป็นตัวเลขที่สะอาดที่สุด
สามัญสำนึกของคุณ

อาจเป็นปัญหาหากค่าล้นเมื่อเพิ่ม?
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 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
IMHO คำตอบนี้ไม่เป็นระเบียบ - คำแนะนำที่ไม่ดีพร้อมกับ "ไม่เป็นไร" ต่อท้าย ครึ่งหนึ่งควรเป็นความคิดเห็นและส่วนเดียวของส่วนที่เหลือที่เกี่ยวข้องกับคำถามคือลิงก์ไปยังเอกสาร 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;
อะไรทำนองนี้?

แก้ไข: เกี่ยวกับความคิดเห็นของ Greg: ไม่ใช้ไม่ได้:

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

มันได้ผลแน่นอน! ฉันแค่ต้องการมันเพื่อสลับคอลัมน์ราคายูโรและ 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 เป็นผลลัพธ์ของการสืบค้นแรก


1

ฉันไม่ได้ลอง แต่

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

อาจจะทำได้

เครื่องหมาย


1

คุณสามารถเปลี่ยนชื่อคอลัมน์ได้ แต่เป็นการแฮ็กมากกว่า แต่ระวังดัชนีที่อาจอยู่ในคอลัมน์เหล่านี้


1

ชื่อโต๊ะคือลูกค้า ฟิลด์คือ a และ 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

การสลับค่าคอลัมน์โดยใช้แบบสอบถามเดียว

อัปเดต my_table SET a = @ tmp: = a, a = b, b = @ tmp;

ไชโย ... !


1
นี่เป็นเพียงการทำซ้ำของ # 3 ของคำตอบที่ได้รับการยอมรับ
ปาง

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 ในการเขียนซ้ำครั้งใหญ่ฉันพบว่าวันที่เริ่มต้นบางวันช้ากว่าวันที่สิ้นสุดลงโปรแกรมเมอร์แย่!)

ในแหล่งกำเนิดฉันใช้ MEDIUMINTs เพื่อเหตุผลด้านประสิทธิภาพ (เช่นวันจูเลียน แต่มีรากของ 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;

FYI: รหัสนี้อัปเดต 145 / 108,456 ระเบียนใน 0.203 วินาที มันเป็นงานที่ทำเพียงครั้งเดียวดังนั้นประสิทธิภาพจึงไม่สำคัญ
Andrew Foster

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;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.