MySQL'de sütun değerlerini değiştirme


127

Koordinatlı bir MySQL tablom var, sütun adları X ve Y'dir. Şimdi, bu tablodaki sütun değerlerini takas etmek istiyorum, böylece X, Y ve Y, X olur. En belirgin çözüm, sütunları yeniden adlandırmak olurdu, ancak ben Bunu yapmak için gerekli izinlere sahip olmadığım için yapı değişiklikleri yapmak istemiyorum.

Bu bir şekilde UPDATE ile yapmak mümkün mü ? GÜNCELLEME tablosu SET X = Y, Y = X tabii ki istediğimi yapmayacak.


Düzenleme: Lütfen yukarıda bahsedilen izinlerle ilgili kısıtlamamın ALTER TABLE veya tablo / veritabanı yapısını değiştiren diğer komutların kullanımını etkili bir şekilde engellediğini unutmayın. Sütunları yeniden adlandırmak veya yenilerini eklemek maalesef seçenekler değildir.


5
not olarak, UPDATE table SET X = Y, Y = Xbunu SQL'de yapmanın standart yoludur, yalnızca MySQL yanlış davranır.
Antti Haapala

Yanıtlar:


204

Ben de aynı şeyi halletmek zorunda kaldım ve bulgularımı özetleyeceğim.

  1. Her UPDATE table SET X=Y, Y=Xiki değeri de Y olarak ayarlayacağından, yaklaşım açıkça işe yaramıyor.

  2. İşte geçici bir değişken kullanan bir yöntem. "IS NOT NULL" ince ayarı için http://beerpla.net/2009/02/17/swapping-column-values-in-mysql/ yorumlarından Antony'ye teşekkürler . O olmadan, sorgu tahmin edilemeyecek şekilde çalışır. Gönderinin sonundaki tablo şemasına bakın. Bu yöntem, biri NULL ise değerleri değiş tokuş etmez. Bu sınırlamaya sahip olmayan 3 numaralı yöntemi kullanın.

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

  3. Bu yöntem Dipin tarafından http://beerpla.net/2009/02/17/swapping-column-values-in-mysql/ yorumlarında sunulmuştur . Bence en şık ve temiz çözüm. Hem NULL hem de NULL olmayan değerlerle çalışır.

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

  4. Bulduğum başka bir yaklaşım işe yarıyor gibi görünüyor:

    UPDATE swap_test s1, swap_test s2 SET s1.x=s1.y, s1.y=s2.x WHERE s1.id=s2.id;

Esasen, 1. tablo güncellenen tablodur ve 2. tablo eski verileri almak için kullanılır.
Bu yaklaşımın mevcut olması için bir birincil anahtar gerektirdiğini unutmayın.

Bu benim test şemam:

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 belgelerinde belirtildiği gibi, değişkenleri tek bir ifadede atamak ve okumak güvenli değildir. İşlem sırası garanti edilmez. Yani tek güvenli yöntem # 4
AMIB

Seçenek 4 benim için çalıştı. Sütunları yalnızca bazı satırlar için takas etmeniz gerekiyorsa, where cümlesine daha fazla koşul ekleyebilirsiniz.
Brad Campbell

7
Biliyorsunuz, bu aptal mülakat sorusunun geçici kullanmadan iki değişkeni değiştirmeyi istemesinin pratik bir kullanımı olacağını hiç düşünmemiştim, ama işte burada ve tamsayılar için bu gerçekten işe yarayacak: swap_test setini x = x + y güncelle, y = xY x = xy;
izak


17
@Jhawins Bunun nedeni, beerpla.net'in blogum olması.
Artem Russakovskii

52

X ve Y kullanarak toplamı alıp karşıt değeri çıkarabilirsiniz.

UPDATE swaptest SET X=X+Y,Y=X-Y,X=X-Y;

İşte örnek bir test (ve negatif sayılarla çalışıyor)

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>

İşte gerçekleştirilen takas

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>

