MySQL Error 1093 - Tidak dapat menentukan tabel target untuk pembaruan dalam klausa FROM

593

Saya memiliki tabel story_categorydi database saya dengan entri yang korup. Kueri berikutnya mengembalikan entri yang rusak:

SELECT * 
FROM  story_category 
WHERE category_id NOT IN (
    SELECT DISTINCT category.id 
    FROM category INNER JOIN 
       story_category ON category_id=category.id);

Saya mencoba menghapusnya:

DELETE FROM story_category 
WHERE category_id NOT IN (
    SELECT DISTINCT category.id 
    FROM category 
      INNER JOIN story_category ON category_id=category.id);

Tapi saya mendapatkan kesalahan selanjutnya:

# 1093 - Anda tidak dapat menentukan tabel target 'story_category' untuk pembaruan dalam klausa FROM

Bagaimana saya bisa mengatasi ini?

Sergio del Amo
sumber
3
Terkait: stackoverflow.com/a/14302701/238419
BlueRaja - Danny Pflughoeft
2
Sepertinya permintaan fitur di pelacak bug MySQL ada di sini: tidak dapat memperbarui tabel dan memilih dari tabel yang sama dalam subquery
Ben Creasy

Jawaban:

713

Pembaruan: Jawaban ini mencakup klasifikasi kesalahan umum. Untuk jawaban yang lebih spesifik tentang cara terbaik menangani permintaan persis OP, silakan lihat jawaban lain untuk pertanyaan ini

Di MySQL, Anda tidak bisa mengubah tabel yang sama yang Anda gunakan di bagian SELECT.
Perilaku ini didokumentasikan di: http://dev.mysql.com/doc/refman/5.6/en/update.html

Mungkin Anda bisa bergabung dengan tabel itu sendiri

Jika logikanya cukup sederhana untuk membentuk kembali kueri, kehilangan subquery dan bergabung dengan tabel itu sendiri, menggunakan kriteria seleksi yang sesuai. Ini akan menyebabkan MySQL melihat tabel sebagai dua hal yang berbeda, memungkinkan perubahan destruktif untuk terus maju.

UPDATE tbl AS a
INNER JOIN tbl AS b ON ....
SET a.col = b.col

Atau, coba nempatkan subquery lebih dalam ke dalam dari klausa ...

Jika Anda benar-benar membutuhkan subquery, ada solusinya, tetapi jelek karena beberapa alasan, termasuk kinerja:

UPDATE tbl SET col = (
  SELECT ... FROM (SELECT.... FROM) AS x);

Subquery bersarang di klausa FROM membuat tabel sementara implisit , sehingga tidak dihitung sebagai tabel yang sama dengan yang Anda perbarui.

... tapi hati-hati dengan pengoptimal kueri

Namun, berhati-hatilah bahwa dari MySQL 5.7.6 dan seterusnya, pengoptimal dapat mengoptimalkan subquery, dan masih memberi Anda kesalahan. Untungnya, optimizer_switchvariabel dapat digunakan untuk mematikan perilaku ini; meskipun saya tidak bisa merekomendasikan melakukan ini sebagai sesuatu yang lebih dari perbaikan jangka pendek, atau untuk tugas-tugas kecil satu kali.

SET optimizer_switch = 'derived_merge=off';

Terima kasih kepada Peter V. Mørch untuk saran ini di komentar.

Contoh teknik berasal dari Baron Schwartz, awalnya diterbitkan di Nabble , diparafrasekan dan diperluas di sini.

