Найти ряды, в которых многие дети не удовлетворяют определенным условиям. - PullRequest
1 голос
/ 19 ноября 2010

Вот общая версия того, что я пытаюсь сделать:

Таблица recipes имеет поля id и name. В таблице ingredients есть поля id, name и sweetness, описывающие, насколько сладок этот ингредиент по шкале от 1 до 10. Рецепты содержат много ингредиентов, а ингредиенты есть во многих рецептах, поэтому оба они связаны в таблице ingredients_recipes с полями ingredient_id и recipe_id.

Легко найти рецепты, содержащие ингредиент со сладостью 10.

SELECT DISTINCT recipes.* FROM recipes
INNER JOIN recipes_ingredients ri ON ri.recipe_id = recipes.id
INNER JOIN ingredients ON ingredients.id = ri.ingredient_id
WHERE ingredients.sweetness = 10

Однако у меня возникли проблемы с отменой этого запроса, чтобы найти рецепты с без ингредиентов со сладостью 10. Моя первая мысль была такой:

SELECT DISTINCT recipes.* FROM recipes
INNER JOIN recipes_ingredients ri ON ri.recipe_id = recipes.id
INNER JOIN ingredients ON ingredients.id = ri.ingredient_id
WHERE ingredients.sweetness != 10

Однако, здесь можно найти рецепты, содержащие любых не сладких ингредиентов - 10 .

Моя следующая попытка была следующей, которая, кажется, работает:

SELECT * FROM recipes WHERE
(
  SELECT count(*) FROM ingredients INNER JOIN recipes_ingredients ri ON
  ri.ingredient_id = ingredients.id WHERE ingredients.sweetness = 10 AND
  ri.recipe_id = recipes.id
) = 0

Однако мой общий опыт заключается в том, что зависимые подзапросы выполняются медленно по сравнению с эквивалентными, хорошо разработанными JOIN. Я играл с объединениями, группировкой и т. Д., Но не мог полностью обдумать это, тем более что, хотя кажется, что LEFT JOIN и IS NULL были подходящими инструментами, два соединения уже делали вещи неприятными. Великие мастера SQL, какой запрос я могу выполнить, чтобы получить наилучшие результаты? Спасибо!

Ответы [ 3 ]

1 голос
/ 19 ноября 2010

Попробуйте:

select
  r.*
from
  recipes r
where
  not exists (
    select
      1
    from
      recipe_ingredients ri
      join ingredients i on ri.ingredient_id = ri.ingredient_id
    where
      ri.recipie_id = r.recipe_id
      and i.sweetness = 10
  )

Это все еще коррелированный подзапрос, но у exists и not exists есть некоторые оптимизации, которые должны заставить их работать лучше, чем ваш исходный запрос.

Для решения с прямым соединением это должно работать:

select distinct
  r.*
from
  recipes r
  join recipe_ingredients ri on ri.recipe_id = r.recipe_id
  left join ingredents i on i.ingredient_id = ri.ingredient_id and i.sweetness = 10
where
  i.ingredient_id is null

В зависимости от индексации решение not exists может быть быстрее, так как not exists возвращается сразу после выяснения, удовлетворяют ли какие-либо строки заданным условиям, не просматривая таблицы больше, чем необходимо. Например, если он находит одну строку сладости 10, он перестает смотреть на стол и возвращает ложь.

1 голос
/ 19 ноября 2010

Попробуйте это:

SELECT DISTINCT recipes.* 
FROM recipes r LEFT JOIN
(SELECT ri.recipe_id
FROM recipes_ingredients ri 
INNER JOIN ingredients ON ingredients.id = ri.ingredient_id
WHERE ingredients.sweetness = 10) i on i.recipe_id=r.recipe_id
WHERE i.recipe_id is null
0 голосов
/ 19 ноября 2010

Я поигрался с ответами, данными мне здесь (которые я с тех пор проголосовал), и, исходя из их вдохновения, придумал запрос, который, кажется, выполняет работу с удивительно выдающейся производительностью:

SELECT r.* FROM recipes r
LEFT JOIN recipes_ingredients ri ON ri.parent_id = r.id
LEFT JOIN ingredients i ON i.id = ri.ingredient_id AND i.sweetness = 10
GROUP BY r.id HAVING MAX(i.id) IS NULL

Соединения с условием внутри (по вдохновению @Donnie) выявляют комбинации рецепт-ингредиент со строками NULL, если ингредиент не сладкий 10. Затем мы группируем по идентификатору рецепта и выбираем «максимальный» идентификатор ингредиента.(Функция MAX будет возвращать ноль в том и только в том случае, если нет фактических идентификаторов для выбора, т. Е. Нет абсолютно никаких предметов не-сладости-10, связанных с этим рецептом, для выбора.) Если этот «максимальный» идентификатор ингредиентаnull, тогда не было элементов sweetness-10 для функции MAX для выбора, и поэтому выбираются строки HAVING null MAX(i.id).

Я запустил обе версии NOT EXISTS запросаи вышеупомянутая версия запроса несколько раз с отключенным кешированием запросов.По сравнению с примерно 400 рецептами, выполнение запроса NOT EXISTS всегда занимало бы около 1,0 секунды, тогда как время выполнения этого запроса обычно составляло около 0,1 секунды.Для примерно 5000 рецептов запрос NOT EXISTS занял около 30 секунд, тогда как вышеупомянутый запрос обычно занимал 0,1 секунды и почти всегда был ниже 1,0.

Стоит отметить, что при проверке EXPLAINs для каждого запросаПеречисленные здесь могут работать почти полностью по индексам, которые я дал этим таблицам, что, вероятно, объясняет, почему он может выполнять все виды объединения и группировки, не падая глазом.Запрос NOT EXISTS, с другой стороны, должен выполнять зависимые подзапросы.Эти два показателя могли бы работать более равномерно, если бы эти индексы не были на месте, но этот оптимизатор запросов был бы чертовски мощным, если бы у него была возможность использовать необработанные объединения. Казалось бы,

Мораль истории: хорошо сформированныйСОЕДИНЕНИЯ супер-мощные :) Спасибо всем!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...