Ich versuche etwas zu tun, von dem ich dachte, es wäre einfach, aber es scheint nicht so zu sein.
Ich habe ein Projektmodell mit vielen offenen Stellen.
class Project < ActiveRecord::Base
has_many :vacancies, :dependent => :destroy
end
Ich möchte alle Projekte erhalten, die mindestens 1 freie Stelle haben. Ich habe so etwas ausprobiert:
Project.joins(:vacancies).where('count(vacancies) > 0')
aber es heißt
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
verwendet standardmäßig einen inneren Join, so dass bei Verwendung von Project.joins(:vacancies)
nur Projekte zurückgegeben werden, denen eine Vakanz zugeordnet ist.
AKTUALISIEREN:
Wie von @mackskatz im Kommentar ohne group
-Klausel ausgeführt, gibt der obige Code doppelte Projekte für Projekte mit mehr als einer offenen Stelle zurück. Verwenden Sie zum Entfernen der Duplikate
Project.joins(:vacancies).group('projects.id')
1) Projekte mit mindestens 1 Vakanz zu erhalten:
Project.joins(:vacancies).group('projects.id')
2) Projekte mit mehr als 1 Vakanz zu erhalten:
Project.joins(:vacancies).group('projects.id').having('count(project_id) > 1')
3) Oder, wenn das Modell Vacancy
den Zählercache setzt:
belongs_to :project, counter_cache: true
dann funktioniert auch das:
Project.where('vacancies_count > ?', 1)
Flexionsregel für vacancy
muss möglicherweise manuell angegeben werden ?
Ja, vacancies
ist kein Feld im Join. Ich glaube du willst:
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')
In Rails 4+ können Sie auch includes oder eager_load verwenden, um dieselbe Antwort zu erhalten:
Project.includes(:vacancies).references(:vacancies).
where.not(vacancies: {id: nil})
Project.eager_load(:vacancies).where.not(vacancies: {id: nil})
Das Ausführen eines Inner-Joins für die Tabelle has_many in Kombination mit einer group
oder uniq
ist möglicherweise sehr ineffizient. In SQL wäre dies besser als Semi-Join zu implementieren, der EXISTS
mit einer korrelierten Unterabfrage verwendet.
Auf diese Weise kann das Abfrageoptimierungsprogramm die Tabelle "Vakanzen" durchsuchen, um zu überprüfen, ob eine Zeile mit der richtigen project_id vorhanden ist. Es spielt keine Rolle, ob es eine Zeile oder eine Million gibt, die diese project_id haben.
Das ist in Rails nicht so einfach, kann aber erreicht werden mit:
Project.where(Vacancies.where("vacancies.project_id = projects.id").exists)
Ebenso finden Sie alle Projekte, die keine offenen Stellen haben:
Project.where.not(Vacancies.where("vacancies.project_id = projects.id").exists)
Ohne viel Rails-Zauber können Sie Folgendes tun:
Project.where('(SELECT COUNT(*) FROM vacancies WHERE vacancies.project_id = projects.id) > 0')
Diese Art von Bedingungen funktioniert in allen Rails-Versionen, da ein Großteil der Arbeit direkt auf der DB-Seite ausgeführt wird. Außerdem funktioniert die Verkettung der .count
-Methode gut. Ich wurde vorher durch Abfragen wie Project.joins(:vacancies)
gebrannt. Natürlich gibt es Vor- und Nachteile, da es keine DB-Agnostiker sind.
Ich denke, es gibt eine einfachere Lösung:
Project.joins(:vacancies).uniq