Próbuję zrobić coś, co wydawało mi się proste, ale wydaje się, że tak nie jest.
Mam model projektu z wieloma wakatami.
class Project < ActiveRecord::Base
has_many :vacancies, :dependent => :destroy
end
Chcę otrzymać wszystkie projekty, które mają co najmniej 1 wakat. Próbowałem czegoś takiego:
Project.joins(:vacancies).where('count(vacancies) > 0')
ale mówi
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) Aby otrzymać projekty z co najmniej 1 wakatem:
2) Aby otrzymać projekty z więcej niż 1 wakatem:
3) Lub, jeśli
Vacancy
model ustawia pamięć podręczną liczników:to też zadziała:
vacancy
Może trzeba określić regułę fleksji dla ręcznie ?źródło
Project.joins(:vacancies).group('projects.id').having('count(vacancies.id) > 1')
? Zapytanie o liczbę wolnych miejsc pracy zamiast identyfikatorów projektówprojects.id
,project_id
orazvacancies.id
. Zdecydowałem się liczyć,project_id
ponieważ jest to pole, na którym jest wykonywane łączenie; kręgosłup połączenia, jeśli chcesz. Przypomina mi również, że jest to stół do wspólnego stołu.Tak,
vacancies
nie jest polem w złączeniu. Wierzę, że chcesz:źródło
źródło
Wykonywanie sprzężenia wewnętrznego do tabeli has_many w połączeniu z
group
lubuniq
jest potencjalnie bardzo nieefektywne, aw SQL byłoby lepiej zaimplementowane jako półzłączenie, które używaEXISTS
skorelowanego podzapytania.Dzięki temu optymalizator zapytań może sondować tabelę wakatów w celu sprawdzenia, czy istnieje wiersz z poprawnym project_id. Nie ma znaczenia, czy istnieje jeden wiersz, czy milion, które mają ten identyfikator projektu.
Nie jest to takie proste w Railsach, ale można to osiągnąć za pomocą:
Podobnie znajdź wszystkie projekty, w których nie ma wolnych miejsc pracy:
Edycja: w ostatnich wersjach Railsów otrzymujesz ostrzeżenie o wycofaniu, informujące, że nie możesz polegać na
exists
delegowaniu na arel. Napraw to za pomocą:Edycja: jeśli nie czujesz się komfortowo z surowym SQL, spróbuj:
Możesz zrobić to mniej bałaganem, dodając metody klasowe, aby ukryć użycie
arel_table
, na przykład:... więc ...
źródło
Vacancy.where("vacancies.project_id = projects.id").exists?
daje albotrue
albofalse
.Project.where(true)
jestArgumentError
.Vacancy.where("vacancies.project_id = projects.id").exists?
nie zostanie wykonany - spowoduje to zgłoszenie błędu, ponieważprojects
relacja nie będzie istniała w zapytaniu (aw przykładowym kodzie powyżej również nie ma znaku zapytania). Więc rozkładanie tego na dwa wyrażenia nie jest poprawne i nie działa. W ostatnich RailsachProject.where(Vacancies.where("vacancies.project_id = projects.id").exists)
pojawia się ostrzeżenie o wycofaniu ... Zaktualizuję pytanie.W Railsach 4+ możesz także użyć include lub eager_load, aby uzyskać tę samą odpowiedź:
źródło
Myślę, że jest prostsze rozwiązanie:
źródło
Bez dużej magii Railsów możesz:
Tego typu warunki będą działać we wszystkich wersjach Railsów, ponieważ większość pracy jest wykonywana bezpośrednio po stronie DB. Poza tym
.count
metoda łączenia w łańcuch też będzie działać dobrze. Wypalały mnie zapytania, jakProject.joins(:vacancies)
wcześniej. Oczywiście są plusy i minusy, ponieważ nie jest agnostykiem DB.źródło
Możesz również użyć
EXISTS
zSELECT 1
zamiast wybierania wszystkich kolumn wvacancies
tabeli:źródło
Błąd mówi ci, że wakaty nie są w zasadzie kolumną w projektach.
To powinno działać
źródło
aggregate functions are not allowed in WHERE