Bir şans ver !!!


5
Rakamlar için gerçekten en temiz olanı.
Sizin Common Sense

Eklendiğinde bir değer taşıyorsa sorun olabilir mi?
ToolmakerSteve

@ToolmakerSteve belki için TINYINTya da büyük değerler için INT, haklısın !!!
RolandoMySQLDBA

29

Aşağıdaki kod, hızlı testimdeki tüm senaryolar için çalışır:

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

UPDATE table swap_test? Olması gerekmiyor UPDATE swap_testmu?
Pang

12

GÜNCELLEME tablosu SET X = Y, Y = X tam olarak istediğinizi yapacak (düzenleme: MySQL'de değil, PostgreSQL'de, aşağıya bakınız). Değerler eski satırdan alınır ve aynı satırın yeni bir kopyasına atanır, ardından eski satır değiştirilir. Geçici bir tablo, geçici bir sütun veya diğer takas numaralarını kullanmak zorunda değilsiniz.

@ D4V360: Anlıyorum. Bu şok edici ve beklenmedik. PostgreSQL kullanıyorum ve cevabım orada doğru çalışıyor (denedim). Bkz PostgreSQL GÜNCELLEME dokümanlar açıkça sütunların eski değerleri kullanmak SET maddelerinin sağ taraftaki o ifadeleri bahseder, (Parametreler, ifadesi altında). Karşılık gelen MySQL UPDATE belgelerinin , tanımladığınız davranışı ifade eden "Tek tablo GÜNCELLEME atamaları genellikle soldan sağa değerlendirilir" ifadesini içerdiğini görüyorum .

Bunu bildiğim iyi oldu.


Greg ve D4V360'a teşekkürler, güncelleme sorgularının davranışı hakkında PostgreSQL ve MySQL arasındaki farkları bilmek güzel.
Vijay Dev

"X = y, y = x" yaklaşımı, değeri ne olursa olsun Oracle'da da çalışır.
Burhan Ali

2
PostgreSQL kullandım ve SET X = Y, Y = X beni kurtardı :)
Anonim

4
IMHO bu cevap bir karmaşadır - "oops, boşver" ekli kötü tavsiye. Yarısı bir yorum olmalı ve geri kalan kısmın soruyla ilgili olan tek kısmı MySQL belgelerine bağlantıdır ...
Air

6

Tamam, sadece eğlenmek için bunu yapabilirsin! (dize değerlerini değiştirdiğinizi varsayarsak)

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'de soldan sağa değerlendirme sürecini kötüye kullanmak biraz eğlenceli.

Alternatif olarak, sayılarsa sadece XOR kullanın. Koordinatlardan bahsettiniz, peki güzel tamsayı değerleriniz veya karmaşık dizeleriniz var mı?

Düzenleme: XOR işi bu arada şöyle çalışır:

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

5

Bir ara değişim değişkenine sahip olmanın şu şekilde en iyi uygulama olduğuna inanıyorum:

update z set c1 = @c := c1, c1 = c2, c2 = @c

Birincisi, her zaman çalışır; ikincisi, veri türünden bağımsız olarak çalışır.

İkisine rağmen

update z set c1 = c1 ^ c2, c2 = c1 ^ c2, c1 = c1 ^ c2

ve

update z set c1 = c1 + c2, c2 = c1 - c2, c1 = c1 - c2

genellikle çalışıyoruz, bu arada sadece sayı veri türü için ve taşmayı önlemek sizin sorumluluğunuzdadır, XOR'u imzalı ve işaretsiz arasında kullanamazsınız, ayrıca taşma olasılığı için toplam kullanamazsınız.

Ve

update z set c1 = c2, c2 = @c where @c := c1

c1 0 veya NULL ise veya sıfır uzunluklu dize veya yalnızca boşluk ise çalışmıyor.

Değiştirmemiz gerek

update z set c1 = c2, c2 = @c where if((@c := c1), true, true)

