Anda tidak dapat menentukan tabel target untuk pembaruan dalam klausa FROM

381

Saya punya tabel mysql sederhana:

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);

Saya mencoba menjalankan pembaruan berikut, tetapi saya hanya mendapatkan kesalahan 1093:

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
))

Saya mencari kesalahan dan menemukan dari halaman berikut mysql http://dev.mysql.com/doc/refman/5.1/en/subquery-restrictions.html , tetapi tidak membantu saya.

Apa yang harus saya lakukan untuk memperbaiki kueri sql?

CSchulz
sumber

Jawaban:

771

Masalahnya adalah MySQL, untuk alasan apa pun yang tidak masuk akal, tidak memungkinkan Anda untuk menulis pertanyaan seperti ini:

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

Artinya, jika Anda melakukan UPDATE/ INSERT/ DELETEdi atas meja, Anda tidak dapat referensi tabel yang dalam inner query (Anda dapat namun referensi field dari yang tabel luar ...)


Solusinya adalah mengganti instance myTabledalam sub-kueri dengan (SELECT * FROM myTable), seperti ini

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

Ini tampaknya menyebabkan bidang yang diperlukan disalin secara implisit ke tabel sementara, jadi itu dibolehkan.

Saya menemukan solusi ini di sini . Catatan dari artikel itu:

Anda tidak ingin hanya SELECT * FROM tabledi subquery di kehidupan nyata; Saya hanya ingin menjaga contoh sederhana. Pada kenyataannya, Anda seharusnya hanya memilih kolom yang Anda butuhkan dalam kueri paling dalam itu, dan menambahkan WHEREklausa yang baik untuk membatasi hasil juga.

BlueRaja - Danny Pflughoeft
sumber
10
Saya pikir alasannya tidak masuk akal. Pikirkan tentang semantiknya. Baik MySQL harus menyimpan salinan tabel sebelum pembaruan dimulai, atau kueri dalam mungkin menggunakan data yang telah diperbarui oleh kueri saat sedang berlangsung. Tak satu pun dari efek samping ini yang diinginkan, jadi taruhan teraman adalah memaksa Anda untuk menentukan apa yang akan terjadi dengan menggunakan tabel tambahan.
siride
35
@siride: Database lain, seperti MSSQL atau Oracle, tidak memiliki batasan arbitrer ini
BlueRaja - Danny Pflughoeft
3
@ BlueRaja-DannyPflughoeft: tidak sembarangan. Ini keputusan desain yang masuk akal berdasarkan biaya alternatif. Sistem DB lainnya memilih untuk menangani biaya-biaya tersebut. Tetapi sistem-sistem itu tidak, misalnya, membiarkan Anda memasukkan kolom non-agregat dalam daftar SELECT ketika Anda menggunakan GROUP BY, dan MySQL melakukannya. Saya berpendapat bahwa MySQL salah di sini, dan saya mungkin mengatakan hal yang sama dengan DBMS lain untuk pernyataan UPDATE.
siride
33
@siride Dari sudut pandang aljabar relasional, Tdan (SELECT * FROM T)sepenuhnya setara. Mereka adalah hubungan yang sama. Karena itu, ini adalah pembatasan sewenang-wenang dan tidak waras. Lebih khusus, ini merupakan solusi untuk memaksa MySQL melakukan sesuatu yang jelas dapat dilakukan, tetapi karena alasan tertentu ia tidak dapat menguraikan dalam bentuk yang lebih sederhana.
Tobia
4
Dalam kasus saya, solusi yang diterima tidak berfungsi karena meja saya terlalu besar. Permintaan tidak pernah selesai. Tampaknya ini mengambil terlalu banyak sumber daya internal. Alih-alih, saya membuat Tampilan dengan kueri dalam dan menggunakannya untuk pemilihan data, yang bekerja dengan sangat baik. DELETE FROM t WHERE tableID NOT IN (SELECT viewID FROM t_view);Saya juga merekomendasikan menjalankan OPTIMIZE TABLE t;setelahnya untuk mengurangi ukuran tabel.
CodeX
53

Anda dapat membuatnya dalam tiga langkah:

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;

atau

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
Michael Pakhantsov
sumber
16
Ya, sebagian besar subkueri dapat ditulis ulang sebagai beberapa langkah dengan CREATE TABLEpernyataan - saya harap penulis mengetahui hal ini. Namun, apakah ini satu-satunya solusi? Atau bisakah kueri ditulis ulang dengan subkueri atau gabungan? Dan mengapa (tidak) melakukan itu?
Konerak
Saya pikir Anda memiliki kesalahan kapitalisasi dalam solusi kedua Anda. Tidak seharusnya UPDATE Pers Pmembaca UPDATE pers P?
ubiquibacon
2
Mencoba solusi ini dan untuk sejumlah besar entri dalam tabel sementara / kedua kueri bisa sangat lambat; cobalah membuat tabel sementara / kedua dengan indeks / kunci utama [lihat dev.mysql.com/doc/refman/5.1/en/create-table-select.html ]
Alex
Seperti yang dinyatakan @Konerak, ini bukan jawaban terbaik. Jawaban dari BlueRaja di bawah ini sepertinya yang terbaik untuk saya. Suara terbalik tampaknya setuju.
ShatyUT
@Konerak, Tidak CREATE TABLE AS SELECTmemberikan kinerja yang mengerikan?
Pacerier
27

