Masalah dengan subquery MySQL

16

Kenapa kueri ini

DELETE FROM test 
WHERE id = ( SELECT id 
             FROM (SELECT * FROM test) temp 
             ORDER BY RAND() 
             LIMIT 1
           );

kadang menghapus 1 baris, kadang 2 baris dan kadang tidak sama sekali?

Jika saya menulisnya di formulir ini:

SET @var = ( SELECT id 
             FROM (SELECT * FROM test) temp 
             ORDER BY RAND() 
             LIMIT 1
           ); 
DELETE FROM test 
WHERE id=@var;

lalu bekerja dengan benar - apakah ada masalah dalam subquery?

tomas.lang
sumber

Jawaban:

13

Alasan kueri pertama tidak berfungsi secara konsisten terkait dengan cara MySQL memproses subquery. Bahkan, subqueries akan mengalami penulisan ulang dan transformasi .

Ada empat (4) komponen yang dijelaskan di sini:

  • Item_in_optimizer
  • Item_in_subselect
  • Item_ref
  • Left_expression_Cache

Dari contoh yang diposting, tidak mungkin untuk membiarkan item_ref menjadi referensi mandiri. Dalam hal permintaan DELETE tunggal Anda, tabel pengujian secara keseluruhan tidak dapat sepenuhnya merujuk dirinya sendiri karena beberapa kunci tersedia selama transformasi dan beberapa tidak. Oleh karena itu, ketika kueri melakukan referensi-diri, kunci (dalam hal ini id) dapat menghilang dalam transformasi meskipun tabel referensi-sendiri yang sebenarnya memiliki kunci.

Subquery Mysql hanya bagus untuk sub-SELECT, bahkan merujuk sendiri tabel beberapa kali. Hal yang sama tidak dapat dikatakan untuk permintaan non-SELECT.

Saya harap penjelasan ini membantu.

RolandoMySQLDBA
sumber
7

Saya pikir alasan mengapa itu tidak berfungsi seperti yang diharapkan bukanlah bagaimana MySQL memproses subqueries tetapi bagaimana MySQL memproses UPDATEpernyataan. Pernyataan:

DELETE 
FROM test 
WHERE id = 
      ( SELECT id 
        FROM 
            ( SELECT * 
              FROM test
            ) temp 
        ORDER BY RAND() 
        LIMIT 1
      ) 

akan memproses WHEREkondisi baris demi baris. Artinya, untuk setiap baris, itu akan menjalankan subquery dan menguji hasilnya terhadap id:

  ( SELECT id 
    FROM 
        ( SELECT * 
          FROM test
        ) temp 
    ORDER BY RAND() 
    LIMIT 1
  ) 

Jadi, itu sesekali akan cocok (dan menghapus) 0, 1, 2 atau bahkan lebih banyak baris!


Anda dapat menulis ulang seperti ini dan subquery akan diproses sekali:

DELETE t
FROM 
      test t
  JOIN 
      ( SELECT id 
        FROM test  
        ORDER BY RAND() 
        LIMIT 1
      ) tmp
    ON tmp.id = t.id
ypercubeᵀᴹ
sumber
1

Dari butir pertama di halaman ini , LIMITtidak didukung di subqueries mysql. Saya tidak yakin mengapa itu tidak membuat kesalahan untuk Anda.

Derek Downey
sumber
2
LIMITtidak didukung hanya untuk menggunakan IN (<code> diganti dengan backticks ~ drachenstern)
tomas.lang
baik ... saya belajar sesuatu, yang menjelaskan mengapa itu tidak melemparkan kesalahan!
Derek Downey
@ tomas.lang Anda dapat menggunakan `(tanda centang) di sekitar kata, alih-alih blok <code>.
Derek Downey