MySQL: поиск строк, которые не участвуют в отношениях - PullRequest
23 голосов
/ 13 февраля 2009

У меня есть две таблицы: «фильмы» и «пользователи». Между ними существует отношение n: m, описывающее, какие фильмы видел пользователь. Это описано с помощью таблицы «видел» Теперь я хочу выяснить для данного пользователя все фильмы, которые он не видел. Мое текущее решение выглядит так:

SELECT *
FROM movies 
WHERE movies.id NOT IN (
     SELECT seen.movie_id 
     FROM seen 
     WHERE seen.user_id=123
)

Это работает нормально, но, похоже, не очень хорошо масштабируется. Есть ли лучший подход к этому?

Ответы [ 4 ]

33 голосов
/ 13 февраля 2009

Вот типичный способ выполнить этот запрос без использования показанного вами метода подзапроса. Это может удовлетворить запрос @ Godeke о поиске решения на основе объединения.

SELECT * 
FROM movies m
 LEFT OUTER JOIN seen s
 ON (m.id = s.movie_id AND s.user_id = 123)
WHERE s.movie_id IS NULL;

Однако в большинстве брендов баз данных это решение может работать хуже, чем решение подзапросов. Лучше всего использовать EXPLAIN для анализа обоих запросов, чтобы увидеть, какой из них будет работать лучше, учитывая вашу схему и данные.

Вот еще один вариант решения подзапроса:

SELECT * 
FROM movies m
WHERE NOT EXISTS (SELECT * FROM seen s 
                  WHERE s.movie_id = m.id 
                    AND s.user_id=123);

Это коррелированный подзапрос, который должен оцениваться для каждой строки внешнего запроса. Обычно это дорого, и ваш оригинальный пример запроса лучше. С другой стороны, в MySQL «NOT EXISTS» часто лучше, чем «column NOT IN (...)»

Опять же, вы должны проверить каждое решение и сравнить результаты, чтобы убедиться. Выбор любого решения без измерения производительности - пустая трата времени.

4 голосов
/ 13 февраля 2009

Видна ваша таблица соединений, так что да, это похоже на правильное решение. Вы фактически «вычитаете» набор идентификаторов фильмов в SEEN (для пользователя) из совокупности в MOVIES, что приводит к появлению невидимых фильмов для этого пользователя.

Это называется "отрицательное соединение", и, к сожалению, НЕ ВНУТРИ или НЕ СУЩЕСТВУЮТ - лучшие варианты. (Я хотел бы видеть синтаксис отрицательного соединения, который был бы похож на соединения INNER / OUTER / LEFT / RIGHT, но где предложение ON могло бы быть оператором вычитания).

@ Решение Билла без подзапроса должно работать, хотя, как он отметил, было бы неплохо проверить ваше решение на производительность в обоих направлениях. Я подозреваю, что подзапрос или нет, весь индекс SEEN.ID (и, конечно, весь индекс MOVIE.ID) будет оцениваться в обоих направлениях: это будет зависеть от того, как оптимизатор оттуда его обработает.

4 голосов
/ 13 февраля 2009

Мало того, что ваш запрос работает, это правильный подход к проблеме, как указано. Возможно, вы можете найти другой способ решения проблемы? Простой LIMIT для вашего внешнего выбора должен быть очень быстрым даже для больших таблиц, например.

0 голосов
/ 13 февраля 2009

Если ваша СУБД поддерживает растровые индексы, вы можете попробовать их.

...