親子のレコードをSOQL1回でまとめて取得する書き方
親のレコードを取ってから子をループで引き直すと、件数が増えた日に止まります。親子は1回のSOQLで取れます。ドット記法とサブクエリの使い分けと、リレーション名でつまずく場所を書きます。
なぜ1回にまとめるのか
取引先を取ってから、1件ずつ取引先責任者を引き直す。動きはしますが、これはループの中にSOQLを置く形です。Apexのガバナ制限は1トランザクションあたりのSOQL発行回数を数えるので、親の件数が増えた日に、ある日突然止まります。
SOQLは親子をまたいで1回で取れます。回数が1回で済むだけでなく、コードも短くなります。
向きが2つある
| 向き | 書き方 | 例 |
|---|---|---|
| 子 → 親 | ドット記法 | 取引先責任者から、その取引先の名前を取る |
| 親 → 子 | サブクエリ | 取引先から、ぶら下がる取引先責任者を取る |
この2つは書き方がまったく違います。同じ「参照関係」でも、たどる向きで形が変わります。
子から親をたどる
参照項目にドットをつなげます。取引先責任者から取引先を見るなら、次のようになります。
SELECT Id, Name, Account.Id, Account.Name FROM Contact WHERE Account.Industry = 'Banking'
開発者コンソールのクエリエディタでそのまま実行できます。読み取りだけです。
ドット記法は WHERE でも使えます。上の例は、取引先の業種で取引先責任者を絞り込んでいます。親の項目で子を絞れるのが、この向きの強みです。
カスタムオブジェクトのときは、参照項目の API 参照名の __c を __r に置き換えます。
SELECT Id, Name, Parent__r.Id, Parent__r.Name FROM ChildObject__c
カスタムの参照項目 Parent__c をたどるときは Parent__r と書きます。
公式リファレンスは「リレーション名を使うときは __c を外し、代わりに __r を付ける」と明記しています。
親から子をたどる
SELECT の中に、丸カッコでサブクエリを置きます。
SELECT Id, Name, (SELECT Id, LastName FROM Contacts) FROM Account WHERE Industry = 'Banking'
取引先と、その取引先にぶら下がる取引先責任者を1回で取ります。
丸カッコの中の FROM に書くのは、オブジェクト名ではなくリレーション名です。ここが最初のつまずき所です。
Contacts、商談なら Opportunities。カスタムオブジェクトは、参照項目を作ったときに決めた「子リレーション名」に __r を付けた形になります。既定では複数形にはなりません。カスタムオブジェクトの例です。
SELECT Id, Name, (SELECT Id, Name FROM Childs__r) FROM ParentObject__c
子リレーション名が Childs のとき、サブクエリでは Childs__r と書きます。
リレーション名を確かめる場所
推測で書くと動きません。画面で確かめられます。
設定からオブジェクトマネージャを開く
子オブジェクト(参照項目を持っているほう)を選びます。
項目とリレーションを開く
親をさしている参照項目をクリックします。
子リレーション名を見る
「子リレーション名」に書かれている値が、サブクエリで使う名前です。これに
__rを付けます。
⚠️ 子リレーション名は、項目を作った人が決めた文字列です。組織によって違います。他社の記事に出てくる名前をそのまま使っても通りません。
Apexで受け取る
サブクエリの結果は、親のレコードの中にリストとして入っています。
List<Account> accounts = [ SELECT Id, Name, (SELECT Id, LastName FROM Contacts) FROM Account WHERE Industry = 'Banking' ]; for (Account a : accounts) { for (Contact c : a.Contacts) { System.debug(a.Name + ' / ' + c.LastName); } }
親を1回取るだけで、子も一緒に手に入ります。ループの中にSOQLはありません。
子のリストは、サブクエリで取ったときだけ入ります。サブクエリを書かずに a.Contacts を触ると、実行時に例外になります。
どこまでたどれるか
公式リファレンスに上限が書かれています。数が多いように見えますが、複雑なクエリを書くと意外に当たります。
| 向き | 1つのクエリで使える数 | たどれる階層 |
|---|---|---|
| 子 → 親 | 55リレーションまで | 5階層まで |
| 親 → 子 | 20リレーションまで | API 58.0 以降は5階層まで |
⚠️ 親から子の階層は、APIバージョンで変わります。API 57.0 以前は2階層までで、5階層になったのは 58.0 からです。しかも5階層が使えるのは REST・SOAP・Apex で、ビッグオブジェクト・外部オブジェクト・Bulk API は対象外です。古い apiVersion のまま置いてあるクラスでは、2階層で止まります。
ここで間違えやすい
| 間違い | 何が起きるか |
|---|---|
サブクエリの FROM にオブジェクト名を書く | コンパイルが通りません。リレーション名を書きます |
カスタムの参照で __c のまま書く | 同じく通りません。__r に置き換えます |
| 子リレーション名を他組織の記事から写す | 名前は組織ごとに違うので通りません |
| サブクエリを書かずに子のリストを触る | 実行時に例外になります |
古い apiVersion のまま3階層以上のサブクエリを書く | 57.0 以前では通りません |
確認した環境
- 2026年9月 / Salesforce Summer '26(APIバージョン 67.0)時点の公式リファレンスで、上限とリレーション名の決まりを確認しています
まとめ
- 親子は1回のSOQLで取れます。ループの中でSOQLを発行しないでください
- 子から親はドット記法、親から子はサブクエリ。向きで書き方が変わります
- サブクエリの
FROMに書くのはリレーション名です。標準は子オブジェクトの複数形、カスタムは子リレーション名に__rを付けた形です - 子リレーション名は組織ごとに違います。オブジェクトマネージャの参照項目で確かめてください
- 上限は、子から親が55リレーション・5階層、親から子が20リレーション・5階層(API 58.0 以降)です
Salesforceの導入・運用についてご相談ください
導入前の検討から、お使いの環境の改修・運用、AIとの連携まで承ります。状況を伺ったうえで、進め方をご提案します。