Найти записи, где нет объединения

У меня есть возможности ограничить все questions по тому, проголосовал ли пользователь за них. В модели:

scope :answered_by, lambda {|u| joins(:votes).where("votes.user_id = ?", u.id) }
scope :unanswered_by, lambda {|u| joins(:votes).where("votes.user_id != ?", u.id) }

В контроллере я называю их так:

@answered = Question.answered_by(current_user)
@unanswered = Question.unanswered_by(current_user)

Область unanspted_by неверна. Я по сути хочу найти там, где нет голосования. Вместо этого он пытается найти голосование, которое не соответствует текущему пользователю. Любые идеи, как вернуть все записи, где объединение не существует?

3 ответа

Решение

Используйте EXISTS анти-полусоединение:

WHERE NOT EXISTS (
   SELECT 1
   FROM   votes v
   WHERE  v.some_id = base_table.some_id
   AND    v.user_id = ?
   )

Различия

... между NOT EXISTS() (е) и NOT IN() (я) имеет два аспекта:

  • Спектакль
    (е), как правило, быстрее. Он может остановить сканирование таблицы или индекса, как только будет найдена первая подходящая строка. (i) также может быть оптимизирован планировщиком запросов, но в меньшей степени, так как NULL обработка сложнее.

  • правильность
    Если одно из результирующих значений в выражении подзапроса может быть NULL результат (i) может быть NULL где вы ожидаете - и (е) вернется - TRUE, Руководство:

Если все результаты для каждой строки являются неравными или нулевыми, хотя бы с одним нулем, то результат NOT IN нулевой.

По существу, (NOT) EXISTS это лучший выбор в большинстве случаев.

пример

Ваш запрос может выглядеть так:

SELECT *
FROM   questions q
WHERE  NOT EXISTS (
    SELECT 1
    FROM   votes v 
    WHERE  v.question_id = q.id
    AND    v.user_id = ?
    )

Не присоединяйтесь к votes в базовом запросе. Это лишило бы усилий.

Кроме того NOT EXISTS а также NOT IN существует также LEFT JOIN / IS NULL, Больше вариантов синтаксиса в этом связанном ответе:

И если вы хотите сделать EXISTS запрос в элегантном и Rails-ишем стиле, вы можете использовать Где я написал Gem:

Question.where_not_exists(:votes, user_id: current_user.id)

Конечно, вы можете сделать это также:

scope :unanswered_by, ->(user){ where_not_exists(:votes, user_id: user.id) }

Попробуйте это и дайте мне знать, если это работает

EDIT-1

scope :unanswered_questions, lambda { joins('LEFT OUTER JOIN votes ON questions.id = votes.question_id').where('votes.question_id IS NULL') }

РЕДАКТИРОВАТЬ-2

scope :unanswered_by, lambda {|u| where("questions.id NOT IN (SELECT votes.question_id from votes where votes.user_id = ?)",u.id) }
Другие вопросы по тегам