SQL-запрос для исключения элементов на основе одного значения - PullRequest
3 голосов
/ 09 января 2009

Я вытягиваю список предметов из одной таблицы, исходя из того, что они включены в другую таблицу, например:

select fruit.id, fruit.name from fruit, fruit_rating where fruit_rating.fruit_id=fruit.id group by fruit.name;

Это прекрасно работает - в основном он создает список всех фруктов, которые были оценены кем-то. Но теперь я хочу исключить все фрукты, которые были оценены одним конкретным пользователем, поэтому я попробовал это:

select fruit.id, fruit.name from fruit, fruit_rating where fruit_rating.fruit_id=fruit.id and fruit_rating.user_id != 10 group by fruit.name;

Это нормально, но не совсем верно. Он показывает все фрукты, которые были оценены людьми, отличными от 10, но если пользователи 1 и 10 оценили один и тот же фрукт, он все равно показывает этот фрукт. Может кто-нибудь сказать мне, как построить запрос, который показывает только фрукты, которые НЕ были оценены пользователем 10, независимо от того, кто еще их оценил?

Ответы [ 4 ]

6 голосов
/ 09 января 2009
... WHERE fruit_rating.fruit_id=fruit.id 
      and fruit.id not in 
          (select fruit_rating.fruit_id 
             from fruit_rating 
            where fruit_rating.user_id = 10)
4 голосов
/ 09 января 2009

Я читаю это не так, как Кован, и согласен с Ноем ...

Найти все фрукты, где: - Пользователь 10 не оценил это - По крайней мере, еще один пользователь сделал оценку

Однако, по моему опыту, использование NOT IN может быть довольно медленным. Итак, я обычно предпочитаю фильтровать с использованием LEFT JOIN так же, как Cowan. Вот несколько вариантов, хотя я не успел протестировать производительность на больших наборах данных ...

SELECT
   [f].id,
   [f].name
FROM
   fruit           AS [f]
INNER JOIN
   fruit_rating    AS [fr]
      ON [fr].fruit_id = [f].id
GROUP BY
   [f].id,
   [f].name
HAVING
   SUM(CASE WHEN [fr_exclude].user_id = 10 THEN 1 ELSE 0 END) = 0


SELECT
   [f].id,
   [f].name
FROM
   fruit           AS [f]
INNER JOIN
   fruit_rating    AS [fr]
      ON [fr].fruit_id = [f].id
LEFT JOIN
   fruit_rating    AS [fr_exclude]
      ON [fr_exclude].fruit_id = [fr].fruit_id
      AND [fr_exclude].user_id = 10
GROUP BY
   [f].id,
   [f].name
HAVING
   MAX([fr_exclude].user_id) IS NULL


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

SELECT
   [f].id,
   [f].name
FROM
   fruit           AS [f]
INNER JOIN
   fruit_rating    AS [fr]
      ON [fr].fruit_id = [f].id
LEFT JOIN
   excluded_users  AS [ex]
      AND [ex].user_id = [fr].user_id
GROUP BY
   [f].id,
   [f].name
HAVING
   MAX([ex].user_id) IS NULL


Или что-то гораздо более длинное, но я подозреваю, что это самый быстрый набор больших данных с соответствующими индексами ...

SELECT
   [f].id,
   [f].name
FROM
   fruit           [f]
INNER JOIN
(
   SELECT
      fruit_id
   FROM
      fruit_rating
   GROUP BY
      fruit_id
)
   AS [rated]
      ON [rated].fruit_id = [f].id
LEFT JOIN
(
   SELECT
      [fr].fruit_id
   FROM
      fruit_rating    AS [fr]
   INNER JOIN
      excluded_users  AS [ex]
         ON [ex].user_id = [fr].user_id
   GROUP BY
      [fr].fruit_id
)
   AS [excluded]
      ON [rated].fruit_id = [excluded].fruit_id
WHERE
   [excluded].fruit_id IS NULL
GROUP BY
   [f].id,
   [f].name
3 голосов
/ 09 января 2009

Я немного уточнил ваш запрос, чтобы его было легче читать, и добавил подзапрос, чтобы отфильтровать все фрукты, которые были оценены пользователем 10

select f.id, f.name 
from fruit f
inner join fruit_rating fr on
 fr.fruit_id = f.id 
where f.id not in (
    select id
    from fruit_rating
    where [user_id] = 10) 
group by fruit.name;
1 голос
/ 09 января 2009

Одна вещь, которая мне не совсем понятна на 100%: хотите ли вы все фрукты, которые не были оценены пользователем 10, или просто фрукты, которые были оценены другими людьми, но не пользователь 10? например следует ли включать фрукты, которые не имеют оценок?

Я думаю вы хотите, чтобы все фрукты (включая без рейтинга), в этом случае ответы Ноя и мистера Браунстоуна, не совсем то, что вы ищете. Если вы удалите внутреннее соединение с fruit_rating, а теперь ненужную группу, то в их состав войдут не оцененные фрукты. Альтернативный подход, который позволяет избежать выбора, -

select f.id, f.name 
from fruit f
left join fruit_rating fr on
  (f.id = fr.fruit_id)
  and (fr.user_id = 10)
where
  (fr.user_id is null)

То есть, выполните левое соединение (если хотите, необязательное) для рейтинга фруктов ТОЛЬКО для пользователя 10, а затем возвращайте только те строки, в которых совпадение не найдено.

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