J'essaie de faire quelque chose que je pensais que ce serait simple mais qui ne semble pas l'être.
J'ai un modèle de projet qui a de nombreux postes vacants.
class Project < ActiveRecord::Base
has_many :vacancies, :dependent => :destroy
end
Je souhaite obtenir tous les projets qui ont au moins 1 poste vacant. J'ai essayé quelque chose comme ça:
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)
.
Project.joins(:vacancies).distinct
?1) Pour obtenir des projets avec au moins 1 poste vacant:
2) Pour obtenir des projets avec plus d'un poste vacant:
3) Ou, si le
Vacancy
modèle définit le cache du compteur:alors cela fonctionnera aussi:
La règle d'inflexion pour
vacancy
peut devoir être spécifiée manuellement ?la source
Project.joins(:vacancies).group('projects.id').having('count(vacancies.id) > 1')
? Interrogation du nombre de postes vacants au lieu des identifiants de projetprojects.id
,project_id
etvacancies.id
. J'ai choisi de compterproject_id
car c'est le champ sur lequel se fait la jointure; la colonne vertébrale de la jointure si vous voulez. Cela me rappelle également qu'il s'agit d'une table de jointure.Ouais, ce
vacancies
n'est pas un champ dans la jointure. Je crois que tu veux:la source
la source
Effectuer une jointure interne à la table has_many combinée avec un
group
ouuniq
est potentiellement très inefficace, et en SQL, cela serait mieux implémenté comme une semi-jointure qui utiliseEXISTS
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 project_id correct. Peu importe qu'il y ait une ligne ou un million qui aient cet id_projet.
Ce n'est pas aussi simple dans Rails, mais peut être réalisé avec:
De même, recherchez tous les projets qui n'ont pas de poste vacant:
Edit: dans les versions récentes de Rails, vous recevez un avertissement d'obsolescence vous indiquant de ne pas compter sur la
exists
délégation à arel. Corrigez cela avec:Edit: si vous n'êtes pas à l'aise avec le SQL brut, essayez:
Vous pouvez rendre cela moins compliqué en ajoutant des méthodes de classe pour masquer l'utilisation de
arel_table
, par exemple:... alors ...
la source
Vacancy.where("vacancies.project_id = projects.id").exists?
renvoie soittrue
oufalse
.Project.where(true)
est unArgumentError
.Vacancy.where("vacancies.project_id = projects.id").exists?
ne va pas s'exécuter - cela déclenchera une erreur car laprojects
relation n'existera pas dans la requête (et il n'y a pas non plus de point d'interrogation dans l'exemple de code ci-dessus). Donc, décomposer cela en deux expressions n'est pas valide et ne fonctionne pas. Récemment, RailsProject.where(Vacancies.where("vacancies.project_id = projects.id").exists)
soulève un avertissement de dépréciation ... Je vais mettre à jour la question.Dans Rails 4+, vous pouvez également utiliser includes ou eager_load pour obtenir la même réponse:
la source
Je pense qu'il existe une solution plus simple:
la source
Sans beaucoup de magie Rails, vous pouvez faire:
Ce type de conditions fonctionnera dans toutes les versions de Rails car une grande partie du travail est effectuée directement du côté DB. De plus, la
.count
méthode de chaînage fonctionnera bien aussi. J'ai été brûlé par des requêtes commeProject.joins(:vacancies)
avant. Bien sûr, il y a des avantages et des inconvénients car ce n'est pas indépendant de DB.la source
Vous pouvez également utiliser
EXISTS
avecSELECT 1
plutôt que de sélectionner toutes les colonnes duvacancies
tableau:la source
L'erreur vous dit que les postes vacants ne sont pas essentiellement une rubrique dans les projets.
Cela devrait fonctionner
la source
aggregate functions are not allowed in WHERE