Problème avec la sous-requête MySQL

16

Pourquoi cette requête

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

parfois supprimer 1 ligne, parfois 2 lignes et parfois rien?

Si je l'écris sous cette forme:

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

alors cela fonctionne correctement - est un problème dans la sous-requête?

tomas.lang
la source

Réponses:

13

La raison pour laquelle la première requête ne fonctionne pas de manière cohérente est liée à la façon dont MySQL traite les sous-requêtes. En fait, les sous - requêtes connaîtront des réécritures et des transformations .

Il y a quatre (4) composants expliqués ici:

  • Item_in_optimizer
  • Item_in_subselect
  • Item_ref
  • Left_expression_Cache

D'après les exemples publiés, il serait impossible de permettre à un item_ref de devenir une auto référence. En ce qui concerne votre seule requête DELETE, la table de test dans son ensemble ne peut pas se référencer entièrement elle-même car certaines clés sont disponibles pendant la transformation et d'autres non. Par conséquent, lorsqu'une requête effectue une auto-référence, une clé (dans ce cas id) peut disparaître dans une transformation même si la table auto-référencée réelle a la clé.

Les sous-requêtes Mysql ne sont idéales que pour les sous-SELECT, même en faisant plusieurs fois référence à une table. La même chose ne peut pas être dite pour les requêtes non SELECT.

J'espère que cette explication aide.

RolandoMySQLDBA
la source
7

Je pense que la raison pour laquelle cela ne fonctionne pas comme prévu n'est pas comment MySQL traite les sous-requêtes mais comment MySQL traite les UPDATEinstructions. La déclaration:

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

traitera la WHEREcondition ligne par ligne. Cela signifie que pour chaque ligne, il exécutera la sous-requête et testera le résultat par rapport à id:

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

Ainsi, il correspondra parfois (et supprimera) 0, 1, 2 ou même plus de lignes!


Vous pouvez le réécrire comme ceci et la sous-requête sera traitée une fois:

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

À partir de la première puce de cette page , LIMITn'est pas pris en charge dans les sous-requêtes mysql. Je ne sais pas pourquoi cela ne génère pas d'erreur pour vous, cependant.

Derek Downey
la source
2
LIMITn'est pas pris en charge uniquement pour l'utilisation IN (<code> remplacé par des backticks ~ drachenstern)
tomas.lang
eh bien ... j'ai appris quelque chose, ce qui explique pourquoi ça n'a pas jeté une erreur!
Derek Downey
@ tomas.lang vous pouvez utiliser `(coche) entourant le mot, au lieu des blocs <code>.
Derek Downey