Принятый ответ (Parent.joins(:children).uniq
) генерирует SQL с использованием DISTINCT, но это может быть медленный запрос.Для повышения производительности вы должны писать SQL с использованием EXISTS:
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 в 20.047661 раз быстрее, чем DISTINCT.
Я отправил пример приложения в GitHub, так что вы можете подтвердить разницу самостоятельно:
https://github.com/JunichiIto/exists-query-sandbox