ActiveRecord查询联盟

我用Ruby on Rail的查询界面写了一些复杂的查询(至less对我来说):

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}) 

这两个查询都可以自行工作。 两个都返回Post对象。 我想将这些post合并成一个ActiveRelation。 由于在某个时候可能会有数十万个职位,因此需要在数据库层面进行。 如果它是一个MySQL查询,我可以简单地使用UNION运算符。 有谁知道我能否用RoR的查询界面做类似的事情?

下面是我写的一个快速的小模块,它允许您使用UNION多个作用域。 它还以ActiveRecord :: Relation的实例的forms返回结果。

 module ActiveRecord::UnionScope def self.included(base) base.send :extend, ClassMethods end module ClassMethods def union_scope(*scopes) id_column = "#{table_name}.id" sub_query = scopes.map { |s| s.select(id_column).to_sql }.join(" UNION ") where "#{id_column} IN (#{sub_query})" end end end 

这是要点: https : //gist.github.com/tlowrimore/5162327

编辑:

按照要求,下面是UnionScope工作原理的一个例子:

 class Property < ActiveRecord::Base include ActiveRecord::UnionScope # some silly, contrived scopes scope :active_nearby, -> { where(active: true).where('distance <= 25') } scope :inactive_distant, -> { where(active: false).where('distance >= 200') } # A union of the aforementioned scopes scope :active_near_and_inactive_distant, -> { union_scope(active_nearby, inactive_distant) } end 

我也遇到了这个问题,现在我的策略是生成SQL(手工或在现有范围内使用to_sql ),然后将其粘贴在from子句中。 我不能保证它比你接受的方法更有效率,但是它在眼睛上相对容易,并给你一个正常的ARel对象。

 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}) Post.from("(#{watched_news_posts.to_sql} UNION #{watched_topic_posts.to_sql}) AS posts") 

你也可以用两种不同的模型来做到这一点,但是你需要确保它们在UNION里面看起来相同 – 你可以在两个查询中使用select来确保它们会产生相同的列。

 topics = Topic.select('user_id AS author_id, description AS body, created_at') comments = Comment.select('author_id, body, created_at') Comment.from("(#{comments.to_sql} UNION #{topics.to_sql}) AS comments") 

基于橄榄的回答,我提出了另一个解决这个问题的办法。 这感觉有点像黑客攻击,但是它返回了ActiveRelation一个实例,这正是我之前的工作。

 Post.where('posts.id IN ( SELECT post_topic_relationships.post_id FROM post_topic_relationships INNER JOIN "watched" ON "watched"."watched_item_id" = "post_topic_relationships"."topic_id" AND "watched"."watched_item_type" = "Topic" WHERE "watched"."user_id" = ? ) OR posts.id IN ( SELECT "posts"."id" FROM "posts" INNER JOIN "news" ON "news"."id" = "posts"."news_id" INNER JOIN "watched" ON "watched"."watched_item_id" = "news"."id" AND "watched"."watched_item_type" = "News" WHERE "watched"."user_id" = ? )', id, id) 

如果任何人有任何build议来优化或提高性能,我还是会很感激,因为它本质上是执行三个查询,并且感觉有点多余。

怎么样…

 def union(scope1, scope2) ids = scope1.pluck(:id) + scope2.pluck(:id) where(id: ids.uniq) end 

你也可以使用Brian Hempel的active_record_union gem来扩展ActiveRecord的范围的union方法。

你的查询会是这样的:

 Post.joins(:news => :watched). where(:watched => {:user_id => id}). union(Post.joins(:post_topic_relationships => {:topic => :watched} .where(:watched => {:user_id => id})) 

希望有一天这个最终会被合并到ActiveRecord

你可以用一个OR来代替一个UNION吗?

那么你可以做一些事情:

 Post.joins(:news => :watched, :post_topic_relationships => {:topic => :watched}) .where("watched.user_id = :id OR topic_watched.user_id = :id", :id => id) 

(既然你两次join了监视表,我不太清楚这个表的名字是什么)

由于有很多连接,所以在数据库上也可能很繁重,但可能会被优化。

可以说,这提高了可读性,但不一定是性能:

 def my_posts Post.where <<-SQL, self.id, self.id posts.id IN (SELECT post_topic_relationships.post_id FROM post_topic_relationships INNER JOIN watched ON watched.watched_item_id = post_topic_relationships.topic_id AND watched.watched_item_type = "Topic" AND watched.user_id = ? UNION SELECT posts.id FROM posts INNER JOIN news ON news.id = posts.news_id INNER JOIN watched ON watched.watched_item_id = news.id AND watched.watched_item_type = "News" AND watched.user_id = ?) SQL end 

这个方法返回一个ActiveRecord :: Relation,所以你可以这样调用它:

 my_posts.order("watched_item_type, post.id DESC") 

我只是运行你需要的两个查询,并结合返回的logging数组:

 @posts = watched_news_posts + watched_topics_posts 

或者,至lesstesting一下。 你认为在rubyarrays组合会太慢? 看看build议的查询来解决这个问题,我不相信会有一个显着的性能差异。

在类似的情况下,我总结了两个数组,并使用Kaminari:paginate_array() 。 非常好的工作解决scheme。 我无法使用where() ,因为我需要在同一个表上以不同的order()将两个结果相加。

有一个active_record_uniongem。 可能有帮助

https://github.com/brianhempel/active_record_union

通过ActiveRecordUnion,我们可以做到:

当前用户的(草稿)post和来自任何人的所有已发布postcurrent_user.posts.union(Post.published)它与以下SQL等效:

SELECT "posts".* FROM ( SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = 1 UNION SELECT "posts".* FROM "posts" WHERE (published_at < '2014-07-19 16:04:21.918366') ) posts