Найти все записи, которые имеют счетчик больше нуля
Я пытаюсь сделать что-то, что, по-моему, было бы просто, но, похоже, это не так.
У меня есть модель проекта, у которой много вакансий.
class Project < ActiveRecord::Base
has_many :vacancies, :dependent => :destroy
end
Я хочу получить все проекты, у которых есть как минимум 1 вакансия.
Я пробовал что-то вроде этого:
Project.joins(:vacancies).where('count(vacancies) > 0')
но он говорит
SQLite3::SQLException: no such column: vacancies: SELECT "projects".* FROM "projects" INNER JOIN "vacancies" ON "vacancies"."project_id" = "projects"."id" WHERE ("projects"."deleted_at" IS NULL) AND (count(vacancies) > 0)
.
Ответы
Ответ 1
joins
использует внутреннее соединение по умолчанию, поэтому использование Project.joins(:vacancies)
будет фактически возвращать проекты, у которых есть связанная вакансия.
UPDATE:
Как указано в комментарии @mackskatz в комментарии, без предложения group
, код выше вернет дублирующие проекты для проектов с более чем одной вакансией. Чтобы удалить дубликаты, используйте
Project.joins(:vacancies).group('projects.id')
Ответ 2
1) Чтобы получить проекты с не менее чем 1 вакансии:
Project.joins(:vacancies).group('projects.id')
2) Чтобы получить проекты с более чем 1 вакансии:
Project.joins(:vacancies).group('projects.id').having('count(project_id) > 1')
3) Или, если модель Vacancy
задает счетчик кеша:
belongs_to :project, counter_cache: true
тогда это тоже будет работать:
Project.where('vacancies_count > ?', 1)
Правило исключения для Vacancy
возможно, должно быть указано вручную?
Ответ 3
Да, vacancies
не является полем в соединении. Я считаю, что вы хотите:
Project.joins(:vacancies).group("projects.id").having("count(vacancies.id)>0")
Ответ 4
# None
Project.joins(:vacancies).group('projects.id').having('count(vacancies) = 0')
# Any
Project.joins(:vacancies).group('projects.id').having('count(vacancies) > 0')
# One
Project.joins(:vacancies).group('projects.id').having('count(vacancies) = 1')
# More than 1
Project.joins(:vacancies).group('projects.id').having('count(vacancies) > 1')
Ответ 5
В Rails 4+ вы также можете использовать includes или eager_load, чтобы получить тот же ответ:
Project.includes(:vacancies).references(:vacancies).
where.not(vacancies: {id: nil})
Project.eager_load(:vacancies).where.not(vacancies: {id: nil})
Ответ 6
Я думаю, что есть более простое решение:
Project.joins(:vacancies).distinct
Ответ 7
Выполнение внутреннего соединения с таблицей has_many в сочетании с group
или uniq
потенциально очень неэффективно, и в SQL это лучше реализовать в виде полусоединения, в котором используется EXISTS
с коррелированным подзапросом.
Это позволяет оптимизатору запросов проверять таблицу вакансий, чтобы проверить наличие строки с правильным идентификатором project_id. Неважно, есть ли одна строка или миллион, у которых есть этот project_id.
Это не так просто в Rails, но может быть достигнуто с помощью:
Project.where(Vacancies.where("vacancies.project_id = projects.id").exists)
Аналогичным образом найдите все проекты, в которых нет вакансий:
Project.where.not(Vacancies.where("vacancies.project_id = projects.id").exists)
Редактировать: в последних версиях Rails вы получаете предупреждение об устаревании, говорящее вам не полагаться на то, что exists
делегирован arel. Исправьте это с помощью:
Project.where.not(Vacancies.where("vacancies.project_id = projects.id").arel.exists)
Ответ 8
Без особой магии Rails вы можете сделать:
Project.where('(SELECT COUNT(*) FROM vacancies WHERE vacancies.project_id = projects.id) > 0')
Условия такого типа будут работать во всех версиях Rails, так как большая часть работы выполняется непосредственно на стороне БД. Кроме того, метод цепочки .count
тоже будет работать. Я был сожжен вопросами вроде Project.joins(:vacancies)
раньше. Конечно, есть плюсы и минусы, так как это не агностика БД.
Ответ 9
Ошибка говорит вам, что вакансии не являются столбцом проектов, в основном.
Это должно работать
Project.joins(:vacancies).where('COUNT(vacancies.project_id) > 0')