Lan*_*opp 82 union activerecord ruby-on-rails active-relation
我用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})
Run Code Online (Sandbox Code Playgroud)
这两个查询都可以自行完成.两者都返回Post对象.我想将这些帖子合并到一个ActiveRelation中.由于某些时候可能有数十万个帖子,因此需要在数据库级别完成.如果是MySQL查询,我可以简单地使用UNION
运算符.有没有人知道我是否可以用RoR的查询界面做类似的事情?
Tim*_*ore 89
这是我编写的一个快速小模块,允许您使用UNION多个范围.它还将结果作为ActiveRecord :: Relation的实例返回.
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
Run Code Online (Sandbox Code Playgroud)
这是要点: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
Run Code Online (Sandbox Code Playgroud)
Ell*_*son 59
我也遇到过这个问题,现在我的首选策略是生成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")
Run Code Online (Sandbox Code Playgroud)
您也可以使用两个不同的模型执行此操作,但是您需要确保它们在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")
Run Code Online (Sandbox Code Playgroud)
Lan*_*opp 11
根据Olives的回答,我确实想出了另一个解决这个问题的方法.它感觉有点像黑客,但它返回一个实例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)
Run Code Online (Sandbox Code Playgroud)
如果任何人有任何建议来优化或提高性能,我仍然会感激,因为它实际上是执行三个查询并感觉有点多余.
小智 9
怎么样...
def union(scope1, scope2)
ids = scope1.pluck(:id) + scope2.pluck(:id)
where(id: ids.uniq)
end
Run Code Online (Sandbox Code Playgroud)
您还可以使用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}))
Run Code Online (Sandbox Code Playgroud)
希望这最终会在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)
Run Code Online (Sandbox Code Playgroud)
(因为你加入了两次监视的表,我不太清楚查询表的名称是什么)
由于存在大量连接,因此数据库上也可能非常繁重,但它可能可以进行优化.
可以说,这提高了可读性,但不一定是性能:
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
Run Code Online (Sandbox Code Playgroud)
此方法返回一个ActiveRecord :: Relation,因此您可以这样调用它:
my_posts.order("watched_item_type, post.id DESC")
Run Code Online (Sandbox Code Playgroud)