İşte komut dosyaları:

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)

Sonunda iki değişkeni geçici olmaksızın değiştireceğiniz aptal mülakat sorusu için iyi bir kullanım bulduğunuz için +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;
Bunun gibi bir şey mi?

Düzenleme: Greg'in yorumu hakkında: Hayır, bu işe yaramıyor:

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)


Sadece kayıt için: Bu does it yapar iken PostgreSQL iş değil MySQL çalışır.
str

2

Bu kesinlikle işe yarıyor! Sadece Euro ve SKK fiyat sütunlarını değiştirmek için ihtiyacım vardı. :)

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

Yukarıdakiler çalışmayacak (ERROR 1064 (42000): SQL sözdiziminizde bir hata var)


1

Sütunlarınızda tamsayılar imzaladığınızı varsayarsak, ^ operatörünün sonucu MySQL'de imzasız 64 bitlik bir tam sayı olduğu için CAST (İMZALANDIĞI GİBİ a ^ b) kullanmanız gerekebilir.

Herhangi birine yardımcı olması durumunda, aynı sütunu verilen iki satır arasında değiştirmek için kullandığım yöntem şu şekildedir:

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

Burada $ 1 ve $ 2 iki satırın anahtarlarıdır ve $ 3 ilk sorgunun sonucudur.


1

Denemedim ama

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

Yapabilirsin.

işaret


1

Sen olabilir sütun adlarını değiştirmek, ama bu bir kesmek fazlasıdır. Ancak bu sütunlarda olabilecek herhangi bir dizine karşı dikkatli olun


1

Tablo adı müşteridir. alanları a ve b'dir, bir değeri b ile değiştirin.

GÜNCEL MÜŞTERİ SETİ a = (@ temp: = a), a = b, b = @temp

Bunun iyi çalıştığını kontrol ettim.


1

SQL Server'da şu sorguyu kullanabilirsiniz:

update swaptable 
set col1 = t2.col2,
col2 = t2.col1
from swaptable t2
where id = t2.id

0

Tek sorgu kullanarak sütun değerlerinin değiştirilmesi

GÜNCELLEME my_table SET a = @ tmp: = a, a = b, b = @ tmp;

şerefe ...!


1
Bu, kabul edilen cevabın 3 numaralı tekrarıdır .
Pang

0

Değeri bir sütundan diğerine taşımak (arşivleme gibi) ve orijinal sütunun değerini sıfırlamak zorunda kaldım.
Aşağıdakiler (yukarıda kabul edilen cevaptan 3 numaralı referans) benim için çalıştı.

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

Bu örnek takasları START_DATE ve end_date tarihleri yanlış şekilde yuvarlak kayıtları için (büyük bir yeniden yazma içine ETL gerçekleştirirken, bazılarını buldum başlangıç onların daha buluşma sonra . Aşağı, kötü programcılar tarihleri!).

In situ, performans nedenleriyle MEDIUMINT'leri kullanıyorum (Julian günleri gibi, ancak 1900-01-01 0 kökü var), bu yüzden WHERE mdu.start_date> mdu.end_date koşulunu yerine getirmekte sorun yaşamadım .

PK'ler ayrı ayrı 3 sütunun tamamındaydı (operasyon / dizin oluşturma nedenleriyle).

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;

Bilginize: Bu kod 145 / 108.456 kaydı 0.203 saniyede güncelledi. Tek seferlik bir görevdi ve bu nedenle performans kritik değildi.
Andrew Foster

0

Diyelim ki tb_user içinde ad ve soyadın değerini değiştirmek istiyorsunuz.

En güvenli olan:

  1. Tb_user kopyalayın. Yani 2 tablonuz olacak: tb_user ve tb_user_copy
  2. UPDATE INNER JOIN sorgusunu kullan
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

Aşağıdaki sorguyu uygulayabilirsiniz, Benim için mükemmel çalıştı.

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;
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.