J'essaie de faire quelque chose que je pensais être simple mais qui semble ne pas l'être.
J'ai un modèle de projet qui a beaucoup de postes vacants.
class Project < ActiveRecord::Base
has_many :vacancies, :dependent => :destroy
end
Je veux obtenir tous les projets qui ont au moins un poste vacant ..__J'ai essayé quelque chose comme ceci:
Project.joins(:vacancies).where('count(vacancies) > 0')
mais ça dit
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)
.
joins
utilise par défaut une jointure interne. Par conséquent, l'utilisation de Project.joins(:vacancies)
ne renvoie en réalité que les projets associés à une offre d'emploi.
METTRE À JOUR:
Comme l'a souligné @mackskatz dans le commentaire, sans la clause group
, le code ci-dessus renverra les projets en double pour les projets comportant plusieurs offres d'emploi. Pour supprimer les doublons, utilisez
Project.joins(:vacancies).group('projects.id')
1) Pour obtenir des projets avec au moins 1 poste vacant:
Project.joins(:vacancies).group('projects.id')
2) Pour obtenir des projets avec plus d'un poste vacant:
Project.joins(:vacancies).group('projects.id').having('count(project_id) > 1')
3) Ou, si le modèle Vacancy
définit le cache de compteur:
belongs_to :project, counter_cache: true
alors cela fonctionnera aussi:
Project.where('vacancies_count > ?', 1)
La règle d'inflexion pour vacancy
devra peut-être être spécifiée manuellement ?
Oui, vacancies
n'est pas un champ dans la jointure. Je crois que tu veux:
Project.joins(:vacancies).group("projects.id").having("count(vacancies.id)>0")
# 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')
Dans Rails 4+, vous pouvez également utiliser includes ou eager_load pour obtenir la même réponse:
Project.includes(:vacancies).references(:vacancies).
where.not(vacancies: {id: nil})
Project.eager_load(:vacancies).where.not(vacancies: {id: nil})
Effectuer une jointure interne à la table has_many associée à une variable group
ou uniq
est potentiellement très inefficace et, dans SQL, cela serait mieux implémenté comme une semi-jointure utilisant EXISTS
avec une sous-requête corrélée.
Cela permet à l’optimiseur de requêtes de sonder la table des postes vacants pour vérifier l’existence d’une ligne avec le bon project_id. Peu importe qu'il y ait une ligne ou un million avec ce project_id.
Ce n'est pas aussi simple dans Rails, mais peut être atteint avec:
Project.where(Vacancies.where("vacancies.project_id = projects.id").exists)
De même, recherchez tous les projets qui n'ont pas de postes vacants:
Project.where.not(Vacancies.where("vacancies.project_id = projects.id").exists)
Sans trop de magie Rails, vous pouvez faire:
Project.where('(SELECT COUNT(*) FROM vacancies WHERE vacancies.project_id = projects.id) > 0')
Ce type de conditions fonctionnera dans toutes les versions de Rails car une grande partie du travail est effectuée directement du côté de la base de données. De plus, l'enchaînement de la méthode .count
fonctionnera également. J'ai déjà été brûlé par des requêtes telles que Project.joins(:vacancies)
. Bien sûr, il y a des avantages et des inconvénients, car ce n'est pas agnostique envers la DB.
Je pense qu'il existe une solution plus simple:
Project.joins(:vacancies).uniq