Dynamic SOQL における Set<Id> のエレガントな使用法


Apex の Dynamic SOQL で `Set<Id>` を利用して `IN` 条件を構築する場合、最もエレガントで効率的な方法はバインド変数を使用することです。このアプローチは、スクリプトステートメントやヒープスペースを削減し、セキュリティも向上させます。


コード例: バインド変数を使用した Dynamic SOQL

以下の例では、`Set<Id>` を直接 Dynamic SOQL に渡す方法を示しています:

Map<Id, Account> accts = new Map<Id, Account>([SELECT Id FROM Account]);
Set<Id> accountIds = accts.keySet();

// バインド変数を使用した Dynamic SOQL
String query = 'SELECT Id, Name FROM Contact WHERE AccountId IN :accountIds';
List<Contact> contacts = Database.query(query);

System.debug(contacts);

特徴

  1. 安全性:

    • バインド変数を使用すると、SQL インジェクションのリスクが自動的に回避されます。
    • Salesforce が入力データを適切にエスケープします。
  2. パフォーマンス:

    • バインド変数を使用すると、クエリ文字列の長さが `Set` のサイズに影響されません。
    • 10,000 文字の SOQL 制限を回避できます。
  3. 効率性:

    • ヒープスペースとスクリプトステートメントの消費が削減されます。
  4. 制約:

    • バインド変数には、直接のメソッド呼び出しや計算式は使用できません。
      • ❌ `AccountId IN :accts.keySet()`
      • ✅ `AccountId IN :accountIds`(事前に変数に格納する必要があります)

メソッドを使った変換の非推奨例

一部の開発者は、`Set<Id>` を文字列に変換し、SOQL クエリを手動で構築することを検討します。しかし、この方法は推奨されません。

非推奨の方法

以下のコードはエレガントではなく、セキュリティとパフォーマンスの問題を引き起こす可能性があります:

Set<Id> accountIds = new Set<Id>{'001D000000IqhSL', '001D000000IqhSM'};
String idsString = '(' + String.join(new List<String>(accountIds), ',') + ')';

String query = 'SELECT Id, Name FROM Contact WHERE AccountId IN ' + idsString;
List<Contact> contacts = Database.query(query);

System.debug(contacts);

問題点

  1. SQL インジェクションのリスク:

    • バインド変数を使用せずにクエリ文字列を直接構築すると、不正な入力による攻撃のリスクが高まります。
  2. クエリ長の制限:

    • `Set<Id>` の要素が多い場合、SOQL の 10,000 文字制限にすぐ到達します。
  3. パフォーマンスの低下:

    • 手動で文字列を構築すると、スクリプトステートメントの消費量が増加します。

結論

  • 推奨される方法:

    • 必ずバインド変数を使用して Dynamic SOQL を構築してください。
  • バインド変数のメリット:

    • セキュリティ: SQL インジェクションを回避。
    • パフォーマンス: クエリ長制限を回避し、リソース効率を向上。
    • 可読性: クエリがシンプルで保守性が高い。

コメント