FROM yan tümcesinde güncelleme için hedef tablo belirtemezsiniz


380

Basit bir mysql tablo var:

CREATE TABLE IF NOT EXISTS `pers` (
  `persID` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(35) NOT NULL,
  `gehalt` int(11) NOT NULL,
  `chefID` int(11) DEFAULT NULL,
  PRIMARY KEY (`persID`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;

INSERT INTO `pers` (`persID`, `name`, `gehalt`, `chefID`) VALUES
(1, 'blb', 1000, 3),
(2, 'as', 1000, 3),
(3, 'chef', 1040, NULL);

Aşağıdaki güncelleştirmeyi çalıştırmayı denedim, ancak yalnızca 1093 hatasını alıyorum:

UPDATE pers P 
SET P.gehalt = P.gehalt * 1.05 
WHERE (P.chefID IS NOT NULL 
OR gehalt < 
(SELECT (
    SELECT MAX(gehalt * 1.05) 
    FROM pers MA 
    WHERE MA.chefID = MA.chefID) 
    AS _pers
))

Hatayı aradım ve mysql'den şu sayfayı buldum: http://dev.mysql.com/doc/refman/5.1/en/subquery-restrictions.html , ama bana yardımcı olmuyor.

Sql sorgusunu düzeltmek için ne yapmalıyım?


Yanıtlar:


769

Sorun şu ki, MySQL, hangi nedenle olursa olsun, böyle sorgular yazmanıza izin vermiyor:

UPDATE myTable
SET myTable.A =
(
    SELECT B
    FROM myTable
    INNER JOIN ...
)

Yani, bir tablo üzerinde UPDATE/ INSERT/ yapıyorsanız DELETE, bir iç sorguda bu tabloya başvuramazsınız ( ancak bu dış tablodaki bir alana başvurabilirsiniz ...)


Solüsyon örneğini değiştirmektir myTableile alt sorguda (SELECT * FROM myTable)böyle,

UPDATE myTable
SET myTable.A =
(
    SELECT B
    FROM (SELECT * FROM myTable) AS something
    INNER JOIN ...
)

Bu açıkça gerekli alanların geçici bir tabloya dolaylı olarak kopyalanmasına neden olduğundan, buna izin verilir.

Bu çözümü burada buldum . Bu makaleden bir not:

SELECT * FROM tableGerçek hayatta sadece alt sorguda olmak istemezsiniz ; Sadece örnekleri basit tutmak istedim. Gerçekte, yalnızca en içteki sorguda ihtiyacınız olan sütunları seçmeli WHEREve sonuçları da sınırlamak için iyi bir cümle eklemelisiniz .


10
Nedenin anlamsız olduğunu düşünmüyorum. Anlambilimi düşünün. MySQL'in güncelleme başlamadan önce tablonun bir kopyasını tutması gerekir ya da iç sorgu, devam etmekte olan sorgu tarafından önceden güncellenmiş verileri kullanabilir. Bu yan etkilerin hiçbiri istenmez, bu nedenle en güvenli bahis sizi ekstra bir tablo kullanarak ne olacağını belirlemeye zorlamaktır.
siride

35
@siride: MSSQL veya Oracle gibi diğer veritabanlarında bu keyfi kısıtlama yoktur
BlueRaja - Danny Pflughoeft 13:13

3
@ BlueRaja-DannyPflughoeft: keyfi değil. Alternatiflerin maliyetlerine dayalı makul tasarım kararı. Diğer DB sistemleri yine de bu maliyetlerle başa çıkmayı seçti. Ancak bu sistemler, ör. GROUP BY kullandığınızda SELECT listelerine toplanmamış sütunlar eklemenize izin vermez ve MySQL kullanır. MySQL burada yanlış olduğunu iddia ediyorum ve aynı UPDATE deyimleri için diğer DBMS'leri söyleyebiliriz.
siride

33
görünüşünün ilişkisel cebir açıdan @siride, Tve (SELECT * FROM T)tamamen eşdeğerdir. Aynı ilişki. Bu nedenle, bu keyfi, ani bir kısıtlamadır. Daha spesifik olarak, MySQL'i açıkça yapabileceği bir şey yapmaya zorlamak için bir çözüm, ancak bazı nedenlerden dolayı daha basit bir şekilde ayrıştırılamıyor.
Tobia

4
Benim durumumda kabul edilen çözüm işe yaramadı çünkü masam çok büyüktü. Sorgu hiç tamamlanmadı. Görünüşe göre bu çok fazla iç kaynak gerektiriyor. Bunun yerine, iç sorgu ile bir Görünüm oluşturdum ve kesinlikle iyi çalıştı veri seçimi için kullandım. DELETE FROM t WHERE tableID NOT IN (SELECT viewID FROM t_view);Ayrıca OPTIMIZE TABLE t;masanın boyutunu küçültmek için daha sonra çalıştırmanızı tavsiye ederim .
CodeX

53

Bunu üç adımda yapabilirsiniz:

CREATE TABLE test2 AS
SELECT PersId 
FROM pers p
WHERE (
  chefID IS NOT NULL 
  OR gehalt < (
    SELECT MAX (
      gehalt * 1.05
    )
    FROM pers MA
    WHERE MA.chefID = p.chefID
  )
)

...

UPDATE pers P
SET P.gehalt = P.gehalt * 1.05
WHERE PersId
IN (
  SELECT PersId
  FROM test2
)
DROP TABLE test2;

veya

UPDATE Pers P, (
  SELECT PersId
  FROM pers p
  WHERE (
   chefID IS NOT NULL 
   OR gehalt < (
     SELECT MAX (
       gehalt * 1.05
     )
     FROM pers MA
     WHERE MA.chefID = p.chefID
   )
 )
) t
SET P.gehalt = P.gehalt * 1.05
WHERE p.PersId = t.PersId

16
Evet, çoğu alt sorgu, CREATE TABLEifadelerle birden fazla adım olarak yeniden yazılabilir - umarım yazar bunun farkındaydı. Ancak, bu tek çözüm mü? Veya sorgu alt sorgular veya birleşimlerle yeniden yazılabilir mi? Ve bunu neden yapmıyoruz?
Konerak

Sanırım ikinci çözümünüzde büyük harf kullanımı hatası var. Gerekmiyor UPDATE Pers Pokumak UPDATE pers P?
ubiquibacon

2
Bu çözümü denedim ve geçici / ikinci tabloda çok sayıda giriş için sorgu çok yavaş olabilir; Bir dizin / birincil anahtarla geçici / ikinci tablo oluşturmaya çalışın [bkz. dev.mysql.com/doc/refman/5.1/en/create-table-select.html ]
Alex

@Konerak'ın belirttiği gibi, bu gerçekten en iyi cevap değil. Aşağıdaki BlueRaja'nın cevabı benim için en iyisi. Upvotes kabul ediyor gibi görünüyor.
ShatyUT

@Konerak, CREATE TABLE AS SELECTKorkunç performans vermiyor mu ?
Pacerier

27

Mysql'de, aynı tabloyu alt sorgu ile bir tabloyu güncelleyemezsiniz.

Sorguyu iki bölüme ayırabilir veya

 A OLARAK GÜNCELLEME TABLOSU
 İÇ MEKAN TABLO_A'YA AS B ÜZERİNE KATIN A.field1 = B.field1
 SET field2 =? 

5
SELECT ... SET? Bunu hiç duymadım.
Serge S.

@grisson Açıklama için teşekkürler. Şimdi IN deyimimin neden çalışmadığını anlıyorum - aynı tabloyu hedefliyordum.
Anthony

2
... aslında işe yaramıyor gibi görünüyor. Hala bana aynı hatayı veriyor.
BlueRaja - Danny Pflughoeft

2
bu cevap aslında AS Bikinci referansta kullanılan daha doğru ve verimli olanı yapar TABLE_A. en çok oylanan örnekteki cevap AS T, potansiyel olarak verimsiz olmak yerine basitleştirilebilir FROM (SELECT * FROM myTable) AS something; neyse ki, sorgu optimize edici tipik olarak ortadan kaldırır, ancak her zaman yapamayabilir.
natbro

23

Alt sorgudan geçici tablo (tempP) oluşturma

UPDATE pers P 
SET P.gehalt = P.gehalt * 1.05 
WHERE P.persID IN (
    SELECT tempP.tempId
    FROM (
        SELECT persID as tempId
        FROM pers P
        WHERE
            P.chefID IS NOT NULL OR gehalt < 
                (SELECT (
                    SELECT MAX(gehalt * 1.05) 
                    FROM pers MA 
                    WHERE MA.chefID = MA.chefID) 
                    AS _pers
                )
    ) AS tempP
)

Ayrı bir ad (takma ad) ekledim ve geçici tablo için 'persid' sütununa yeni bir ad verdim


Neden iç iç iç seçimler yapmak yerine değerleri değişkenlere seçmiyorsunuz?
Pacerier

SELECT ( SELECT MAX(gehalt * 1.05)..- ilki SELECTherhangi bir sütun seçmez.
Istiaque Ahmed

18

Oldukça basit. Örneğin, yazmak yerine:

INSERT INTO x (id, parent_id, code) VALUES (
    NULL,
    (SELECT id FROM x WHERE code='AAA'),
    'BBB'
);

yazmalısın

INSERT INTO x (id, parent_id, code)
VALUES (
    NULL,
    (SELECT t.id FROM (SELECT id, code FROM x) t WHERE t.code='AAA'),
    'BBB'
);

veya benzeri.


13

BlueRaja tarafından yayınlanan Yaklaşım yavaş tablodan yinelenenleri silmek için kullandığım gibi değiştirdim. Büyük tabloları olan herkese yardımcı olması durumunda Orijinal Sorgu

delete from table where id not in (select min(id) from table group by field 2)

Bu daha fazla zaman alıyor:

DELETE FROM table where ID NOT IN(
  SELECT MIN(t.Id) from (select Id,field2 from table) AS t GROUP BY field2)

Daha Hızlı Çözüm

DELETE FROM table where ID NOT IN(
   SELECT x.Id from (SELECT MIN(Id) as Id from table GROUP BY field2) AS t)

Aşağı oy veriyorsanız bir yorum ekleyin.
Ajak6


3

A alanını tableA'dan okumaya ve aynı tablodaki fieldB'ye kaydetmeye çalışıyorsanız, fieldc = fieldd olduğunda bunu düşünebilirsiniz.

UPDATE tableA,
    tableA AS tableA_1 
SET 
    tableA.fieldB= tableA_1.filedA
WHERE
    (((tableA.conditionFild) = 'condition')
        AND ((tableA.fieldc) = tableA_1.fieldd));

Yukarıdaki kod, koşul alanı koşulunuzla karşılaştığında değeri A alanından B alanına kopyalar. bu ADO'da da çalışır (ör. erişim)

Kaynak: kendimi denedim


3

Mariadb 10.3.x'ten (hem bu başlangıç kaldırmıştır DELETEve UPDATE):

GÜNCELLEME - Aynı Kaynak ve Hedefe Sahip İfadeler

MariaDB 10.3.2'den UPDATE ifadeleri aynı kaynak ve hedefe sahip olabilir.

MariaDB 10.3.1'e kadar aşağıdaki UPDATE deyimi çalışmaz:

UPDATE t1 SET c1=c1+1 WHERE c2=(SELECT MAX(c2) FROM t1);
  ERROR 1093 (HY000): Table 't1' is specified twice, 
  both as a target for 'UPDATE' and as a separate source for data

MariaDB 10.3.2'den, ifade başarıyla yürütülür:

UPDATE t1 SET c1=c1+1 WHERE c2=(SELECT MAX(c2) FROM t1);

SİL - Aynı Kaynak ve Hedef Tablosu

MariaDB 10.3.1'e kadar, aynı kaynak ve hedefe sahip bir tablodan silmek mümkün değildi. MariaDB 10.3.1'den bu mümkün. Örneğin:

DELETE FROM t1 WHERE c1 IN (SELECT b.c1 FROM t1 b WHERE b.c2=0);

DBFiddle MariaDB 10.2 - Hata

DBFiddle MariaDB 10.3 - Başarı


0

Diğer geçici çözümler alt sorguda SELECT DISTINCT veya LIMIT kullanılmasını içerir, ancak bunlar materyalizasyon üzerindeki etkileri bakımından açık değildir. bu benim için çalıştı

MySql Doc'ta belirtildiği gibi


0

MySQL, bir tablodan seçim yapmaya ve aynı tabloda aynı anda güncelleme yapmaya izin vermez. Ama her zaman bir çözüm var :)

Bu işe yaramıyor >>>>

UPDATE table1 SET col1 = (SELECT MAX(col1) from table1) WHERE col1 IS NULL;

Ama bu işe yarıyor >>>>

UPDATE table1 SET col1 = (SELECT MAX(col1) FROM (SELECT * FROM table1) AS table1_new) WHERE col1 IS NULL;
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.