GROUP BY での SUM に DISTINCT を適用

rails/rails

relation.distinct.sum が生成する SUM(DISTINCT column) は重複除去に有効ですが、group が付与されたときに DISTINCT が失われ、重複した行が合計に含まれる不整合がありました。この PR はその不具合を解消し、グループ化された集計でも正しく SUM(DISTINCT) が適用されるようにします。結果として、同一カラムの重複値を除いた正確な合計が取得可能になります。

背景

Account.where(firm_id: 1).distinct.sum(:credit_limit)SUM(DISTINCT credit_limit) を生成し 110 を返す一方で、group を追加した Account.where(firm_id: 1).group(:firm_id).distinct.sum(:credit_limit) は DISTINCT が除外され、重複がカウントされた 160 を返していました。この差異は、未グループ化パスでは select_value.distinct = true if operation == "sum" && distinct が設定されているのに対し、グループ化パスでは同様のフラグ設定が抜けていたことが原因です。結果として、集計結果が数値的に誤ったものとなり、ビジネスロジック上の不整合を引き起こしていました。

技術的な変更

execute_grouped_calculation メソッド内に select_value.distinct = true if operation == "sum" && distinct を追加し、グループ化された計算でも select_value.distinct フラグがオンになるよう統一しました。この変更は既存のロジックに最小限の差分で挿入され、他の集計操作や非 DISTINCT のケースには影響を与えません。

変更前:

select_value = operation_over_aggregate_column(column, operation, distinct)
select_value = select_value.as(model.adapter_class.quote_column_name(column_alias))

変更後:

select_value = operation_over_aggregate_column(column, operation, distinct)
select_value.distinct = true if operation == "sum" && distinct
select_value = select_value.as(model.adapter_class.quote_column_name(column_alias))

テストスイートにも test_should_group_by_distinct_summed_field が追加され、未グループ化・グループ化の両ケースで期待通りに 110 と {1 => 110} が得られることを検証しています。このテストはデータをリセットし複数レコードを作成した上で、distinct.sum の結果を明示的に比較しています。

設計判断

修正は select_value.distinct の設定をグループ化パスへ移植するという、既存のフラグ設定ロジックを再利用する方針で実装されました。代替案として新しいメソッドや設定キーの導入が検討されましたが、既存コードとの互換性とシンプルさを保つために現在の実装が選択されました。

この変更は distinct が指定されていない従来の sum 動作を全く変えず、sum とともに distinct オプションが要求された場合のみフラグを付与します。そのため、既存アプリケーションの振る舞いは保持されつつ、期待通りの集計が可能になるという利点があります。

まとめ

本 PR は SUM(DISTINCT ...)GROUP BY と併用されたときに失われていた問題を修正し、relation.distinct.sum が常に重複除去された合計を返すよう統一しました。変更点は execute_grouped_calculation へのフラグ追加と対応テストの追加のみで、既存コードへの影響は最小限です。これにより、Rails の集計 API がより一貫した動作を提供できるようになりました。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
edfdafa5

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文が要旨を示し、背景・技術的変更・設計判断・まとめの各セクションが揃っており、総論→各論→結論という構成が明確です。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きシンタックスハイライトが正しい形式で使用され、PRリンクも #番号 で正しくリンク化されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

Rails の内部実装に詳しいエンジニア向けの内容で、初心者向けの余計な説明はなく適切です。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションは総論・各論・結論の段落構成となり、トピックセンテンスで始まり、1段落1トピック、長さも適切で空行で区切られています。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事中のコードブロックは提供された Diff と完全に一致し、変更箇所(select_value.distinct の追加)も正確に反映されています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

distinct、sum、group、select_value などの用語は PR と一致し、誤用はありません。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

変更理由や影響が PR の記述と合致しており、技術的説明に矛盾や誤りはありません。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事の全ての主張は PR のタイトル・説明・Diff で裏付けられており、推測や捏造はありません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

数値(110、160、{1 => 110})は PR の例と一致しています。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事タイトルは PR タイトルの意味を日本語で正確に表現しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

バージョン情報やリリーススケジュール等、PR に記載されていない外部知識は含まれていません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

時間表現の歪曲はなく、PR の記述と一致しています。