Подтвердить что ты не робот

Найти все записи, которые имеют счетчик больше нуля

Я пытаюсь сделать что-то, что, по-моему, было бы просто, но, похоже, это не так.

У меня есть модель проекта, у которой много вакансий.

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).

4b9b3361

Ответ 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')