Найти записи, где нет объединения
У меня есть возможности ограничить все 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) }