元フリーエンジニアライフ

Ruby on Rails とか MovableType とかAWSやってるフリーランスウェブエンジニアの記録でした。現在は法人成りしてIT社長。

ActiveRecordでサブクエリのJOIN

たとえば

「ユーザー1がコメントした記事の中で、コメントのlike数が1以上の記事を検索する」

SELECT `posts`.* 
FROM `posts` 
  INNER JOIN (
    SELECT `comments`.* 
    FROM `comments`
    WHERE 
      `comments`.`user_id` = 1 AND
      `comments`.`likes_count` >= 1
  ) liked_user_comments ON liked_user_comments.`post_id` = `posts`.`id`

こんなのをRailsで書くには?

コード

user = User.find(1)
posts = Post.arel_table
comments = Comment.arel_table 

# サブクエリの組み立て
# .project などによってできる Arel::SelectManagerオブジェクト の .as を呼ぶと 
# Arel::Nodes::TableAliasオブジェクト を得ることができ、
# テーブルと同様に扱うことができるようになります。
liked_user_comments = comments
                          .project(Arel.sql('*'))
                          .where(comments[:user_id]
                              .eq(user.id)
                              .and(comments[:likes_count].gtdq(1)))
                          .as('liked_user_comments') # これ

# JOIN句の組み立て
# 先ほど得た Arel::Nodes::TableAlias を .join に渡すとサブクエリになります。
# 最後に .join_sources を呼ぶことでJOINの右辺を取り出すことができます。(Arel::Nodes::InnerJoinオブジェクト)
join_conds = posts
                 .join(liked_user_comments, Arel::Nodes::InnerJoin)
                 .on(liked_user_comments[:post_id].eq(posts[:id]))
                 .join_sources

# ActiveRecord の joins に渡す
Post.joins(join_conds)

参考

AdventCalendar - Arel でサブクエリ - Qiita