Eu escrevi algumas consultas complexas (pelo menos para mim) com a interface de consulta do Ruby on Rail:
watched_news_posts = Post.joins(:news => :watched).where(:watched => {:user_id => id})
watched_topic_posts = Post.joins(:post_topic_relationships => {:topic => :watched}).where(:watched => {:user_id => id})
Ambas as consultas funcionam bem sozinhas. Ambos retornam objetos Post. Eu gostaria de combinar essas postagens em um único ActiveRelation. Como pode haver centenas de milhares de postagens em algum ponto, isso precisa ser feito no nível do banco de dados. Se fosse uma consulta MySQL, eu poderia simplesmente usar o UNION
operador. Alguém sabe se posso fazer algo semelhante com a interface de consulta do RoR?
ruby-on-rails
activerecord
union
active-relation
LandonSchropp
fonte
fonte
Post.watched_news_posts.watched_topic_posts
. Pode ser necessário enviar params para os escopos de coisas como:user_id
e:topic
.find_by_sql("#{watched_news_posts.to_sql} UNION #{watched_topic_posts.to_sql}")
. Eu não testei isso, então me diga como foi se você tentar. Além disso, provavelmente há alguma funcionalidade ARel que funcionaria.find_by_sql
não podem ser usados com outras consultas encadeadas, o que significa que agora tenho que reescrever meus filtros e consultas will_paginate também. Por que o ActiveRecord não oferece suporte a umaunion
operação?Respostas:
Aqui está um pequeno módulo rápido que escrevi que permite UNIONAR múltiplos escopos. Ele também retorna os resultados como uma instância de ActiveRecord :: Relation.
Aqui está a essência: https://gist.github.com/tlowrimore/5162327
Editar:
Conforme solicitado, aqui está um exemplo de como funciona o UnionScope:
fonte
Eu também encontrei esse problema e agora minha estratégia inicial é gerar SQL (manualmente ou usando
to_sql
em um escopo existente) e, em seguida, inseri-lo nafrom
cláusula. Não posso garantir que seja mais eficiente do que o método aceito, mas é relativamente fácil para os olhos e devolve um objeto ARel normal.Você também pode fazer isso com dois modelos diferentes, mas precisa ter certeza de que ambos "têm a mesma aparência" dentro do UNION - você pode usar
select
em ambas as consultas para ter certeza de que produzirão as mesmas colunas.fonte
#or
em meu projeto Rails 4. Supondo o mesmo modelo:klass.from("(#{to_sql} union #{other_relation.to_sql}) as #{table_name}")
Com base na resposta do Olives, descobri outra solução para esse problema. Parece um pouco como um hack, mas retorna uma instância de
ActiveRelation
, que é o que eu estava procurando em primeiro lugar.Ainda agradeceria se alguém tivesse alguma sugestão para otimizar isso ou melhorar o desempenho, porque essencialmente está executando três consultas e parece um pouco redundante.
fonte
Você também pode usar Brian Hempel 's active_record_union gem que se estende
ActiveRecord
com umunion
método para escopos.Sua consulta seria assim:
Esperançosamente isso será eventualmente mesclado em
ActiveRecord
algum dia.fonte
E se...
fonte
pluck
chamada é uma consulta em si..order
ou.paginate
... Ele mantém as classes ormVocê poderia usar um OR em vez de um UNION?
Então você poderia fazer algo como:
(Como você se junta à tabela observada duas vezes, não tenho certeza de quais serão os nomes das tabelas para a consulta)
Como há muitas junções, também pode ser muito pesado no banco de dados, mas pode ser otimizado.
fonte
Indiscutivelmente, isso melhora a legibilidade, mas não necessariamente o desempenho:
Este método retorna um ActiveRecord :: Relation, então você pode chamá-lo assim:
fonte
Existe uma gem active_record_union. Pode ser útil
https://github.com/brianhempel/active_record_union
fonte
Gostaria apenas de executar as duas consultas de que você precisa e combinar as matrizes de registros que são retornados:
Ou, pelo menos, faça um teste. Você acha que a combinação do array em ruby será muito lenta? Olhando para as consultas sugeridas para contornar o problema, não estou convencido de que haverá uma diferença significativa de desempenho.
fonte
Em um caso semelhante, somei dois arrays e usei
Kaminari:paginate_array()
. Solução muito agradável e funcional. Não consegui usarwhere()
, pois preciso somar dois resultados com diferentesorder()
na mesma tabela.fonte
Menos problemas e mais fácil de seguir:
Então, no final:
fonte
scopes.drop(1).reduce(where(id: scopes.first)) { |query, scope| query.or(where(id: scope)) }
Thx!Elliot Nelson respondeu bem, exceto no caso em que algumas das relações estão vazias. Eu faria algo assim:
fim
fonte
Veja como juntei consultas SQL usando UNION em meu próprio aplicativo Ruby on Rails.
Você pode usar o seguinte como inspiração em seu próprio código.
Abaixo está a UNION onde juntei os escopos.
fonte