Cheekysoft
sumber
1
Diputakhirkan jawaban ini karena saya harus menghapus item dan tidak bisa mendapatkan info dari tabel lain, harus subquery dari tabel yang sama. Karena ini adalah apa yang muncul di atas saat mencari kesalahan di Google, ini adalah jawaban yang paling cocok untuk saya dan banyak orang yang mencoba memperbarui sementara subquerieing dari tabel yang sama.
HMR
2
@Cheekysoft, Mengapa tidak menyimpan nilai ke dalam variabel saja?
Pacerier
19
Hati-hati, bahwa dari MySQL 5.7.6 aktif , optimizer dapat mengoptimalkan sub-kueri dan masih memberi Anda kesalahan, kecuali jika Anda SET optimizer_switch = 'derived_merge=off';:-(
Peter V. Mørch
1
@ PeterV.Mørch Mengikuti mysqlserverteam.com/derived-tables-in-mysql-5-7 , jika operasi tertentu dilakukan, penggabungan tidak dapat terjadi. Misalnya memberikan tabel dummy yang diturunkan dengan LIMIT (untuk inifity) dan kesalahan tidak akan pernah terjadi. Itu cukup hackish dan masih ada risiko bahwa versi MySQL di masa depan akan mendukung penggabungan query dengan LIMIT.
user2180613
Bisakah Anda, tolong, berikan contoh lengkap tentang solusi ini? UPDATE tbl SET col = (PILIH ... DARI (PILIH .... DARI) SEBAGAI x); saya masih mendapatkan kesalahan
JoelBonetR
310

NexusRex memberikan solusi yang sangat baik untuk dihapus dengan bergabung dari tabel yang sama.

Jika kamu melakukan ini:

DELETE FROM story_category
WHERE category_id NOT IN (
        SELECT DISTINCT category.id AS cid FROM category 
        INNER JOIN story_category ON category_id=category.id
)

Anda akan mendapatkan kesalahan.

Tetapi jika Anda membungkus kondisinya dalam satu lagi pilih:

DELETE FROM story_category
WHERE category_id NOT IN (
    SELECT cid FROM (
        SELECT DISTINCT category.id AS cid FROM category 
        INNER JOIN story_category ON category_id=category.id
    ) AS c
)

itu akan melakukan hal yang benar !!

Penjelasan: Pengoptimal kueri melakukan optimasi gabungan turunan untuk kueri pertama (yang menyebabkannya gagal dengan kesalahan), tetapi kueri kedua tidak memenuhi syarat untuk optimasi gabungan turunan . Oleh karena itu pengoptimal dipaksa untuk mengeksekusi subquery terlebih dahulu.

Ekonoval
sumber
6
Mungkin karena saya terikat hari ini, tetapi ini adalah jawaban termudah, bahkan jika itu mungkin bukan yang "terbaik".
Tyler V.
4
Ini berhasil, terima kasih! Jadi apa logikanya di sini? Jika bersarang satu tingkat lagi maka akan dieksekusi sebelum bagian luar? Dan jika tidak bersarang maka mySQL mencoba menjalankannya setelah delete memiliki kunci di atas meja?
Flat Cat
43
Kesalahan dan solusi ini tidak masuk akal secara logis ... tetapi berhasil. Kadang-kadang saya bertanya-tanya obat apa yang digunakan oleh pengembang MySQL ...
Cerin
1
Setuju dengan @Cerin .. ini benar-benar tidak masuk akal, tetapi berhasil.
FastTrack
@ekonoval Terima kasih atas solusinya tetapi tidak masuk akal bagi saya, sepertinya Anda membodohi MySQL dan dia menerimanya, lol
deFreitas
106

Di inner joindalam sub-kueri Anda tidak perlu. Sepertinya Anda ingin menghapus entri di story_categorymana category_idtidak ada dalam categorytabel.

Melakukan hal ini:

DELETE FROM story_category 
WHERE category_id NOT IN (
    SELECT DISTINCT category.id 
    FROM category);

Alih-alih itu:

DELETE FROM story_category 
WHERE category_id NOT IN (
    SELECT DISTINCT category.id 
    FROM category INNER JOIN
         story_category ON category_id=category.id);
shA.t
sumber
5
Ini harus menjadi jawaban teratas! Mungkin menghapus yang pertama "bukan".
hoyhoy
1
Saya pikir DISTINCTtidak perlu di sini - untuk kinerja yang lebih baik;).
shA.t
1
Saya pasti gila. Jawabannya sama dengan apa yang dinyatakan dalam aslinya.
Jeff Lowery
Ini juga jawaban untuk saya. Jangan where indi kolom ID, jadi Anda tidak perlu membuat subquery tabel utama.
Richard
@ JeffLowery - blok kode pertama adalah jawabannya di sini; ini adalah blok kode kedua dari pertanyaan.
ToolmakerSteve
95

