web-dev-qa-db-fra.com

Trouver tous les enregistrements dont le nombre d'associations est supérieur à zéro

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

66
JPHorta

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')
46
jvnill

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 ?

126
Arta

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")
26
Peter Alfvin
# 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')
10
Dorian

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})
1
konyak

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)
1
David Aldridge

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.

0
konyak

Je pense qu'il existe une solution plus simple:

Project.joins(:vacancies).uniq
0
Yuri Karpovich