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})
이 두 쿼리는 모두 잘 작동합니다. 둘 다 Post 객체를 반환합니다. 이 게시물을 단일 ActiveRelation으로 결합하고 싶습니다. 특정 시점에 수십만 개의 게시물이있을 수 있으므로 이는 데이터베이스 수준에서 수행되어야합니다. MySQL 쿼리라면 간단히 UNION
연산자를 사용할 수 있습니다. RoR의 쿼리 인터페이스와 비슷한 작업을 수행 할 수 있는지 아는 사람이 있습니까?
답변
다음은 여러 범위를 통합 할 수있는 간단한 모듈입니다. 또한 결과를 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
요점은 다음과 같습니다 : 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")
답변
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)
기본적으로 세 개의 쿼리를 실행하고 약간 중복 된 느낌이 들기 때문에 누군가 이것을 최적화하거나 성능을 향상시킬 제안이 있다면 여전히 감사하겠습니다.
답변
범위에 대한 메소드로 확장 되는 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
언젠가 병합 될 것입니다.
답변
어때 …
def union(scope1, scope2)
ids = scope1.pluck(:id) + scope2.pluck(:id)
where(id: ids.uniq)
end
답변
UNION 대신 OR를 사용할 수 있습니까?
그런 다음 다음과 같이 할 수 있습니다.
Post.joins(:news => :watched, :post_topic_relationships => {:topic => :watched})
.where("watched.user_id = :id OR topic_watched.user_id = :id", :id => id)
(당신이 감시 된 테이블을 두 번 조인하기 때문에 쿼리에 대한 테이블 이름이 무엇인지 잘 모르겠습니다)
조인이 많기 때문에 데이터베이스에서 상당히 무겁지만 최적화 할 수 있습니다.
답변
아마도 이것은 가독성을 향상 시키지만 반드시 성능을 향상시키는 것은 아닙니다.
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")