項目セットの項目名から動的SOQLを組み立てる安全な方法
画面に出す項目を管理者が自由に選べるようにしたいとき、項目セットが使えます。項目名をSOQLへどう渡せば安全か、利用者の入力が混ざる場面でどう守るかを書きます。
なぜ項目セットを動的SOQLに使うのか
Visualforceページに表示する項目を、コードを直さずに管理者側で増減させたい場面があります。項目セットを使うと、表示する項目の一覧を設定画面で管理し、Apexは項目セットの中身をそのまま動的SOQLのSELECT句に使えます。
項目セットを作成する
オブジェクトマネージャを開く
設定 > オブジェクトマネージャ > 取引先 を開きます。
項目セットを新規作成する
項目セット > 新規から、項目セットラベルとAPI参照名(例: AccountFieldSetSample)を入力します。
表示したい項目を追加する
「項目セットで使用可能」の一覧から、表示したい項目をドラッグして「このフィールドセットに含まれる項目」へ移し、保存します。
Apexで項目セットの項目名を取得する
Schema.FieldSetのgetFields()は、その項目セットに含まれる項目だけをSchema.FieldSetMemberのリストで返します。getFieldPath()は、動的SOQLでそのまま使える形式の項目パス文字列を返します。
Schema.FieldSet fieldSet =
Schema.SObjectType.Account.fieldSets.getMap().get('AccountFieldSetSample');
List<String> fieldPaths = new List<String>{'Id'};
for (Schema.FieldSetMember member : fieldSet.getFields()) {
fieldPaths.add(member.getFieldPath());
}項目セットに登録された項目のAPI参照名を、動的SOQLで使える形で取り出します。
項目名をそのまま連結してよい理由
SOQLを組み立てて実行する
WHERE句の値(レコードId)はバインド変数で渡します。SELECT句の項目名は項目セットから、WHERE句の値はバインド変数から。文字列連結で作るのは項目名の並びだけです。
public with sharing class AccountFieldSetController { public List<Account> accList { get; private set; } public AccountFieldSetController(ApexPages.StandardController stdCtrl) { Id recordId = stdCtrl.getId(); Schema.FieldSet fieldSet = Schema.SObjectType.Account.fieldSets.getMap().get('AccountFieldSetSample'); List<String> fieldPaths = new List<String>{'Id'}; for (Schema.FieldSetMember member : fieldSet.getFields()) { fieldPaths.add(member.getFieldPath()); } String query = 'SELECT ' + String.join(fieldPaths, ', ') + ' FROM Account WHERE Id = :recordId'; accList = Database.query(query); } }
SELECT句は項目セットの項目名から組み立て、WHERE句のId値はバインド変数recordIdで渡します。利用者入力を連結していません。
Visualforceページからは、標準コントローラの拡張として呼び出します。
<apex:page standardController="Account" extensions="AccountFieldSetController"> <apex:repeat value="{!accList}" var="acc"> <apex:outputText value="{!acc.Name}" /><br/> </apex:repeat> </apex:page>
標準コントローラAccountの拡張としてAccountFieldSetControllerを呼び出し、accListを表示します。
利用者の入力を混ぜるときの注意
利用者が選んだ項目名で絞り込みたい場合、項目名は項目セットのときと違って信用できません。値とは違い、項目名やオブジェクト名のような識別子はバインド変数にできないため、次のどちらかで守ります。
| 混ざる場所 | 対処 |
|---|---|
| WHERE句などの値 | バインド変数(:変数名)を使う。動的SOQLでもDatabase.query内で使えます |
| 値をやむを得ず文字列連結する場合 | String.escapeSingleQuotes()でシングルクォートをエスケープする |
| 項目名・オブジェクト名などの識別子 | あらかじめ許可した名前の集合と突き合わせる(ホワイトリスト化) |
Set<String> allowedFields = new Set<String>{'Name', 'Industry', 'Phone'}; if (!allowedFields.contains(userSelectedField)) { throw new IllegalArgumentException('許可されていない項目です: ' + userSelectedField); }
利用者が選んだ項目名は、許可済みの集合に含まれるかを確認してから使います。含まれない場合は例外を投げて処理を止めます。
ここで間違えやすい
| 間違い | 何が起きるか |
|---|---|
| SOQL文そのものをレコードの項目に保存して読み出す | 保存した文字列が意図せず変わる余地が増えます。項目セットのAPI参照名を直接指定するほうが単純で安全です |
| 利用者の入力をSELECT句へそのまま連結する | SOQLインジェクションが起きます。値はバインド変数、識別子はホワイトリストで扱います |
| 項目セットに無い項目名を動的に許可する | ホワイトリストで確認していない項目名を連結すると、想定外の項目や項目が存在しないエラーにつながります |
| Database.query()に利用者が選んだオブジェクト名をそのまま渡す | オブジェクト名も識別子です。項目名と同じくホワイトリストで照合してから使います |
確認した環境
- 2026年9月/Salesforce Summer '26(APIバージョン67.0)時点のApexリファレンスガイドとSOQLインジェクションのガイドで、FieldSet・FieldSetMemberのメソッドと、動的SOQLの安全な組み立て方を確認しています
まとめ
- 項目セットの中身は組織のメタデータで、利用者入力ではありません。getFieldPath()の結果はSELECT句へそのまま連結できます
- WHERE句の値はバインド変数で渡します。SOQL文そのものをデータとして保存して読み出す設計は避けます
- 利用者が選んだ値を混ぜるときは、値はバインド変数かString.escapeSingleQuotes()、項目名・オブジェクト名はホワイトリスト照合で扱います
- 識別子(項目名・オブジェクト名)はバインド変数にできないことが、値の扱いと違う一番の注意点です
参考:当時の画面
記事を最初に書いた当時の画面です。いまの手順と違うところは、各画像の説明に書いています。
Salesforceの導入・運用についてご相談ください
導入前の検討から、お使いの環境の改修・運用、AIとの連携まで承ります。状況を伺ったうえで、進め方をご提案します。