この種のクエリを構築する上での一般的な経験則は、「Ruby-land」での作業を最小限に抑え、「Database-land」での作業を最大限にすることです。上記のソリューションでは、セットarray
のタグを使用してマーキングのセットを取得しています。これはおそらく、非常に大きなセット(それらのタグを持つすべての投稿)になります。これはルビー配列で表現され、処理されます(group_by
はRuby-world、group
はDatabase-landで同等です)。
読みにくいというだけでなく、その解決策は大きなマークセットでは遅くなります。
Ruby-Worldで重労働を起こさずに問題を解決する方法はいくつかあります。一つの方法は、このように、サブクエリを使用している:
scope :with_tag_ids, ->(tag_ids) {
tag_ids.map { |tag_id|
joins(:markings).where(markings: { tag_id: tag_id })
}.reduce(all) { |scope, subquery| scope.where(id: subquery) }
}
これは、ここですべてがSQLで直接計算されているためという
SELECT "posts".*
FROM "posts"
WHERE "posts"."id" IN (SELECT "posts"."id" FROM "posts" INNER JOIN "markings" ON "markings"."post_id" = "posts"."id" WHERE "markings"."tag_id" = 5)
AND "posts"."id" IN (SELECT "posts"."id" FROM "posts" INNER JOIN "markings" ON "markings"."post_id" = "posts"."id" WHERE "markings"."tag_id" = 8)
注意、無アレイ(tag_ids 5と8のために再び)このようなクエリを生成し、 Rubyで生成または処理されます。これは一般的にははるかに優れています。
また、あなたはCOUNT
を使用して、サブクエリなしで単一のクエリでそれを行うことができます。
このようなSQLを生成
scope :with_tag_ids, ->(tag_ids) {
joins(:markings).where(markings: { tag_id: tag_ids }).
group(:post_id).having('COUNT(posts.id) = ?', tag_ids.count)
}
:
SELECT "posts".*
FROM "posts"
INNER JOIN "markings" ON "markings"."post_id" = "posts"."id"
WHERE "markings"."tag_id" IN (5, 8)
GROUP BY "post_id"
HAVING (COUNT(posts.id) = 2)
これは、あなたが複数のマーキングを持っていないことを前提としていtag_id
とpost_id
の同じペアを使用してカウントを停止します。
最後の解決策はおそらく最も効率的だと思いますが、さまざまな解決策を試して、自分のデータに最も適したものを見てください。
も参照してください。Query intersection with activerecord
このようなことを試すことができます。 http://stackoverflow.com/questions/25606775/how-to-find-posts-tagged-with-more-than-one-tag-in-rails-and-postgresql – Jayaprakash