SOQLの構文
SELECT 項目/サブクエリ
FROM オブジェクト名
[WHERE 検索条件]
[GROUP BY 集計単位の項目 [HAVING 集計結果の検索条件]]
[ORDER BY 項目 {ASC|DESC} [NULLS {FIRST|LAST}]]
[LIMIT 検索結果の上限件数]
[OFFSET 無視する検索結果の行数]
SELECT fieldList [subquery][...]
[TYPEOF typeOfField whenExpression[...] elseExpression END][...]
FROM objectType[,...]
[USING SCOPE filterScope]
[WHERE conditionExpression]
[WITH [DATA CATEGORY] filteringExpression]
[GROUP BY {fieldGroupByList|ROLLUP (fieldSubtotalGroupByList)|CUBE (fieldSubtotalGroupByList)}
[HAVING havingConditionExpression] ]
[ORDER BY fieldOrderByList {ASC|DESC} [NULLS {FIRST|LAST}] ]
[LIMIT numberOfRowsToReturn]
[OFFSET numberOfRowsToSkip]
[FOR {VIEW | REFERENCE}[,...] ]
[ UPDATE {TRACKING|VIEWSTAT}[,...] ]
クエリに関する制限
SOQLの制限
- WHERE 句の文字列は 4,000 文字を超えることができません。
- 子-親リレーション(
SELECT Account.Name
など)の制限- 1回のクエリに指定できる子-親リレーションは、55 個以下です。
- 指定された各リレーションで、1 つの子-親リレーションに指定できるレベルは 5 つ以下です。たとえば、Contact.Account.Owner.FirstName は 3 レベルです。
- 親-子リレーション(
SELECT (SELECT Id FROM OpportunityLineItems)
など)の制限- 1回のクエリに指定できる親-子リレーションは、20 個以下です。
- REST、SOAPの場合、最大 5 レベルの親-子リレーションを照会できます(v58.0以降)。Big Object、外部オブジェクトとApex、Bulk APIでは、5レベルの親-子リレーションをサポートしていません。
リレーションクエリ制限について | SOQL および SOSL リファレンス | Salesforce Developers
トランザクション当たりのApex制限
- SOQLクエリ回数:同期Apex 100、非同期Apex 200
- サブクエリ回数:同期Apex 300、非同期Apex 6001
- SOQLクエリで取得するレコード件数(合計):50,000
Apex制限の留意事項
- カスタムメタデータのクエリはSOQLクエリ回数にカウントされません。
- サブクエリはSOQLクエリ回数にカウントされません。サブクエリ回数(AggregateQueries)でカウントされます。ただし、WHERE句のサブクエリの回数はカウントされません。
- SELECT句のサブクエリは「SOQLクエリで取得するレコード件数(合計)」にカウントされます。WHERE句のサブクエリの取得件数はカウントされません。
- COUNT()、COUNT(fieldname)以外の集計関数を使用すると、集計される対象のレコード数が「SOQLクエリで取得するレコード件数(合計)」にカウントされます。
それぞれの上限、現在値は下記のメソッドで取得できます。
Limit.getQueries();
Limit.getLimitQueries();
Limit.getQueryRows();
Limit.getLimitQueryRows();
Limit.getAggregateQueries();
Limit.getLimitAggregateQueries();
Salesforce プラットフォームの制限
- ユーザごとに同時に開くクエリカーソルの制限
- SOAP APIクエリカーソル最大数:10
- メタデータAPIクエリカーソル最大数:10
- Apexクエリカーソル最大数:50
- Apex一括処理startメソッドのクエリカーソル最大数:15
- Apex一括処理executeメソッドのクエリカーソル最大数:5
- Apex一括処理finishメソッドのクエリカーソル最大数:5
- Visualforceカーソル最大数:5
- SOQLクエリの最大実行時間(タイムアウト):120 秒
クエリカーソルについて
クエリカーソルが最大数に達したとき、古いクエリカーソルから削除されます。queryMore()でクエリカーソルにアクセスします。このとき、クエリカーソルが存在していないと、INVALID_QUERY_LOCATOR
が発生します。
AttachmentのBodyなどbase64型を含むクエリは1件ごとにqueryMoreが発生します。APIを使用してbase64型を含むクエリを発行して繰り返し処理を実装すると長時間クエリカーソルへのアクセスを行うことになるので、上記のエラーが発生しやすくなります。そのためbase64型項目をクエリする場合は1件ずつクエリを発行することをお勧めします。
文字数
SOQLの文法
日付型でフィルターする
yyyy-MM-dd
の形式で比較します。TODAY
などの日付リテラルを使用できます。
SELECT Id
FROM Opportunity
WHERE CloseDate > 2022-05-16
日時型でフィルターする
yyyy-MM-ddThh:mm:ss+09:00
の形式で比較します。+09:00
の箇所はタイムゾーンです。日本時間の時は+09:00
にします。TODAY
などの日付リテラルを使用できます。
SELECT Id
FROM Account
WHERE CreatedDate >= 2022-05-16T00:00:00+09:00
複数選択リストでフィルターする
複数選択リストでフィルターする場合に使用できる演算子は=
、!=
、INCLUDES
、EXCLUDES
、;
です。
公式リファレンスには記述がないですが、一応IN
も使えるようです。IN
は完全一致になるため誤用に注意です。
- AAAのみ:
Selectable__c = 'AAA'
- AAAとBBBのみ:
Selectable__c = 'AAA;BBB'
- AAAを含む:
Selectable__c INCLUDES ('AAA')
- AAAとBBBを含む:
Selectable__c INCLUDES ('AAA;BBB')
- AAAとBBBを含む、またはCCCを含む:
Selectable__c INCLUDES ('AAA;BBB', 'CCC')
ApexでINCLUDES
にListを使用する場合、バインド変数が使用できないため動的クエリにする必要があります。
List<String> values = new List<String>{'AAA;BBB', 'CCC'};
String valuesAsString = '(\'' + String.join(values, '\', \'') + '\')';
String query = '';
query += 'SELECT Id FROM Account ';
query += 'WHERE Selectable__c INCLUDES ' + valuesAsString);
List<Account> accList = Database.query(query);
- 複数選択リストのクエリ | SOQL および SOSL リファレンス | Salesforce Developers
- apex - using collections in SOQL INCLUDES - Salesforce Stack Exchange
項目の値がリストに含まれているかでフィルターする
SELECT Id, Name
FROM Account
WHERE Name IN ('sForce', 'United Oil & Gas Corp.')
Set<String> names = new Set<String>{'sForce', 'United Oil & Gas Corp.'};
List<Account> accountList = [
SELECT Id, Name
FROM Account
WHERE Name IN :names
];
項目の値がリストに含まれていないでフィルターする
SELECT Id, Name
FROM Account
WHERE Name NOT IN ('sForce', 'United Oil & Gas Corp.')
Set<String> names = new Set<String>{'sForce', 'United Oil & Gas Corp.'};
List<Account> accountList = [
SELECT Id, Name
FROM Account
WHERE Name NOT IN :names
];
結果の一部だけ得る(オフセット)
LIMIT 取得件数
とOFFSET スキップする件数
を使用します。
OFFSETは2000まで指定できます。2001以上を指定するとNUMBER_OUTSIDE_VALID_RANGE
が発生します。
OFFSETを使用するとLIMITに2001以上指定していても2000件までしか取得できません。
/* 301件目から100件取得する */
SELECT Id
FROM Account
ORDER BY CreatedDate DESC
LIMIT 100
OFFSET 300
重複を除いた件数を取得する
COUNT_DISTINCT
を使用します。NULLも除外されます。COUNT
とガバナ制限「SOQLクエリで取得するレコード件数」のカウントの仕方が異なるので注意が必要です。COUNT_DISTINCT
は集計対象レコードの数がガバナ制限の対象です。
SELECT COUNT(Id), COUNT_DISTINCT(Product2Id)
FROM OpportunityLineItem
WHERE句でサブクエリを使用する
項目 IN (SELECT 比較する項目 FROM ...)
または項目 NOT IN (SELECT 比較する項目 FROM ...)
のように記述することで、WHERE句でサブクエリを利用できます。
WHERE句でサブクエリを使用する場合、OR
は使用できません。
/* OK */
SELECT Name FROM Account WHERE Id IN (SELECT AccountId FROM Opportunity WHERE Id = '0065g00000Sr6EwAAJ')
SELECT Phone FROM Account WHERE (Phone != null) AND (Id IN (SELECT AccountId FROM Contact WHERE Phone != null))
/* NG: Semi join sub-selects are not allowed with the 'OR' operator */
SELECT Phone FROM Account WHERE (Phone != null) OR (Id IN (SELECT AccountId FROM Contact WHERE Phone != null))
SOQLをApexで使用する
動的にクエリを組み立てる
動的なクエリを文字列で作成し、Database.query()
に渡すことでクエリを実行することができます。
静的なクエリとことなり、バインド変数に.
は使用できません。使用した場合、Variable does not exist
となります。
ユーザ・API・DBから渡された値は、SOQLインジェクションを避けるためString.escapeSingleQuotes()
でエスケープする必要があります。
List<String> fields = new List<String>{'Id', 'Name'};
String name = 'Force';
String query = '';
query += 'SELECT ' + String.join(fields, ',') + ' ';
query += 'FROM Account ';
query += 'WHERE Name LIKE \'%' + String.escapeSingleQuotes(name) + '%\'';
List<SObject> fetched = Database.query(query);
クエリした結果を変数に代入する
SOQLの結果はSObjectのリストで返ります。1件も取得できなかった場合はnullではなく、空のリストが返却されます。
結果が1件の場合は、SObject型に代入できますが、確実に1件だけ取得できるときのみ使用できます。1件もない場合はQueryException: List has no rows for assignment to SObject
、結果が複数ある場合はQueryException: List has more than 1 row for assignment to SObject
が発生します。
// SObjectのコレクション
List<Account> accList = [SELECT Id FROM Account];
Set<Account> accSet = new Set<Account>([SELECT Id FROM Account]);
Map<Id, Account> accMap = new Map<Id, Account>([SELECT Id FROM Account]);
// SObject型
Account acc = [SELECT Name FROM Account WHERE Id = :accId LIMIT 1];
集計関数を使用する
集計関数を使用したSOQLの結果はAggregateResultオブジェクトのリストになります。各項目の値はAggregateResultオブジェクトのgetメソッドで取得します。Apexでも集計関数の別名を使用できます。
for (AggregateResult ar : [
SELECT AccountId, COUNT(Id) num, MAX(Amount) max, MIN(Amount) min, AVG(Amount) avg
FROM Opportunity
GROUP BY AccountId
]) {
System.debug(LoggingLevel.DEBUG, ar);
// AggregateResult:{AccountId=0015h00000SrA4lAAF, num=1, max=235000, min=235000, avg=235000.0}
System.debug(LoggingLevel.DEBUG, 'accountId:' + ar.get('AccountId'));
System.debug(LoggingLevel.DEBUG, ' max:' + ar.get('max'));
}
別名を使用しない場合、expr0
などの暗黙的列名が使用されます。
for (AggregateResult ar : [
SELECT AccountId, COUNT(Id) num, MAX(Amount), MIN(Amount), AVG(Amount)
FROM Opportunity
GROUP BY AccountId
]) {
System.debug(LoggingLevel.DEBUG, ar);
// AggregateResult:{AccountId=0015h00000SrA4lAAF, num=1, expr0=235000, expr1=235000, expr2=235000.0}
System.debug(LoggingLevel.DEBUG, 'accountId:' + ar.get('AccountId'));
System.debug(LoggingLevel.DEBUG, ' max:' + ar.get('expr0'));
}
- SOQL 集計関数の使用 | Apex 開発者ガイド | Salesforce Developers
- GROUP BY での別名の使用 | SOQL および SOSL リファレンス | Salesforce Developers
ApexでSOQLを使用するときの考慮事項
- クエリは極力セレクティブにします。特にトリガでは非セレクティブなクエリでクエリ対象が200,000件超える場合にはエラーが発生してしまいます。
- クエリで取得したレコードで繰り返し処理を行うときはSOQL For ループを使用します。リストに詰めるよりヒープサイズを削減することができます。
- 大量の子レコード(200件以上)を取得する可能性がある場合は、直接アクセスせずにforループで子レコードを反復処理をします。200件以上の子レコードに直接アクセスするとエラーになります。
- COUNT() および COUNT(fieldname) 以外集計関数を使用するときはガバナ制限(Too many query rows)に注意が必要です。集計に含まれるレコード数もカウントされます。
その他
クエリのパフォーマンスを測定する
クエリプランツールを使用します。また、REST APIのqueryリソースにexplainパラメータを指定することでもパフォーマンスに関するフィードバックが得られます(ベータ機能)。
クエリにかかる時間を短縮したい
Visual Studio Codeでクエリを作成したい
Salesforce公式のVSCode拡張機能に含まれるSOQL Query Builderを使用する。
カスタムメタデータのソート順がおかしい
カスタムメタデータ型のクエリでは、ORDER BYする項目はSELECTしていないとソート順が保証されない、という不具合があります。
参考
- SOQL および SOSL リファレンス
- SOQL Basics | Developer Quick Takes - YouTube
- Salesforce SOQL: クエリと制限 | セールスフォース・ベン
-
ドキュメントには「親子リレーションのサブクエリを使用する SOQL クエリでは、各親子リレーションは追加クエリとみなされます。これらのクエリタイプは、最上位クエリ数の 3 倍に制限されています。」とありますが、実際は最上位クエリの制限数の3倍のようです。 ↩