PROOFロゴ

ENTRY

MENU

シュミレーションアイコン

年収シュミレーション

2025.09.04
  • 技術コラム

Apexの静的SOQLと動的SOQLについて

Apexの静的SOQLと動的SOQLについて

こんにちは!
代表の阿部です!

本日は、Apexの静的SOQLと動的SOQLについてです。
タイトルで何やら難しいそうと思った初心者の方!
全然難しい話じゃないので、ちょっと読んでみると、開発の際の選択肢が広がると思います!

まずは、静的SOQL、動的SOQLについて

静的SOQLとは?

静的SOQLは、コード内で、確定されたSOQL文を記述して、レコード取得します。
例えば以下です。

List<String> Names  = new List<String>{'aaa','bbb','ccc'};
List<Account> Accounts = [SELECT Id , Name FROM Account WHERE Name = :Names];

皆様の知っている普通のSOQL文ですね。
Nameが、’aaa’,’bbb’,’ccc’の取引先が取得されます。
バインド変数(条件句のNamesの部分の事)の中身以外は、確定された文を記述しています。

動的SOQLとは?

では、確定されていない記述のSOQL文とは?それが動的SOQLです。
こちらは、処理の中で、動的にSOQL文自体を構成出来ます。
例えば以下です。

//ユーザー入力を以下の変数で受けると仮定
String nameStr;
Boolean parentFlg; ​
Account parentAccount;

//ユーザー入力により取引先を作る際のサンプル
Account acc​= new Account();
acc.Name = nameStr;
acc.IsParent = parentFlg;
acc.ParentId = parentAccount.Id;
insert acc;

//取得しなおす。
//条件句手前までのSOQL文
String queryString = 'SELECT Id , Name FROM Account WHERE ';
//親取引先True/FalseでSOQL文を分ける
if (IsParent )[
    queryString += 'Name ='+ acc.Name;
}else{
    queryString += 'Parent.Name ='+ acc.Parent.Name;
}
List<Account> accountList = Database.query(queryString);

ちょっとわかりづらいっすね・・・。
取引先をユーザー入力で作成し取得しなおしています。あまり現実的な処理じゃないけど。
上記の例は、取引先を検索する際に、親取引先項目のTrue/Falseによって、SOQLの条件句の記述を分けています。
取得されるのは、以下の条件の取引先です。

  • IsParent =Trueであれば、入力された取引先名で検索
  • IsParent = Falseであれば、入力された取引先の親の取引先名で検索

ということを表現しています。
後述しますが、このSOQL文には重大な欠陥があります。(ミステリー風)
動的SOQLは、最後にDatabeseクラスを呼び出して、DML操作します。Database.queryの部分ですね。

データベースクラス公式リファレンス

静的SOQLはどのようなときに使うの?

基本的には、Apexで作るSOQL文はこちらを採用する場合が多いと思います。処理の中で、必要なレコードを取得する時は、大体これですね。基本的にこっちを使った方がいいです。理由は後述します。

動的SOQLはどのようなときに使うの?

処理の分岐によって、SOQL文を変更したい時です。例えば

  • レコードの値によって、他の値も取得条件に加えたい時(WHERE句に、条件文を追加する分岐を作成します)
  • SELECTする項目を、動的に変更したい時(例えば、”全項目”のような動的な項目など)
  • ユーザー入力によって、取得条件となる項目を変更したい時(例えば、特定のユーザーの入力によってfield1__c,field2__c,field3__c・・・を条件句に加えたいなど)

特にユーザー入力によって動的に条件を変えるSOQL文は、ExperienceCloudで、ポータルサイトの構築などで、よく使います!

動的SOQLの落とし穴

動的に、SOQL文を組み立てられる便利な記述方法ですが、重要な落とし穴があります。
それはSOQL文を入力するユーザーが意図的に、クエリ文を変更する事によって、本来事業側、開発側が意図していないデータベースにアクセスできてしまう事です。

これをSOQLインジェクションと言います

SOQLインジェクションについて

例えば、ユーザーが取引先名を入力して取引先を検索する場合、以下のような記述になります。入力された取引先名であいまい検索をしています。

//ユーザー入力の取引先名を受け取る変数
String accountName = ''
//受け取ったユーザー名をSOQL文の条件句にする​
queryString = 'SELECT Id FROM Account WHERE (IsDeleted = false and Name like \'%' + accountName + '%\') '​

この時、ユーザーが「取引先A」という取引先名を入力すると、削除されていない’取引先A’が出力されます。
さて、それではユーザーが以下のように入力すると、どうでしょう。

'test%') OR (Name LIKE'

出来上がるSOQL文は、以下のようになります。

(コードブロック)

queryString = ‘SELECT Id FROM Account WHERE (IsDeleted = false and Name like ‘%test%’) OR (Name LIKE ‘%’)) ‘

Nameがワイルドカード(なんでもヒットする)となり、すべての取引先が取得されます。

どのようなリスクがある?

SOQLインジェクションは、ITの知見のある方には一般的ですが、初心者の方は、ここを見落としがちです。
アクセス権がしっかりと制御されていれば、いったん大問題は起こりませんが、抜け穴を狙った攻撃をされたり、アクセス件の変更タイミングなどで正しく制御できてない場合、不正アクセスや情報漏洩を引き起こします。

SOQLインジェクション公式リファレンス

どうやって防ぐ?

StringクラスのescapeSingleQuotesメソッドを使用します。
このメソッドは、バインドされた変数の値をシングルクオートで囲み、入力された値で、SOQL文として組み立てられないようにします。書き方は、リファレンスをご参照ください。

Stringクラス公式リファレンス

つまり、先ほどの例でいうと、こんな感じです。

queryString = 'SELECT Id FROM Account WHERE (IsDeleted = false and Name like '% 'test%') OR (Name LIKE ''%')) '

入力された文字列のままの「test%’) OR (Name LIKE」という名前の取引先を探そうとするので、ORで追加使用しようとしたワイルドカードは無効になります。
ちなみに、静的SOQLでは、このエスケープ処理は仕様に組み込まれているので、SOQLインジェクションは起こりません。
これで、SOQLインジェクションは回避できます。

最後に

いかがでしたか?動的SOQLを使わないと実装出来ない要件も沢山あります!

正しく理解して、使いこなせるように出来るといいですね!
では、また!!

ALL BLOG

→

年収シュミレーション

PROOFのエンジニアとして働いた場合の
あなたの想定年収を計算してみましょう

ロール
開発・運用支援の現場経験年数
ご経験のある最も上流の工程
お持ちのスキルをご選択ください複数選択可
リーダーの経験年数

あなたの推定年収は‥

4,464,000 円です!

「この年収を稼ぎたい!」と
PROOFにご興味いただいた方
ぜひカジュアル面談してみませんか?

カジュアル面談に申し込む

→
  • ※案件単価の目安
    3年目:60~80万円 4年目:70~90万円 5年目以降:80~110万円程度
  • ※計算は目安です。
    待期期間の発生や個別の案件単価により変動します。
  • ※開発現場での経験年数は、アドミン経験を含む場合は業務内容を
    お伺いした上で双方合意の上で起算します。
  • ※開発現場での経験年数は、勉強期間、社内OJT期間は含みません。