Apexは ガバナ制限(Governor Limits) が厳しく、効率の悪いコードを書くと SOQLクエリの上限(1回のトランザクションで最大100回) に達してしまい、エラーが発生します。
ここでは、ベストプラクティスとアンチパターン を初心者にもわかるように、具体的なコード例を使って整理しました。
後編:
❌ 1. アンチパターン: ループ内のクエリ
問題点
- ループ内でSOQLクエリを実行すると、短時間でクエリ制限(100回)に達してしまう。
- 大量データ処理に向いていない(ガバナ制限違反)。
❌ 悪い例(NGコード)
public void updateAccountContacts() {
List<Account> accounts = [SELECT Id, Name FROM Account]; // 1回目のクエリ
for (Account acc : accounts) {
List<Contact> contacts = [SELECT Id FROM Contact WHERE AccountId = :acc.Id]; // ❌ ループ内クエリ
for (Contact con : contacts) {
con.LastName = acc.Name + ' Customer';
}
update contacts; // DML操作もループ内にあるとさらに悪化
}
}
💥 何が問題か?
- 取引先(Account)の数だけSOQLが実行される。(100件以上ならエラー)
- 非効率なDML(データ更新) でガバナ制限に引っかかる。
✅ 良い例(修正コード)
public void updateAccountContacts() {
// 1回のクエリでアカウント情報を取得
List<Account> accounts = [SELECT Id, Name FROM Account];
// 取得したアカウントのIDをリスト化
Set<Id> accountIds = new Set<Id>();
for (Account acc : accounts) {
accountIds.add(acc.Id);
}
// 1回のクエリで対象のコンタクトを取得(親のIDでフィルタ)
List<Contact> contacts = [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accountIds];
// まとめてデータを更新
for (Contact con : contacts) {
con.LastName = 'Updated Customer';
}
update contacts; // 1回のDMLで処理
}
✨ 修正のポイント
- 1回のSOQLで必要なデータをまとめて取得
- ループ内ではなく、1回のDMLで一括更新
- クエリ回数とDML回数を最小化し、ガバナ制限を回避
✅ 2. 原則: コレクションの使用によるクエリ数の最小化
コレクション(SetやMap)を活用することで、クエリの回数を削減できる。
✅ 良い例(Setを活用)
public void updateContactsUsingCollections() {
List<Account> accounts = [SELECT Id, Name FROM Account];
// Setを使ってアカウントIDを一括管理
Set<Id> accountIds = new Set<Id>();
for (Account acc : accounts) {
accountIds.add(acc.Id);
}
// アカウントIDを一括検索してコンタクトを取得
List<Contact> contacts = [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accountIds];
// Mapを使ってアカウント名を取得
Map<Id, String> accountNameMap = new Map<Id, String>();
for (Account acc : accounts) {
accountNameMap.put(acc.Id, acc.Name);
}
// 一括更新
for (Contact con : contacts) {
con.LastName = accountNameMap.get(con.AccountId) + ' Customer';
}
update contacts;
}
✨ 修正のポイント
- Setを使い、対象のIDリストを作成(重複なし)
- Mapを使い、関連情報(Account名)を取得
- 最小限のSOQLで最大限のデータを取得し、効率化
⚠️ 3. 注意: 親-子クエリのカウントは異なる
親オブジェクトのレコード数 vs 子オブジェクトのレコード数
List<Account> accounts = [SELECT Id, (SELECT Id FROM Contacts) FROM Account];
この場合、クエリの回数は1回だが、実際に取得するレコード数は「親の件数×子の件数」になる。
✅ 4. 原則: 必要なものを単一のクエリで取得
1回のSOQLで、できるだけ多くの情報を取得することで、クエリの回数を減らす。
❌ 悪い例(複数回のクエリ)
public void processAccounts() {
List<Account> accounts = [SELECT Id FROM Account];
for (Account acc : accounts) {
List<Contact> contacts = [SELECT Id FROM Contact WHERE AccountId = :acc.Id]; // ❌ ループ内クエリ
System.debug('Contacts: ' + contacts.size());
}
}
✅ 良い例(1回のクエリで済ませる)
public void processAccounts() {
List<Account> accounts = [SELECT Id, (SELECT Id FROM Contacts) FROM Account];
for (Account acc : accounts) {
System.debug('Contacts: ' + acc.Contacts.size()); // ✅ ループ内でクエリなし
}
}
🎯 まとめ
項目 | ❌ 悪い例 | ✅ 良い例 |
---|---|---|
ループ内のクエリ | ループの中でSOQL実行 | 1回のクエリでデータ取得 |
コレクションの活用 | 都度クエリで取得 | Set/Mapでデータ管理 |
親-子のレコード数 | 親と子を個別に取得 | 関連オブジェクトをネスト取得 |
単一クエリで取得 | 必要なデータを何度も取得 | 1回のクエリで最大限の情報を取得 |