Baru-baru ini saya harus memperbarui catatan dalam tabel yang sama saya melakukannya seperti di bawah ini:

UPDATE skills AS s, (SELECT id  FROM skills WHERE type = 'Programming') AS p
SET s.type = 'Development' 
WHERE s.id = p.id;
Pratik Khadloya
sumber
11
Tidak bisakah ini ditulis sebagai UPDATE skills SET type='Development' WHERE type='Programming';? Ini sepertinya tidak menjawab pertanyaan awal.
lilbyrdie
1
Sepertinya berlebihan, @lilbyrdie benar - hanya mungkin UPDATE skills SET type='Development' WHERE type='Programming';. Saya tidak mengerti mengapa begitu banyak orang tidak berpikir tentang apa yang mereka lakukan ...
shadyyx
1
ini jawaban terbaik di sini, IMHO. Sintaksnya mudah dimengerti, Anda dapat menggunakan kembali pernyataan sebelumnya dan tidak terbatas pada beberapa kasus super spesifik.
Steffen Winkler
2
Bahkan jika contoh kasus dipertanyakan, prinsipnya adalah yang terbaik untuk dipahami dan digunakan dalam skenario dunia nyata. Kueri yang ditampilkan berfungsi karena bergabung secara implisit dalam daftar tabel membuat tabel sementara untuk subquery, sehingga memberikan hasil yang stabil dan menghindari bentrokan antara pengambilan dan modifikasi tabel yang sama.
AnrDaemon
1
terlepas dari apa yang orang katakan tentang ini karena terlalu banyak pembunuhan, masih menjawab pertanyaan dalam hal judul. Kesalahan yang disebutkan OP juga ditemui ketika pembaruan dicoba menggunakan tabel yang sama dalam kueri bersarang
smac89
35
DELETE FROM story_category
WHERE category_id NOT IN (
    SELECT cid FROM (
        SELECT DISTINCT category.id AS cid FROM category INNER JOIN story_category ON category_id=category.id
    ) AS c
)
NexusRex
sumber
3
Bisakah Anda menjelaskan mengapa ini berhasil, mengapa hanya bersarang satu tingkat berfungsi? Pertanyaan ini telah diajukan sebagai komentar untuk pertanyaan @ EkoNoval tetapi tidak ada yang menjawab. Mungkin Anda bisa membantu.
Akshay Arora
@AkshayArora, Periksalah bagian di bawah judul 'Mungkin Anda bisa bergabung dengan tabel sendiri' di jawaban @ Cheekysoft. Dia berkata UPDATE tbl AS a INNER JOIN tbl AS b ON .... SET a.col = b.col- ini akan berfungsi sebagai alias berbeda untuk tabel yang sama digunakan di sini. Demikian pula dalam jawaban @ NexusRex SELECTkueri pertama bertindak sebagai tabel turunan yang story_categorydigunakan untuk kedua kalinya. Jadi kesalahan yang disebutkan dalam OP seharusnya tidak terjadi di sini, kan?
Istiaque Ahmed
31

Jika Anda tidak bisa melakukannya

UPDATE table SET a=value WHERE x IN
    (SELECT x FROM table WHERE condition);

karena ini adalah meja yang sama, Anda dapat menipu dan melakukan:

UPDATE table SET a=value WHERE x IN
    (SELECT * FROM (SELECT x FROM table WHERE condition) as t)

[perbarui atau hapus atau apa pun]

Sequoya
sumber
Implementasi yang paling dimengerti dan logis dari semua jawaban di atas. Sederhana dan to the point.
Clain Dsilva
Ini telah menjadi bagian dari alur kerja saya sekarang. Terjebak pada kesalahan ini, menuju ke jawaban ini dan memperbaiki kueri. Terima kasih.
Vaibhav
13

