Проблема с подзапросом MySQL

16

Почему этот запрос

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

иногда удаляют 1 строку, иногда 2 строки, а иногда ничего?

Если я напишу это в этой форме:

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

тогда это работает правильно - это проблема в подзапросе?

tomas.lang
источник

Ответы:

13

Причина, по которой первый запрос не работает согласованно, связана с тем, как MySQL обрабатывает подзапросы. На самом деле, подзапросы будут переписаны и преобразованы .

Здесь объясняется четыре (4) компонента:

  • Item_in_optimizer
  • Item_in_subselect
  • Item_ref
  • Left_expression_Cache

Из приведенных примеров было бы невозможно позволить item_ref стать собственной ссылкой. С точки зрения вашего единственного запроса DELETE, тестовая таблица в целом не может полностью самостоятельно ссылаться на себя, потому что некоторые ключи доступны во время преобразования, а некоторые нет. Следовательно, когда запрос выполняет самоссылку, ключ (в данном случае id) может исчезнуть в преобразовании, даже если фактическая таблица с самоссылкой имеет ключ.

Подзапросы Mysql хороши только для вложенных SELECT, даже если они ссылаются на таблицу несколько раз. То же самое нельзя сказать о невыбранных запросах.

Я надеюсь, что это объяснение помогает.

RolandoMySQLDBA
источник
7

Я думаю, что причина, почему он не работает должным образом, не в том, как MySQL обрабатывает подзапросы, а в том, как MySQL обрабатывает UPDATEоператоры. Заявление:

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

будет обрабатывать WHEREусловие строка за строкой. Это означает, что для каждой строки он будет запускать подзапрос и проверять результат по id:

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

Таким образом, он иногда будет соответствовать (и удалять) 0, 1, 2 или даже больше строк!


Вы можете переписать его так, и подзапрос будет обработан один раз:

DELETE t
FROM 
      test t
  JOIN 
      ( SELECT id 
        FROM test  
        ORDER BY RAND() 
        LIMIT 1
      ) tmp
    ON tmp.id = t.id
ypercubeᵀᴹ
источник
1

От первой пули на этой странице , LIMITне поддерживается в MySQL подзапросов. Я не уверен, почему это не выдает ошибку для вас, хотя.

Дерек Дауни
источник
2
LIMITне поддерживается только для использования IN (<code> заменено на backticks ~ drachenstern)
tomas.lang
хорошо ... я узнал кое-что, что объясняет, почему это не бросило ошибку!
Дерек Дауни
@ tomas.lang вы можете использовать `(галочки) вокруг слова вместо блоков <code>.
Дерек Дауни