接受的答案 (Parent.joins(:children).uniq) 使用 DISTINCT 生成 SQL,但查询速度可能很慢。为了获得更好的性能,您应该使用 EXISTS 编写 SQL:
Parent.where<<-SQL
EXISTS (SELECT * FROM children c WHERE c.parent_id = parents.id)
SQL
EXISTS 比 DISTINCT 快得多。例如,这是一个有 cmets 和 likes 的帖子模型:
class Post < ApplicationRecord
has_many :comments
has_many :likes
end
class Comment < ApplicationRecord
belongs_to :post
end
class Like < ApplicationRecord
belongs_to :post
end
在数据库中有 100 个帖子,每个帖子有 50 个 cmets 和 50 个赞。只有一个帖子没有cmet和点赞:
# Create posts with comments and likes
100.times do |i|
post = Post.create!(title: "Post #{i}")
50.times do |j|
post.comments.create!(content: "Comment #{j} for #{post.title}")
post.likes.create!(user_name: "User #{j} for #{post.title}")
end
end
# Create a post without comment and like
Post.create!(title: 'Hidden post')
如果你想获得至少有一条评论和点赞的帖子,你可以这样写:
# NOTE: uniq method will be removed in Rails 5.1
Post.joins(:comments, :likes).distinct
上面的查询生成如下 SQL:
SELECT DISTINCT "posts".*
FROM "posts"
INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
INNER JOIN "likes" ON "likes"."post_id" = "posts"."id"
但是这个 SQL 会生成 250000 行(100 个帖子 * 50 个 cmets * 50 个赞)然后过滤掉重复的行,所以它可能会很慢。
在这种情况下,你应该这样写:
Post.where <<-SQL
EXISTS (SELECT * FROM comments c WHERE c.post_id = posts.id)
AND
EXISTS (SELECT * FROM likes l WHERE l.post_id = posts.id)
SQL
此查询生成如下 SQL:
SELECT "posts".*
FROM "posts"
WHERE (
EXISTS (SELECT * FROM comments c WHERE c.post_id = posts.id)
AND
EXISTS (SELECT * FROM likes l WHERE l.post_id = posts.id)
)
此查询不会生成无用的重复行,因此它可以更快。
这是基准:
user system total real
Uniq: 0.010000 0.000000 0.010000 ( 0.074396)
Exists: 0.000000 0.000000 0.000000 ( 0.003711)
它显示 EXISTS 比 DISTINCT 快 20.047661 倍。
我在GitHub上推送了示例应用,大家可以自行确认区别:
https://github.com/JunichiIto/exists-query-sandbox