タケユー・ウェブ日報

Ruby on Rails や Flutter といったWeb・モバイルアプリ技術を武器にお客様のビジネス立ち上げを支援する、タケユー・ウェブ株式会社の技術ブログです。

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