Ini adalah apa yang saya lakukan untuk memperbarui nilai kolom Prioritas dengan 1 jika itu adalah = = 1 dalam sebuah tabel dan dalam klausa WHERE yang menggunakan subquery pada tabel yang sama untuk memastikan bahwa setidaknya satu baris berisi Prioritas = 1 (karena itu adalah kondisi yang akan diperiksa saat melakukan pembaruan):


UPDATE My_Table
SET Priority=Priority + 1
WHERE Priority >= 1
AND (SELECT TRUE FROM (SELECT * FROM My_Table WHERE Priority=1 LIMIT 1) as t);

Saya tahu itu agak jelek tapi itu berfungsi dengan baik.

sactiw
sumber
1
@anonymous_reviewer: Dalam hal memberikan [-1] atau bahkan [+1] untuk komentar seseorang, tolong sebutkan juga mengapa Anda memberikannya. Terima kasih!!!
sactiw
1
-1 karena ini salah. Anda tidak bisa mengubah tabel yang sama yang Anda gunakan dalam pernyataan SELECT.
Chris
1
@ Chris Saya telah memverifikasi itu di MySQL dan berfungsi dengan baik untuk saya, jadi saya akan meminta Anda untuk memverifikasikannya di pihak Anda dan kemudian mengklaimnya benar atau salah. Terima kasih!!!
sactiw
1
Di bagian bawah halaman ini tertulis 'Saat ini, Anda tidak dapat memperbarui tabel dan memilih dari tabel yang sama dalam subquery.' - dan saya telah mengalami ini benar pada banyak kesempatan. dev.mysql.com/doc/refman/5.0/en/update.html
Chris
19
@ Chris Saya tahu itu, tetapi ada solusi untuk itu dan yang persis apa yang saya coba tunjukkan dengan permintaan 'PEMBARUAN' saya dan percayalah itu berfungsi dengan baik. Saya tidak berpikir Anda telah benar-benar mencoba memverifikasi permintaan saya sama sekali.
sactiw
6

Cara paling sederhana untuk melakukan ini adalah menggunakan tabel alias ketika Anda merujuk tabel kueri induk di dalam sub kueri.

Contoh:

insert into xxx_tab (trans_id) values ((select max(trans_id)+1 from xxx_tab));

Ubah ke:

insert into xxx_tab (trans_id) values ((select max(P.trans_id)+1 from xxx_tab P));
S.Roshanth
sumber
5

Anda bisa memasukkan id baris yang diinginkan ke tabel temp dan kemudian menghapus semua baris yang ditemukan di tabel itu.

yang mungkin apa yang dimaksud @Cheekysoft dengan melakukannya dalam dua langkah.

YonahW
sumber
3

Menurut Sintaks Mysql UPDATE ditautkan oleh @CheekySoft, ia mengatakan tepat di bagian bawah.

Saat ini, Anda tidak dapat memperbarui tabel dan memilih dari tabel yang sama di subquery.

Saya kira Anda menghapus dari store_category sambil memilih dari dalam serikat.

briankip
sumber
3

Untuk permintaan spesifik yang ingin dicapai oleh OP, cara ideal dan paling efisien untuk melakukan ini adalah TIDAK menggunakan subquery sama sekali.

Berikut adalah LEFT JOINversi dari dua kueri OP:

SELECT s.* 
FROM story_category s 
LEFT JOIN category c 
ON c.id=s.category_id 
WHERE c.id IS NULL;

Catatan: DELETE smembatasi operasi penghapusan ke story_categorytabel.
Dokumentasi

DELETE s 
FROM story_category s 
LEFT JOIN category c 
ON c.id=s.category_id 
WHERE c.id IS NULL;
Lakukan
sumber
1
Terkejut ini tidak memiliki suara lebih banyak. Perlu juga dicatat bahwa sintaks multi-tabel juga berfungsi dengan UPDATEpernyataan dan bergabung dengan sub-kueri. Memungkinkan Anda untuk melakukan yang LEFT JOIN ( SELECT ... )bertentangan WHERE IN( SELECT ... ), membuat implementasi bermanfaat di banyak kasus penggunaan.
fyrye
2

Jika sesuatu tidak berhasil, ketika datang melalui pintu depan, maka ambil pintu belakang:

