Problem z podkwerendą MySQL

16

Dlaczego to zapytanie?

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

czasami usuwam 1 wiersz, czasem 2 wiersze, a czasem nic?

Jeśli napiszę to w tej formie:

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

to działa poprawnie - czy jest problem w podzapytaniu?

tomas.lang
źródło

Odpowiedzi:

13

Powód, dla którego pierwsze zapytanie nie działa konsekwentnie, ma związek ze sposobem, w jaki MySQL przetwarza podkwerendy. W rzeczywistości podzapytania będą podlegać przepisywaniu i przekształcaniu .

Wyjaśniono tutaj cztery (4) elementy:

  • Item_in_optimizer
  • Item_in_subselect
  • Pozycja_ref
  • Left_expression_Cache

Z opublikowanych przykładów nie byłoby możliwe, aby item_ref stał się samodzielnym odnośnikiem. Jeśli chodzi o jedno zapytanie DELETE, tabela testowa jako całość nie może w pełni odwoływać się do siebie, ponieważ niektóre klucze są dostępne podczas transformacji, a inne nie. Dlatego, gdy zapytanie wykonuje samodzielne odwołanie, klucz (w tym przypadku id) może zniknąć w transformacji, nawet jeśli rzeczywista tabela z odnośnikiem zawiera klucz.

Podkwerendy MySQL świetnie nadają się tylko do podselekcji, nawet do wielu odniesień do tabeli. Tego samego nie można powiedzieć o zapytaniach innych niż SELECT.

Mam nadzieję, że to wyjaśnienie pomoże.

RolandoMySQLDBA
źródło
7

Myślę, że powodem, dla którego nie działa zgodnie z oczekiwaniami, nie jest sposób, w jaki MySQL przetwarza podzapytania, ale sposób, w jaki MySQL przetwarza UPDATEinstrukcje. Wyrok:

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

przetworzy WHEREwarunek wiersz po rzędzie. Oznacza to, że dla każdego wiersza uruchomi podzapytanie i przetestuje wynik względem id:

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

Od czasu do czasu dopasuje (i usunie) 0, 1, 2 lub nawet więcej wierszy!


Możesz przepisać go w ten sposób, a podzapytanie zostanie przetworzone raz:

DELETE t
FROM 
      test t
  JOIN 
      ( SELECT id 
        FROM test  
        ORDER BY RAND() 
        LIMIT 1
      ) tmp
    ON tmp.id = t.id
ypercubeᵀᴹ
źródło
1

Od pierwszego pocisku na tej stronie , LIMITnie jest obsługiwana w MySQL podzapytania. Nie jestem jednak pewien, dlaczego nie powoduje to błędu.

Derek Downey
źródło
2
LIMITnie jest obsługiwany tylko w przypadku używania IN (<kod> zastąpiony backtickami ~ drachenstern)
tomas.lang 21.02.11
cóż ... Nauczyłem się czegoś, co wyjaśnia, dlaczego nie rzucił błędu!
Derek Downey
@ tomas.lang możesz użyć `(znaczników) otaczających słowo, zamiast bloków <code>.
Derek Downey