with
メソッドを使うことでモデル上のコードが読みやすくなるかもしれません前田 稔です。弊社では Webアプリケーションフレームワークの Ruby on Rails を利用した顧客・請求・入金管理システムがあり、 SQL のクエリ内で WITH 句を使用する機会がありました。本記事作成時点では Active Record クエリインターフェイス - Railsガイド に with
メソッドの記載がなかったため、参考になればと思い使い方の紹介をします。なお、SQL の実行計画やパフォーマンス・チューニングの掘り下げは扱いません。
各種バージョンは次のとおりです。今回は SQLite を使用していますので、他のデーターベースシステムで試される場合は WITH 句のサポート状況をご確認ください。
$ sqlite3 --version
3.40.1 2022-12-28 14:03:47 df5c253c0b3dd24916e4ec7cf77d3db5294cc9fd45ae7b9c5e82ad8197f3alt1
$ rails --version
Rails 8.0.2.1
$ ruby --version
ruby 3.4.4 (2025-05-14 revision a38531fd3f) +PRISM [aarch64-linux]
本記事用に用意した Ruby on Rails アプリケーションは顧客と請求、入金を扱うモデルがあるものとします。
請求と入金を突き合わせて管理する場面が多くあります。 今回は請求額と入金額が一致していない顧客を抽出するケースを例にします。
考慮する条件は次のとおりです。
最終的には顧客を抽出したいので、以下のように結合していきます。
その上で、HAVING 句を使って集合に対して「請求合計 ≠ 入金合計(または NULL)」の顧客を絞り込みます。
invoice_totals =
Customer.joins(:invoices)
.where(invoices: { due_date: ..Date.today })
.select(:id, 'SUM(invoices.amount) AS total_invoice_amount')
.group(:id)
payment_totals =
Customer.left_outer_joins(:payments)
.where(payments: { payment_date: ..Date.today })
.or(Customer.left_outer_joins(:payments).where(payments: { payment_date: nil }))
.select(:id, 'SUM(payments.amount) AS total_payment_amount')
.group(:id)
# データベースシステムによっては動作しない場合があります
customers = invoice_totals.merge(payment_totals)
.having('total_invoice_amount != COALESCE(total_payment_amount, 0)')
発行されるSQLは次のとおりです。
SELECT
"customers"."id",
SUM(invoices.amount) AS total_invoice_amount,
SUM(payments.amount) AS total_payment_amount
FROM
"customers"
INNER JOIN "invoices"
ON "invoices"."customer_id" = "customers"."id"
LEFT OUTER JOIN "payments"
ON "payments"."customer_id" = "customers"."id"
WHERE
"invoices"."due_date" <= '2025-09-03'
AND (
"payments"."payment_date" <= '2025-09-03'
OR "payments"."payment_date" IS NULL
)
GROUP BY
"customers"."id"
HAVING
(total_invoice_amount != COALESCE(total_payment_amount, 0))
with
メソッドにて一時的なビューとしての名前と ActiveRecord のリレーションを指定することで、内部結合や外部左結合でも利用できるようになります。
参考情報:
customers =
Customer.with(
temp_invoices: Invoice.where(due_date: ..Date.today)
.select(:customer_id, 'SUM(invoices.amount) AS total_amount')
.group(:customer_id),
temp_payments: Payment.where(payment_date: ..Date.today)
.select(:customer_id, 'SUM(payments.amount) AS total_amount')
.group(:customer_id)
)
.left_outer_joins(:temp_payments)
.joins(:temp_invoices)
.where('temp_invoices.total_amount != COALESCE(temp_payments.total_amount, 0)')
発行されるSQLは次のようになります。
WITH "temp_invoices" AS (
SELECT
"invoices"."customer_id",
SUM(invoices.amount) AS total_amount
FROM
"invoices"
WHERE
"invoices"."due_date" <= '2025-09-03'
GROUP BY
"invoices"."customer_id"
), "temp_payments" AS (
SELECT
"payments"."customer_id",
SUM(payments.amount) AS total_amount
FROM
"payments"
WHERE
"payments"."payment_date" <= '2025-09-03'
GROUP BY
"payments"."customer_id"
)
SELECT
"customers".*
FROM
"customers"
LEFT OUTER JOIN "temp_payments"
ON "temp_payments"."customer_id" = "customers"."id"
INNER JOIN "temp_invoices"
ON "temp_invoices"."customer_id" = "customers"."id"
WHERE (
temp_invoices.total_amount != COALESCE(temp_payments.total_amount, 0)
)
ActiveRecord の with
メソッドを使うと、複雑なサブクエリや結合を整理して書けるため、モデル上のコードが読みやすくなる傾向があります。
今回は扱いませんでしたが with
メソッドは UNION ALL
による和集合や、FROM
句や WHERE
句のサブクエリの置き換えにも利用できます。詳しくは ActiveRecord::QueryMethods - Rails API をご覧ください。
既存のサブクエリを整理したいときや SQL をよりわかりやすくしたいときに、ぜひ試してみてください。本記事が参考になれば幸いです。
![]() |
Ruby on Rails アプリケーションである Redmine の Wasm 化を試しました |
![]() |
島根県松江市で開催されたJANOG56ミーティングにRuby Sponsorとしてブース出展しました。 |
![]() |
GoogleフォトからAmazon Photosへ移行しました。容量削減の手順や移行の注意点をまとめて紹介します。 |
![]() |
初めてのJANOG参加。初心者や若者のサポート、議論を活発にするための工夫がたくさん詰まっていて、ネットワークに詳しくない私でも楽しめました。 |
![]() |
家族のANA国内線往復チケットを手配し、家族のスマートフォンに登録する手順を紹介します |
![]() |
Open Developers Conference 2025(9/6開催)登壇・ブース出展 Open Developers Conference 2025で登壇・ブースを出展します。 |
![]() |
【AIがRedmineのチケットを要約】チケット内容が瞬時に分かる新機能「AIチケット要約」をMy Redmineで提供開始! Redmineのクラウドサービス『My Redmine JP Edition』で、チケット内容を要約する新機能「AIチケット要約」が使えるようになりました。 |
![]() |
RubyWorld Conference 2025 (11/6・7開催) にPlatinumスポンサーとして協賛 2025年11月6日(木)〜7日(金)に島根県松江市で開催される「RubyWorld Conference 2025」にPlatinumスポンサーとして協賛しています。 |
![]() |
Redmineの最新情報をメールでお知らせする「Redmine News」配信中 新バージョンやセキュリティ修正のリリース情報、そのほか最新情報を迅速にお届け |