drop table if exists apples;
create table if not exists apples(variety char(10) primary key, price int);

insert into apples values('fuji', 5), ('gala', 6);

drop table if exists apples_new;
create table if not exists apples_new like apples;
insert into apples_new select * from apples;

update apples_new
    set price = (select price from apples where variety = 'gala')
    where variety = 'fuji';
rename table apples to apples_orig;
rename table apples_new to apples;
drop table apples_orig;

Itu cepat. Semakin besar data, semakin baik.

Tom Schaefer
sumber
11
Dan Anda baru saja kehilangan semua kunci asing Anda, dan mungkin Anda memiliki beberapa penghapusan cascading juga.
Walf
2

Cobalah untuk menyimpan hasil pernyataan Pilih dalam variabel terpisah dan kemudian menggunakannya untuk menghapus kueri.

chakraborty lipika
sumber
2

coba ini

DELETE FROM story_category 
WHERE category_id NOT IN (
SELECT DISTINCT category.id 
FROM (SELECT * FROM STORY_CATEGORY) sc;
Sameer Khanal
sumber
1

bagaimana dengan kueri ini semoga membantu

DELETE FROM story_category LEFT JOIN (SELECT category.id FROM category) cat ON story_category.id = cat.id WHERE cat.id IS NULL
Melvin Angelo Jabonillo
sumber
Hasilnya menunjukkan: '# 1064 - Anda memiliki kesalahan dalam sintaks SQL Anda; periksa manual yang sesuai dengan versi server MariaDB Anda untuk sintaks yang tepat untuk digunakan di dekat 'LEFT JOIN (SELECT kategori.id DARI kategori) cat ON story_category.id = cat.' pada baris 1 '
Istiaque Ahmed
Saat menggunakan penghapusan multi-tabel, Anda harus menentukan tabel yang terpengaruh. DELETE story_category FROM ...namun, sub-kueri yang digabung tidak diperlukan dalam konteks ini dan dapat dilakukan dengan menggunakan LEFT JOIN category AS cat ON cat.id = story_category.category_id WHERE cat.id IS NULL, Perhatikan kriteria gabung dalam jawaban yang salah referensistory_category.id = cat.id
fyrye
0

Sejauh menyangkut masalah, Anda ingin menghapus baris story_categoryyang tidak ada di category.

Inilah permintaan awal Anda untuk mengidentifikasi baris yang akan dihapus:

SELECT * 
FROM  story_category 
WHERE category_id NOT IN (
    SELECT DISTINCT category.id 
    FROM category INNER JOIN 
       story_category ON category_id=category.id
);

Menggabungkan NOT INdengan subquery yang JOINmerupakan tabel asli tampaknya berbelit-belit tidak perlu. Ini dapat diekspresikan dengan cara yang lebih langsung dengan not existsdan subquery yang berkorelasi:

select sc.*
from story_category sc
where not exists (select 1 from category c where c.id = sc.category_id);

Sekarang mudah untuk mengubahnya menjadi deletepernyataan:

delete from story_category
where not exists (select 1 from category c where c.id = story_category.category_id);    

Pertanyaan ini akan berjalan pada versi MySQL apa pun, serta di sebagian besar database lain yang saya tahu.

Demo di DB Fiddle :

-- set-up
create table story_category(category_id int);
create table category (id int);
insert into story_category values (1), (2), (3), (4), (5);
insert into category values (4), (5), (6), (7);

-- your original query to identify offending rows
SELECT * 
FROM  story_category 
WHERE category_id NOT IN (
    SELECT DISTINCT category.id 
    FROM category INNER JOIN 
       story_category ON category_id=category.id);
| category_id |
| ----------: |
| 1 |
| 2 |
| 3 |
-- a functionally-equivalent, simpler query for this
select sc.*
from story_category sc
where not exists (select 1 from category c where c.id = sc.category_id)
| category_id |
| ----------: |
| 1 |
| 2 |
| 3 |
-- the delete query
delete from story_category
where not exists (select 1 from category c where c.id = story_category.category_id);

-- outcome
select * from story_category;
| category_id |
| ----------: |
| 4 |
| 5 |
GMB
sumber