Problema com a subconsulta do MySQL

16

Por que essa consulta

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

às vezes excluir 1 linha, às vezes 2 linhas e às vezes nada?

Se eu escrevê-lo desta forma:

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

então ele funciona corretamente - há problema na subconsulta?

tomas.lang
fonte

Respostas:

13

A razão pela qual a primeira consulta não funciona consistentemente tem a ver com o modo como o MySQL processa subconsultas. De fato, as subconsultas sofrerão reescritas e transformações .

Existem quatro (4) componentes explicados aqui:

  • Item_in_optimizer
  • Item_in_subselect
  • Item_ref
  • Left_expression_Cache

A partir dos exemplos publicados, seria impossível permitir que um item_ref se tornasse uma referência própria. Em termos de sua única consulta DELETE, a tabela de teste como um todo não pode se auto-referenciar completamente porque algumas chaves estão disponíveis durante a transformação e outras não. Portanto, quando uma consulta executa uma auto-referência, uma chave (nesse caso, id) pode desaparecer em uma transformação, mesmo que a tabela auto-referenciada real tenha a chave.

As subconsultas do Mysql são ótimas apenas para sub-SELECTs, mesmo fazendo referência automática a uma tabela várias vezes. O mesmo não pode ser dito para consultas não SELECT.

Espero que esta explicação ajude.

RolandoMySQLDBA
fonte
7

Eu acho que a razão pela qual não funciona como esperado não é como o MySQL processa subconsultas, mas como o MySQL processa UPDATEinstruções. A declaração:

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

processará a WHEREcondição linha por linha. Ou seja, para cada linha, ele executará a subconsulta e testará o resultado com relação a id:

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

Portanto, ocasionalmente corresponde (e exclui) 0, 1, 2 ou até mais linhas!


Você pode reescrevê-lo assim e a subconsulta será processada uma vez:

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

Desde o primeiro marcador nesta página , LIMITnão há suporte para subconsultas mysql. Não sei por que isso não gera um erro para você.

Derek Downey
fonte
2
LIMITnão é suportado somente para o uso IN (<code> substituído com acentos graves ~ drachenstern)
tomas.lang
bem ... eu aprendi alguma coisa, o que explica por que não ocorreu um erro!
Derek Downey
@ tomas.lang você pode usar `(marcas) ao redor da palavra, em vez dos blocos <code>.
Derek Downey