sf-soql について
Salesforce 開発者向けの SOQL クエリ構築・最適化ガイドです。リレーション、集計、SOSL、動的 SOQL を扱い、ユーザーモードのセキュリティ、バインド変数、選択的な条件、各種制限を重視します。
主な機能
- 01リレーション・集計クエリの構築
- 02全文検索に SOSL を使用
- 03ユーザーモードとバインド変数の適用
- 04非効率なクエリパターンの特定
- 05クエリプランによる性能分析
- 06クエリ制限と構文上の注意点を確認
活用シーン
- 01アクセス制御に沿った Salesforce クエリの作成
- 02複数オブジェクトを横断したテキスト検索
- 03大規模データ向けクエリの最適化
- 04クエリプランや制限の問題を調査
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ clientell-ai/ salesforce-skills --skill sf-soqlインストール後、Claude Code で/sf-soqlを入力して読み込まれたか確認します。
同梱ファイルreferences/soql-reference.md
詳細
SOQL クエリビルダーとオプティマイザー
Salesforce SOQL のスペシャリストとして、最適化された安全なクエリを作成します。
セキュリティを最優先
- CRUD/FLS を適用するため、必ず
WITH USER_MODEを使用します - 動的 SOQL で文字列連結を使用してはいけません。バインド変数を使用します
Database.query()は、動的クエリが本当に必要な場合にのみ使用します
// GOOD
List<Account> accounts = [
SELECT Id, Name, Industry
FROM Account
WHERE Industry = :industryFilter
WITH USER_MODE
LIMIT 200
];
// BAD — injection risk
String query = 'SELECT Id FROM Account WHERE Name = \'' + userInput + '\'';
クエリパターン
親から子へのクエリ(サブクエリ)
SELECT Id, Name,
(SELECT Id, FirstName, LastName FROM Contacts)
FROM Account
WHERE Industry = 'Technology'
WITH USER_MODE
子から親へのクエリ(ドット表記)
SELECT Id, FirstName, Account.Name, Account.Industry
FROM Contact
WHERE Account.Industry = 'Technology'
WITH USER_MODE
集計クエリ
SELECT Industry, COUNT(Id) cnt, SUM(AnnualRevenue) totalRevenue
FROM Account
WHERE Industry != null
WITH USER_MODE
GROUP BY Industry
HAVING COUNT(Id) > 5
ORDER BY COUNT(Id) DESC
多態型(TYPEOF)
SELECT Id, Subject,
TYPEOF What
WHEN Account THEN Name, Industry
WHEN Opportunity THEN Name, StageName, Amount
END
FROM Task
WITH USER_MODE
セミジョインとアンチジョイン
-- Semi-join: Accounts WITH contacts
SELECT Id, Name FROM Account
WHERE Id IN (SELECT AccountId FROM Contact)
WITH USER_MODE
-- Anti-join: Accounts WITHOUT opportunities
SELECT Id, Name FROM Account
WHERE Id NOT IN (SELECT AccountId FROM Opportunity)
WITH USER_MODE
SOSL(検索言語)
複数のオブジェクトを横断する全文検索には、SOSL を使用します。
FIND {SearchTerm} IN ALL FIELDS
RETURNING Account(Id, Name WHERE Industry = 'Tech'),
Contact(Id, FirstName, LastName)
LIMIT 20
- SOSL を使用する場合:オブジェクトを横断したテキスト検索、あいまい検索、部分一致検索
- SOQL を使用する場合:完全一致、リレーションクエリ、集計、DML 関連のクエリ
- ガバナ制限:1 トランザクションあたり 20 件の SOSL クエリ
日付リテラル
| リテラル | 意味 |
|---|---|
TODAY, YESTERDAY, TOMORROW | 暦日 |
THIS_WEEK, LAST_WEEK, NEXT_WEEK | 日曜日から土曜日までの週 |
THIS_MONTH, LAST_MONTH, NEXT_MONTH | 暦月 |
THIS_QUARTER, LAST_QUARTER | 暦四半期 |
THIS_YEAR, LAST_YEAR, NEXT_YEAR | 暦年 |
LAST_N_DAYS:n | 過去 n 日間(今日を含む) |
NEXT_N_DAYS:n | 今後 n 日間(今日を含む) |
LAST_90_DAYS | 過去 90 日間 |
THIS_FISCAL_QUARTER, THIS_FISCAL_YEAR | 会計期間 |
N_DAYS_AGO:n | ちょうど n 日前 |
FIELDS() 関数
SELECT FIELDS(ALL) FROM Account LIMIT 200 -- All fields (LIMIT required)
SELECT FIELDS(STANDARD) FROM Account -- Standard fields only
SELECT FIELDS(CUSTOM) FROM Account -- Custom fields only
動的 SOQL
String query = 'SELECT Id, Name FROM Account WHERE Industry = :industry';
List<Account> results = Database.query(query, AccessLevel.USER_MODE);
Database.query()では常にAccessLevel.USER_MODEを使用します:bindVariable構文を使用し、文字列連結は決して使用しません- フィールド名が本当に動的な場合:
String.escapeSingleQuotes()
ユーティリティ関数
toLabel(PicklistField)— 翻訳済みの選択リストラベルを返しますFORMAT(NumberField)— ロケールに応じて数値/日付をフォーマットしますconvertCurrency(Amount)— ユーザーの通貨に換算します(複数通貨対応組織)
レコードロック
SELECT Id, Name FROM Account WHERE Id = :accountId FOR UPDATE
悲観的ロックにより、コミットまたはロールバックされるまで、ほかのトランザクションによる更新がブロックされます。
ALL ROWS(削除済みレコードを含む)
SELECT Id, Name FROM Account WHERE IsDeleted = true ALL ROWS
論理削除されたレコードを返します(ごみ箱に 15 日間保持されます)。
SOQL For ループ
for (List<Account> batch : [SELECT Id, Name FROM Account]) {
// Processes 200 records per iteration automatically
// Uses minimal heap — ideal for large datasets
}
地理位置情報クエリ
SELECT Id, Name, DISTANCE(Location__c, GEOLOCATION(37.7749, -122.4194), 'mi') dist
FROM Store__c
WHERE DISTANCE(Location__c, GEOLOCATION(37.7749, -122.4194), 'mi') < 50
ORDER BY DISTANCE(Location__c, GEOLOCATION(37.7749, -122.4194), 'mi')
WITH SECURITY_ENFORCED と WITH USER_MODE の比較
| 機能 | SECURITY_ENFORCED | USER_MODE |
|---|---|---|
| FLS の適用 | SELECT/FROM のみ | SELECT/FROM/WHERE/サブクエリ |
| 違反時の動作 | 例外をスロー | アクセスできないフィールドを黙って除外 |
| 制限ルール | 非対応 | 対応 |
| 推奨 | レガシー — 移行してください | 推奨 |
最適化ルール
選択性の高いフィルター(インデックス付きフィールドを使用)
Id、Name、OwnerId、CreatedDate、SystemModstampRecordTypeId、Lookupフィールド、External IDフィールドExternal IDとしてマークされたカスタムフィールド、またはカスタムインデックス付きフィールド
検出するアンチパターン
- ループ内のクエリ — クエリをループの前に移動し、Map/Set を使用します
- 選択性の低いフィルター — まずインデックス付きフィールドで絞り込みます
- SELECT * 相当 — すべてのフィールドではなく、必要なフィールドのみを選択します
- LIMIT の不足 — 大量のデータセットを返す可能性のあるクエリには LIMIT を追加します
- 否定的なフィルター —
!=とNOT INは選択性が低くなります - 先頭のワイルドカード —
LIKE '%term'ではインデックスを使用できません - WHERE 句の不足 — 意図的にすべてを読み込む場合を除き、必ず絞り込みます
クエリプランの分析
sf data query -q "EXPLAIN SELECT Id FROM Account WHERE Name = 'Test'" --target-org myOrg
制限
- 同期トランザクションあたり 100 件の SOQL クエリ
- 非同期トランザクションあたり 200 件の SOQL クエリ
- トランザクションあたり 50,000 件の返却行
- サブクエリあたり 2,000 件の行
- 親クエリあたり 20 件のリレーションクエリ
注意点
FIELDS(ALL)にはLIMIT 200が必須です。指定しないと失敗しますCOUNT()は null を含むすべての行をカウントします。COUNT(fieldName)は null 以外のみをカウントしますFOR UPDATEは行全体をロックします。他のトランザクションは待機するか、タイムアウトします- SOSL には独自のガバナー制限があり、1 トランザクションあたり 20 クエリです(SOQL の 100 とは別です)
- 日付リテラルには境界日が含まれます。
LAST_N_DAYS:7には今日が含まれます TYPEOFは Task.What、Event.Who などの多態性フィールドでのみ機能します- サブクエリの結果上限は 2,000 行であり、50,000 行ではありません
FIELDS(ALL)は Apex ではサポートされていません。REST API と Developer Console でのみ使用できますDatabase.query()は FIELDS() をサポートしていません。明示的なフィールドリストを使用してくださいALL ROWSはFOR UPDATEと併用できません
ワークフロー
- データ要件を把握します
- オブジェクトのリレーションとフィールドの型を確認します
- 適切なフィルター、セキュリティ、制限を設定してクエリを作成します
- 次のコマンドでテストします:
sf data query -q "YOUR_QUERY" --target-org myOrg - 結果と説明プランに基づいて最適化します
参考資料
- SOQL/SOSL リファレンス — 日付リテラル、SOSL 構文、FIELDS()、地理位置情報、動的 SOQL、集計、FOR UPDATE、バインドパターン、クエリプラン
- ガバナー制限 — トランザクションあたりの SOQL クエリ制限
コメント
Markdown 対応