在谷歌搜索,浏览SO和reading后,似乎没有一种Rails风格的方式来有效地只获得至少一个 <{1}}个对象/ em> Parent
对象(通过Child
关系)。在纯SQL中:
has_many :children
我最接近的是
SELECT *
FROM parents
WHERE EXISTS (
SELECT 1
FROM children
WHERE parent_id = parents.id)
(基于another answer),但效率非常低,因为它为每个Parent.all.reject { |parent| parent.children.empty? }
运行单独的查询。
答案 0 :(得分:52)
Parent.joins(:children).uniq.all
答案 1 :(得分:3)
我刚刚根据您的需要修改了此solution。
Parent.joins("left join childrens on childrends.parent_id = parents.id").where("childrents.parent_id is not null")
答案 2 :(得分:2)
尝试将孩子包括#includes()
Parent.includes(:children).all.reject { |parent| parent.children.empty? }
这将产生2个查询:
SELECT * FROM parents;
SELECT * FROM children WHERE parent_id IN (5, 6, 8, ...);
<强> [UPDATE] 强>
当您需要加载Child对象时,上述解决方案很有用。
但children.empty?
也可以使用计数器缓存 1,2 来确定子女数量。
为此,您需要在parents
表中添加一个新列:
# a new migration
def up
change_table :parents do |t|
t.integer :children_count, :default => 0
end
Parent.reset_column_information
Parent.all.each do |p|
Parent.update_counters p.id, :children_count => p.children.length
end
end
def down
change_table :parents do |t|
t.remove :children_count
end
end
现在更改您的Child
型号:
class Child
belongs_to :parent, :counter_cache => true
end
此时您可以使用size
和empty?
而无需触及children
表:
Parent.all.reject { |parent| parent.children.empty? }
请注意,length
不使用计数器缓存,而size
和empty?
则使用。
答案 3 :(得分:2)
你只想要一个带有不同限定符的内连接
SELECT DISTINCT(*)
FROM parents
JOIN children
ON children.parent_id = parents.id
这可以在标准活动记录中完成
Parent.joins(:children).uniq
但是,如果你想找到更复杂的结果,找不到所有没有孩子的父母 你需要一个外部联接
Parent.joins("LEFT OUTER JOIN children on children.parent_id = parent.id").
where(:children => { :id => nil })
这是一个解决方案,因为很多原因。我推荐Ernie Millers squeel库让你做
Parent.joins{children.outer}.where{children.id == nil}
答案 4 :(得分:2)
接受的答案(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快得多。例如,这是一个有评论和喜欢的帖子模型:
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个评论和50个喜欢。只有一篇文章没有评论和喜欢:
# 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个评论* 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中推送了示例应用程序,因此您可以自己确认差异:
答案 5 :(得分:2)
自Rails 5.1起,不推荐使用uniq
,而应使用distinct
。
Parent.joins(:children).distinct
这是Chris Bailey's answer的后续行动。 <{1}}也会从原始答案中移除,因为它不会添加任何内容。