function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
iwakiiwaki 

Apexガバナ制限に抵触したトリガの修正方法

以下のようなトリガーを作成しています。
データローダで100件ほどinsertしたところ、「System.LimitException: Too many SOQL queries: 101」となってしまいました。
 
trigger setTrainingHistory on education_training__c (after insert,after update) {
    
    List<Contact> ths = new List<Contact>();
    List<Contact> con = [SELECT TrainingHistoryforStaffwin__c FROM Contact
                         WHERE Id IN (SELECT staff__c FROM education_training__c
                                       WHERE Id =: Trigger.New)];
    
    for( Contact c : con){
        c.TrainingHistoryforStaffwin__c = '';
            for( education_training__c e : [SELECT Id, result_print__c FROM education_training__c
                                            WHERE staff__c =: c.Id AND (Status__c ='支払済' OR Status__c = '受講済') ORDER BY ProgramSort__c]){
                c.TrainingHistoryforStaffwin__c += e.result_print__c;
            }
        ths.add(c);
    }    
    update ths;
}
具体的にどの部分を修正したらよいものかわからず、現在はinsertするcsvを分割して行っている状態です。
修正内容をご教示いただけないでしょうか。
何卒宜しくお願い申し上げます。
 
Best Answer chosen by iwaki
Taiki YoshikawaTaiki Yoshikawa
forループの中でSOQLクエリを実行している部分がガバナ制限エラーが発生している原因です。
最初に取得した取引先責任者IDを条件にループの外でエデュケーショントレーニングの情報を取得します。
取引先責任者IDを条件にマップにセットしておけばループ内でクエリを実行しなくても対象レコードを取得できると思います。

ざっくりですが、こんな感じだと思います。
trigger setTrainingHistory on education_training__c (after insert,after update) {
    // Update用の取引先責任者リスト
    List<Contact> ths = new List<Contact>();
    // 取引先責任者取得
    List<Contact> con = [SELECT TrainingHistoryforStaffwin__c FROM Contact 
                            WHERE Id IN (SELECT staff__c FROM education_training__c WHERE Id =: Trigger.New)];
    // エデュケーションTraining取得
    // ※取引先責任者リストのIDを条件に取得
    List<education_training__c> education_training_list = [
        SELECT
             Id
            ,result_print__c
            ,staff__c
        FROM
            education_training__c
        WHERE
            staff__c IN: con
            AND (
                Status__c ='支払済' 
                OR
                Status__c = '受講済'
            )
        ORDER BY ProgramSort__c
    ];
    // 取引先責任者IDをキーにマップ変数にセット
    Map<Id, List<education_training__c>> education_training_map = new Map<Id, List<education_training__c>>();
    for (education_training__c e : education_training_list) {
        // マップにセットするためのリスト
        List<education_training__c> wkEduList = new List<education_training__c>();

        // マップのキーにセット済みか確認
        if (education_training_map.containsKey(e.Staff__c)) {
            wkEduList = education_training_map.get(e.Staff__c);
        }

        // リストに追加
        wkEduList.add(e);

        // マップに追加
        education_training_map.put(e.Staff__c, wkEduList);
    }

    // 取引先責任者をループしながらMap変数にセットしたeducation_training__cリストを取得。
    // TrainingHistoryforStaffwin__c項目に値をセットして、Update用リストに追加していく。
    for( Contact c : con){
        if (education_training_map.containsKey(c.Id)) {
            List<education_training__c> list = education_training_map.get(c.Id);
            c.TrainingHistoryforStaffwin__c = '';        
            for (education_training__c e : list) {
                c.TrainingHistoryforStaffwin__c += e.result_print__c;
            }
            ths.add(c);
        }
    }

    // Update
    update ths;
}

※実装イメージなので実際にはもう少しキレイに直して下さい。

All Answers

Taiki YoshikawaTaiki Yoshikawa
forループの中でSOQLクエリを実行している部分がガバナ制限エラーが発生している原因です。
最初に取得した取引先責任者IDを条件にループの外でエデュケーショントレーニングの情報を取得します。
取引先責任者IDを条件にマップにセットしておけばループ内でクエリを実行しなくても対象レコードを取得できると思います。

ざっくりですが、こんな感じだと思います。
trigger setTrainingHistory on education_training__c (after insert,after update) {
    // Update用の取引先責任者リスト
    List<Contact> ths = new List<Contact>();
    // 取引先責任者取得
    List<Contact> con = [SELECT TrainingHistoryforStaffwin__c FROM Contact 
                            WHERE Id IN (SELECT staff__c FROM education_training__c WHERE Id =: Trigger.New)];
    // エデュケーションTraining取得
    // ※取引先責任者リストのIDを条件に取得
    List<education_training__c> education_training_list = [
        SELECT
             Id
            ,result_print__c
            ,staff__c
        FROM
            education_training__c
        WHERE
            staff__c IN: con
            AND (
                Status__c ='支払済' 
                OR
                Status__c = '受講済'
            )
        ORDER BY ProgramSort__c
    ];
    // 取引先責任者IDをキーにマップ変数にセット
    Map<Id, List<education_training__c>> education_training_map = new Map<Id, List<education_training__c>>();
    for (education_training__c e : education_training_list) {
        // マップにセットするためのリスト
        List<education_training__c> wkEduList = new List<education_training__c>();

        // マップのキーにセット済みか確認
        if (education_training_map.containsKey(e.Staff__c)) {
            wkEduList = education_training_map.get(e.Staff__c);
        }

        // リストに追加
        wkEduList.add(e);

        // マップに追加
        education_training_map.put(e.Staff__c, wkEduList);
    }

    // 取引先責任者をループしながらMap変数にセットしたeducation_training__cリストを取得。
    // TrainingHistoryforStaffwin__c項目に値をセットして、Update用リストに追加していく。
    for( Contact c : con){
        if (education_training_map.containsKey(c.Id)) {
            List<education_training__c> list = education_training_map.get(c.Id);
            c.TrainingHistoryforStaffwin__c = '';        
            for (education_training__c e : list) {
                c.TrainingHistoryforStaffwin__c += e.result_print__c;
            }
            ths.add(c);
        }
    }

    // Update
    update ths;
}

※実装イメージなので実際にはもう少しキレイに直して下さい。
This was selected as the best answer
Taiki YoshikawaTaiki Yoshikawa
取引先責任者とエデュケーショントレーニングオブジェクトが主従関係で紐付いている場合はサブクエリでもっとシンプルに取得できると思います。その場合はMap変数に一度セットするという処理が不要になります。
iwakiiwaki
修正のコードありがとうございました!
取引先責任者とエデュケーショントレーニングのオブジェクトは参照関係なのでいただいた内容を基に修正をさせていただきます。