Apex

参照関係の親レコードへApexで商談数を集計する書き方

積み上げ集計項目が使えない場面で、GROUP BYと差分更新を使ったApexの集計処理を説明します。

2024.08.01

標準の積み上げ集計でできるか先に確かめる

親子の件数や合計を親へ反映したいとき、まず確かめることがあります。積み上げ集計項目(Roll-Up Summary)が使えるのは、主従関係(マスタ詳細関係)でつながった親子だけです。参照関係(ルックアップ)でつながった親子には使えません。

積み上げ集計項目でできるのは、件数(COUNT)、合計(SUM)、最小値・最大値(MIN・MAX)の4種類です。使える条件がそろっているなら、Apexを書くより積み上げ集計項目を作るほうが早く、保守もかかりません。

⚠️ 一度、積み上げ集計項目をどれか1つでも作ると、その主従関係を参照関係へ変更できなくなります。

積み上げ集計が使えないときの選択肢

商談から取引先責任者への参照は、標準では主従関係になりません。カスタムの参照関係項目(ルックアップ)で商談から取引先責任者を指している場合、積み上げ集計項目は使えないので、Apexで集計して書き戻します。

項目を用意する

  1. 商談に参照関係項目を作る

    商談オブジェクトに、取引先責任者を指す参照関係項目(例: Contact__c)を作成します。

  2. 取引先責任者に集計結果を格納する項目を作る

    取引先責任者オブジェクトに、数値項目(例: OppCounts__c)を作成します。

商談のカスタム項目「取引先責任者」の定義画面。データ型が参照関係になっている
商談に作った参照関係項目です。API参照名がContact__c、データ型が参照関係になっている点を見てください。
取引先責任者のカスタム項目「商談数」の定義画面。データ型が数値になっている
集計結果を書き戻す項目です。API参照名がOppCounts__c、データ型が数値であることを確認してください。

集計をApexで行う

取引先責任者ごとに商談を1件ずつ数える代わりに、SOQLの集計関数で最初から件数を取ります。Apex側でMapに数え上げるより、クエリで返る行数そのものが少なくなります。

public with sharing class ContactOpportunityRollup {

    public static void recalculate() {
        Map<Id, Integer> countsByContactId = new Map<Id, Integer>();
        for (AggregateResult ar : [
            SELECT Contact__c contactId, COUNT(Id) oppCount
            FROM Opportunity
            WHERE Contact__c != null
            GROUP BY Contact__c
        ]) {
            countsByContactId.put((Id) ar.get('contactId'), (Integer) ar.get('oppCount'));
        }

        List<Contact> targets = [
            SELECT Id, OppCounts__c
            FROM Contact
            WHERE Id IN :countsByContactId.keySet() OR OppCounts__c != null
        ];

        List<Contact> toUpdate = new List<Contact>();
        for (Contact con : targets) {
            Integer count = countsByContactId.containsKey(con.Id) ? countsByContactId.get(con.Id) : 0;
            if (con.OppCounts__c != count) {
                con.OppCounts__c = count;
                toUpdate.add(con);
            }
        }
        if (!toUpdate.isEmpty()) {
            Database.update(toUpdate, false);
        }
    }
}

GROUP BYで取引先責任者ごとの商談数を1回のSOQLで取得します。ループの中にSOQLはありません。

差分だけを更新する

商談を持つ取引先責任者だけをMapへ集計し、その分だけupdateする書き方だと、商談がすべて削除されて0件になった取引先責任者の数値が、古い値のまま残ります。上のコードでは、OppCounts__cに値が入っている取引先責任者もクエリに含め、集計結果と食い違っている行だけを更新対象にしています。値が変わらない行はDMLに含めないので、対象件数も抑えられます。

動作確認

  1. 取引先責任者を作成する

    集計対象のレコードを用意します。

  2. 商談を複数件作成する

    作成した取引先責任者を参照する商談を、2件以上作ります。

  3. 開発者コンソールで実行する

    Execute Anonymous WindowでContactOpportunityRollup.recalculate();を実行します。

  4. 取引先責任者の項目を確認する

    OppCounts__cが商談の件数と一致していることを確認します。商談を1件削除してから再実行し、件数が減ることも確認します。

取引先責任者ContactBの詳細画面。赤枠で囲まれた商談数の欄はまだ空のまま
実行前の取引先責任者です。赤枠の商談数がまだ空欄のままであることを見てください。
商談テスト1の詳細画面。赤枠の取引先責任者の欄にContactBが入っている
集計対象にする商談です。赤枠の取引先責任者にContactBが設定されている点を見てください。
実行後の取引先責任者の詳細画面。赤枠の商談数に1が入っている
実行したあとの画面です。赤枠の商談数が1になり、商談の件数と一致したことを確認します。
取引先責任者の詳細画面。商談数の欄に1と表示されている
同じ確認画面です。商談数の欄に件数が入っていれば、集計が反映されています。

ここで間違えやすい

間違い何が起きるか
ルックアップでつながった親子に積み上げ集計項目を作ろうとする選択肢に出てきません。使えるのは主従関係だけです
商談を1件ずつクエリして数える取引先責任者の数だけSOQLを発行することになり、件数が増えた日に上限へ当たります
集計結果がある取引先責任者だけを更新対象にする商談が0件に戻った取引先責任者の数値が、古いまま残ります
大量データを一括でrecalculateする対象が多い組織では、1回のトランザクションのDML行数上限に当たります。Database.Batchableに分けて処理してください

確認した環境

  • 2026年9月/Salesforce Summer '26(APIバージョン67.0)時点の公式ヘルプで、積み上げ集計項目が主従関係でのみ使えること、対応する集計種別(COUNT・SUM・MIN・MAX)を確認しています

まとめ

  • 積み上げ集計項目が使えるのは主従関係の親子だけです。ルックアップでつながった親子には使えません
  • 使える条件がそろっているなら、Apexより積み上げ集計項目を作るほうが保守がかかりません
  • Apexで集計するときは、取引先責任者ごとにクエリを発行せず、GROUP BYで1回にまとめます
  • 集計が0件になったレコードも更新対象に含めないと、古い数値が残ります
  • 対象件数が多い組織では、1トランザクションのDML行数上限に当たるのでDatabase.Batchableに分けます

Salesforceの導入・運用についてご相談ください

導入前の検討から、お使いの環境の改修・運用、AIとの連携まで承ります。状況を伺ったうえで、進め方をご提案します。