Di Mysql, Anda tidak dapat memperbarui satu tabel dengan membuat subquery tabel yang sama.

Anda dapat memisahkan kueri dalam dua bagian, atau melakukannya

 UPDATE TABLE_A AS A
 INNER GABUNG TABLE_A SEBAGAI B PADA A.field1 = B.field1
 SET field2 =? 
Yuantao
sumber
5
SELECT ... SET? Saya belum pernah mendengar tentang ini.
Serge S.
@ grisson Terima kasih atas klarifikasi. Sekarang saya mengerti mengapa klausa IN saya tidak berfungsi - saya menargetkan tabel yang sama.
Anthony
2
... ini sepertinya tidak bekerja. Itu masih memberi saya kesalahan yang sama.
BlueRaja - Danny Pflughoeft
2
jawaban ini sebenarnya melakukan hal yang lebih benar dan efisien, yang digunakan AS Bpada referensi kedua TABLE_A. jawaban dalam contoh yang paling banyak dipilih dapat disederhanakan menggunakan AS Talih-alih berpotensi tidak efisien FROM (SELECT * FROM myTable) AS something, yang untungnya kueri pengoptimal biasanya menghilangkan tetapi mungkin tidak selalu melakukannya.
natbro
23

Buat tabel sementara (tempP) dari subquery

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
)

Saya telah memperkenalkan nama yang terpisah (alias) dan memberikan nama baru ke kolom 'persID' untuk tabel sementara

Budda
sumber
Mengapa tidak memilih nilai-nilai ke dalam variabel alih-alih melakukan seleksi batin bagian dalam?
Pacerier
SELECT ( SELECT MAX(gehalt * 1.05)..- yang pertama SELECTtidak memilih kolom apa pun.
Istiaque Ahmed
18

Sederhana saja. Misalnya, alih-alih menulis:

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

kamu harus menulis

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

atau serupa.

Sisi gelap
sumber
13

Pendekatan yang diposting oleh BlueRaja lambat saya memodifikasinya karena saya gunakan untuk menghapus duplikat dari tabel. Dalam kasus ini membantu siapa saja dengan Kueri Asli tabel besar

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

Ini membutuhkan lebih banyak waktu:

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

Solusi lebih cepat

DELETE FROM table where ID NOT IN(
   SELECT x.Id from (SELECT MIN(Id) as Id from table GROUP BY field2) AS t)
Ajak6
sumber
Tambahkan komentar jika Anda downvoting.
Ajak6
3

Jika Anda mencoba membaca fieldA dari tableA dan menyimpannya di fieldB di tabel yang sama, ketika fieldc = fieldd Anda mungkin ingin mempertimbangkan ini.

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

Kode di atas menyalin nilai dari fieldA ke fieldB ketika kondisi-bidang memenuhi kondisi Anda. ini juga berfungsi di ADO (mis. akses)

sumber: mencoba sendiri

Krish
sumber
3

MariaDB telah mengangkat ini mulai dari 10.3.x (baik untuk DELETEdan UPDATE):

UPDATE - Pernyataan dengan Sumber dan Target yang Sama

Dari MariaDB 10.3.2, pernyataan UPDATE mungkin memiliki sumber dan target yang sama.

Sampai MariaDB 10.3.1, pernyataan UPDATE berikut tidak akan berfungsi:

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

Dari MariaDB 10.3.2, pernyataan berhasil dieksekusi:

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

HAPUS - Sumber dan Target Tabel yang Sama

Hingga MariaDB 10.3.1, menghapus dari tabel dengan sumber dan target yang sama tidak dimungkinkan. Dari MariaDB 10.3.1, ini sekarang mungkin. Sebagai contoh:

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

DBFiddle MariaDB 10.2 - Kesalahan

DBFiddle MariaDB 10.3 - Sukses

Lukasz Szozda
sumber
0

Solusi lain termasuk menggunakan SELECT DISTINCT atau LIMIT dalam subquery, meskipun ini tidak eksplisit dalam efeknya pada materialisasi. ini bekerja untuk saya

seperti yang disebutkan dalam MySql Doc

PITU
sumber
0

MySQL tidak mengizinkan memilih dari tabel dan memperbarui dalam tabel yang sama pada saat yang sama Tapi selalu ada solusinya :)

Ini tidak berfungsi >>>>

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

Tapi ini berhasil >>>>

UPDATE table1 SET col1 = (SELECT MAX(col1) FROM (SELECT * FROM table1) AS table1_new) WHERE col1 IS NULL;
Hari Das
sumber