「Too many SOQL queries: 101」エラーの原因と対処法

エラーの概要

Too many SOQL queries: 101」は、SalesforceにおけるSOQLクエリ実行数が1つのトランザクションで許容される上限(100回)を超えた場合に発生するランタイムエラーです。このエラーは、特に複雑なコードや無駄なクエリが多い処理で起きやすく、パフォーマンスの問題につながる可能性があります。

主な原因

  1. トリガー内でのSOQLクエリの乱用
    • トリガーがレコード単位で動作しているため、レコード数に応じてクエリが繰り返し実行される。
  2. ループ内でのSOQLクエリの使用
    • ループ処理の中でSOQLクエリを実行している場合、クエリ数が急増する。
  3. 呼び出しチェーン
    • 複数のトリガーやクラスが連携して動作する場合、それぞれでSOQLクエリが発生する。
  4. 無駄なデータ取得
    • 実際には必要のないデータを頻繁に取得している場合。

解決方法

  1. SOQLクエリのバルク化
    • SOQLクエリをループの外に移動し、複数のレコードをまとめて取得します。

      NG例: ループ内でSOQLクエリを実行

      // 全てのアカウントを取得するクエリ
      List<Account> accounts = [SELECT Id FROM Account];
      // 各アカウントの連絡先を取得するループ
      for (Account acc : accounts) {
          // 各アカウントの連絡先を取得するクエリ
          List<Contact> contacts = [SELECT FirstName FROM Contact WHERE AccountId = :acc.Id];
          // 連絡先の処理
          for (Contact con : contacts) {
              System.debug('連絡先名: ' + con.FirstName);
          }
      }
              

      改善例: 事前取得で関連データを効率化

      // 全てのアカウントを取得するクエリ
      List<Account> accounts = [SELECT Id, 
                                       (
                                           SELECT FirstName
                                           FROM Contacts
                                       ) 
                                       FROM Account];
      for (Account acc : accounts) {
          for (Contact con : acc.Contacts) {
              // 連絡先の処理
              System.debug('連絡先名: ' + con.FirstName);
          }
      }
              

      大量のデータやループ内でSOQLクエリが多い場合、別のエラーが発生する可能性がございます。その場合、4番目の例をお勧めいたします。

  2. トリガーの実行制御
    • トリガーフレームワークを導入し、特定の条件下でのみトリガーを実行する。

      フラグを使ったトリガー制御例

      public class TriggerExecutionController {
          public static Boolean isTriggerExecutionAllowed = true;
      }
      
      // トリガー側
      trigger AccountTrigger on Account (before insert, before update) {
          if (!TriggerExecutionController.isTriggerExecutionAllowed) {
              return; // 条件によりトリガーを終了
          }
          // トリガー処理続行
      }
              
  3. SOQLクエリの数をモニタリング
    • デバッグログを利用して、クエリの実行状況を確認し、無駄なクエリを特定する。
  4. 関連データの事前取得
    • 必要な関連データを一括取得し、繰り返しのクエリを避ける。

      改善例: 事前取得で関連データを効率化

      // マップ定義
      Map<Id, LIST<Contact>> ContactsMap = new Map<Id, LIST<Contact>>();
              
      // 全てのアカウントを取得するクエリ
      List<Account> accounts = [SELECT Id, 
                                       (
                                           SELECT FirstName
                                           FROM Contacts
                                       ) 
                                       FROM Account];
                                       
      // Contactデータを保存する
      for (Account acc : accounts) {
          if (!acc.Contacts.isEmpty()) {
              contactsMap.put(acc.Id, acc.Contacts);
      } else { contactsMap.put(acc.Id, new List<Contact>()); // 連絡先が存在しない場合、空のリストを追加
      } } // Contactデータを使用する for (Account acc : accounts) { LIST<Contact> ContactList= ContactsMap .get(acc.Id); }

その他のポイント

  • デバッグログでガバナ制限をモニタリング
    クエリの回数や実行タイミングをデバッグログで確認し、無駄な処理を特定します。
  • Apex BatchやQueueableを活用
    大量データを処理する際には、Apex BatchやQueueableを使用し、トランザクションを分割する設計を検討します。

コメント