Estou tentando fazer algo que pensei que seria simples, mas parece que não é.
Tenho um modelo de projeto que tem muitas vagas.
class Project < ActiveRecord::Base
has_many :vacancies, :dependent => :destroy
end
Quero pegar todos os projetos que tenham no mínimo 1 vaga. Eu tentei algo assim:
Project.joins(:vacancies).where('count(vacancies) > 0')
mas diz
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)
.
fonte
Project.joins(:vacancies).distinct
?1) Para conseguir Projetos com no mínimo 1 vaga:
2) Para obter Projetos com mais de 1 vaga:
3) Ou, se o
Vacancy
modelo definir o cache do contador:então isso vai funcionar também:
A regra de inflexão para
vacancy
pode precisar ser especificada manualmente ?fonte
Project.joins(:vacancies).group('projects.id').having('count(vacancies.id) > 1')
? Consultando o número de vagas, em vez das identificações de projetoprojects.id
,project_id
evacancies.id
. Optei por contarproject_id
porque é o campo em que a junção é feita; a espinha da junção, se quiser. Também me lembra que esta é uma mesa de junção.Sim,
vacancies
não é um campo no join. Eu acredito que você quer:fonte
fonte
A execução de uma junção interna à tabela has_many combinada com um
group
ouuniq
é potencialmente muito ineficiente e, em SQL, seria melhor implementada como uma semi-junção que usaEXISTS
com uma subconsulta correlacionada.Isso permite que o otimizador de consulta investigue a tabela de vagas para verificar a existência de uma linha com o project_id correto. Não importa se há uma linha ou um milhão com esse project_id.
Isso não é tão simples no Rails, mas pode ser alcançado com:
Da mesma forma, encontre todos os projetos que não têm vagas:
Edit: nas versões recentes do Rails, você recebe um aviso de depreciação dizendo para não confiar em
exists
ser delegado a arel. Corrija isso com:Editar: se você não se sentir confortável com o SQL bruto, tente:
Você pode tornar isso menos confuso adicionando métodos de classe para ocultar o uso de
arel_table
, por exemplo:... tão ...
fonte
Vacancy.where("vacancies.project_id = projects.id").exists?
retornatrue
oufalse
.Project.where(true)
é umArgumentError
.Vacancy.where("vacancies.project_id = projects.id").exists?
não vai ser executado - vai gerar um erro porque aprojects
relação não existirá na consulta (e também não há ponto de interrogação no código de exemplo acima). Portanto, decompor isso em duas expressões não é válido e não funciona. Recentemente, RailsProject.where(Vacancies.where("vacancies.project_id = projects.id").exists)
levantou um aviso de depreciação ... Vou atualizar a questão.No Rails 4+, você também pode usar includes ou eager_load para obter a mesma resposta:
fonte
Acho que há uma solução mais simples:
fonte
Sem muita magia Rails, você pode fazer:
Este tipo de condição funcionará em todas as versões do Rails já que grande parte do trabalho é feito diretamente no lado do banco de dados. Além disso, o
.count
método de encadeamento também funcionará bem. Fui queimado por perguntas comoProject.joins(:vacancies)
antes. Claro, existem prós e contras, pois não é agnóstico de DB.fonte
Você também pode usar
EXISTS
com, emSELECT 1
vez de selecionar todas as colunas davacancies
tabela:fonte
O erro é avisar que vagas não é coluna em projetos, basicamente.
Isso deve funcionar
fonte
aggregate functions are not allowed in WHERE