Apex

項目セットの項目名から動的SOQLを組み立てる安全な方法

画面に出す項目を管理者が自由に選べるようにしたいとき、項目セットが使えます。項目名をSOQLへどう渡せば安全か、利用者の入力が混ざる場面でどう守るかを書きます。

2023.09.27

なぜ項目セットを動的SOQLに使うのか

Visualforceページに表示する項目を、コードを直さずに管理者側で増減させたい場面があります。項目セットを使うと、表示する項目の一覧を設定画面で管理し、Apexは項目セットの中身をそのまま動的SOQLのSELECT句に使えます。

項目セットを作成する

  1. オブジェクトマネージャを開く

    設定 > オブジェクトマネージャ > 取引先 を開きます。

  2. 項目セットを新規作成する

    項目セット > 新規から、項目セットラベルとAPI参照名(例: AccountFieldSetSample)を入力します。

  3. 表示したい項目を追加する

    「項目セットで使用可能」の一覧から、表示したい項目をドラッグして「このフィールドセットに含まれる項目」へ移し、保存します。

取引先の項目セット編集画面。下の枠に取引先IDと取引先名が入っている
項目セットの編集画面です。赤枠の中へ、表示したい項目を上からドラッグして入れます。
項目セットの一覧。取引先項目セットサンプルが1件だけ登録されている
作成したあとの一覧です。赤枠のAPI参照名が、Apexから指定する名前になります。

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で使える形で取り出します。

項目名をそのまま連結してよい理由

項目セットの中身は、管理者がSetupで定義した組織のメタデータです。実行時に利用者が入力する値ではありません。getFieldPath()が返す文字列は、その項目セットに登録済みの項目のAPI参照名に限られるため、SELECT句へそのまま連結しても、利用者の入力が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を表示します。

取引先のアクション詳細。種別がカスタムVisualforce、ページ名が指定されている
呼び出し口のアクション定義です。アクション種別がカスタムVisualforce、Visualforceページが作成したページになっている点を見てください。
取引先のレコードページ。右上に項目セット表示サンプルのボタンが並んでいる
取引先の詳細画面です。赤枠のボタンから、作成したページを開きます。所有者の欄は伏せています。
モーダルに取引先のIdと名前が1行ずつ表示されている
開いた結果です。赤枠に、項目セットで選んだ項目の値が出ていれば成功です。

利用者の入力を混ぜるときの注意

利用者が選んだ項目名で絞り込みたい場合、項目名は項目セットのときと違って信用できません。値とは違い、項目名やオブジェクト名のような識別子はバインド変数にできないため、次のどちらかで守ります。

混ざる場所対処
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()、項目名・オブジェクト名はホワイトリスト照合で扱います
  • 識別子(項目名・オブジェクト名)はバインド変数にできないことが、値の扱いと違う一番の注意点です

参考:当時の画面

記事を最初に書いた当時の画面です。いまの手順と違うところは、各画像の説明に書いています。

項目セットマスタのレコード詳細。SOQL文1から3にクエリの断片が入っている
SOQL文をレコードに分けて持たせていた当時の作りです。「ここで間違えやすい」の表で、避ける書き方として挙げたものです。

Salesforceの導入・運用についてご相談ください

導入前の検討から、お使いの環境の改修・運用、AIとの連携まで承ります。状況を伺ったうえで、進め方をご提案します。