因此,构建此类查询的一般经验法则是尽量减少“Ruby 领域”的工作量,并最大限度地提高“数据库领域”的工作量。在您上面的解决方案中,您正在获取一组带有array 中任何标签的标记,这可能是一个非常大的集合(所有帖子都具有这些标签中的任何一个)。这在 ruby 数组中表示并进行处理(group_by 在 Ruby 世界中,group 在数据库领域中是等价的)。
因此,除了难以阅读之外,该解决方案对于任何大型标记集都会很慢。
在 Ruby 世界中,有几种方法可以在不做任何繁重工作的情况下解决问题。一种方法是使用子查询,如下所示:
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) }
}
这会生成这样的查询(同样针对 tag_ids 5 和 8)
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)
请注意,由于此处的所有内容都是直接在 SQL 中计算的,因此不会在 Ruby 中生成或处理数组。这通常会更好地扩展。
或者,您可以使用 COUNT 并在没有子查询的单个查询中执行此操作:
scope :with_tag_ids, ->(tag_ids) {
joins(:markings).where(markings: { tag_id: tag_ids }).
group(:post_id).having('COUNT(posts.id) = ?', tag_ids.count)
}
生成这样的 SQL:
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