はじめに
Salesforceで開発をしていると、「多対多」のリレーションを扱う場面によく出くわします。例えば、「1つの注文には複数の商品が含まれ、1つの商品は複数の注文に含まれる」といった関係です。この場合は注文と商品は多対多の関係になります。
このようなリレーションを持つデータをSOQLで取得しようとした際、どのようにクエリを組み立てれば良いか迷ったことはありませんか?
この記事では、Salesforceにおける多対多リレーションの基本から、標準オブジェクトおよびカスタムオブジェクトを例にした具体的なSOQLの組み立て方、そして取得したデータの活用方法までを解説します。
1. 多対多リレーションとは
多対多リレーションとは、データベース設計におけるリレーションシップの一種で、一方のテーブルのレコードが、もう一方のテーブルの複数のレコードに対応し、逆もまた同様である関係を指します。
例えば、「学生」と「講座」の関係を考えてみましょう。
1人の学生は複数の講座を受講できる
1つの講座には複数の学生が所属している
このような関係が「多対多」です。これをそのまま2つのテーブルで表現することは難しいため、通常は中間オブジェクト(連結オブジェクト) と呼ばれる3つ目のオブジェクトを介して関係を表現します。
2. Salesforceにおける多対多の表現方法
Salesforceでは、この多対多リレーションを「連結オブジェクト (Junction Object)」を用いて実現します。
連結オブジェクトは、2つのオブジェクト(親オブジェクト)の間に立ち、それぞれへの主従関係 or 参照関係を持つカスタムオブジェクトです。これにより、2つの親オブジェクト間の多対多リレーションを擬似的に表現します。
オブジェクトA <--主従(参照)関係-- 連結オブジェクト --主従(参照)関係--> オブジェクトB
3. 標準オブジェクトで見る多対多の関係
それでは、具体的な例としてSalesforceの標準オブジェクトである「注文 (Order)」と「商品 (Product2)」の関係を見ていきましょう。
1つの注文には、複数の商品を含めることができる
1つの商品は、複数の注文に含めることができる
この関係を繋ぐ連結オブジェクトが「注文商品 (OrderItem)」です。
Order <--参照関係-- OrderItem --参照関係--> Product2
実際に連結オブジェクトであるOrderItemの設定を見ると、OrderオブジェクトにはorderIdで、Product2オブジェクトにはProduct2Idという外部キーが設定されており、それぞれ参照関係が結ばれていることがわかります。
※ このケースのように連結オブジェクト自体が明細のように意味を持つこともあれば、単純に多対多の関係を結ぶためだけに連結オブジェクトを作ることもあります。今回は標準オブジェクトで説明をするためにOrderItemを連結オブジェクトとして扱っていますが、OrderItemは連結オブジェクトかつそれ自体に意味を持つオブジェクトです。
3-1. 取得クエリの構築方法
Orderを主軸として、そこに含まれるProduct2の情報を取得するSOQLを組み立ててみましょう。次の関係を1つのクエリで表す必要があります。
Order : OrderItem = 親 : 子
OrderItem : Product2 = 子 : 親
Order : OrderItem = 親 : 子 を取得するクエリ
SELECT
Id, -- Order.Idを表す
(
SELECT Id -- 子のOrderItemのId を表す
FROM OrderItems
)
FROM
ORDER
-- ↓ 1行バージョン
SELECT ID,(SELECT ID FROM OrderItems) FROM ORDER
OrderItem : Product2 = 子 : 親 を取得するクエリ
SELECT
Id, -- OrderItem.Idを表す
Product2.Id -- 親のProduct2のIdを表す
FROM
OrderItem
-- ↓ 1行バージョン
SELECT ID, Product2.Id FROM OrderItem
この2つをくっつけることで、多対多の関係を取得するクエリとなります。
SELECT
Id, -- 多対多の元となるOrder.Idを表す
(
SELECT
Product2.Id -- 多対多の先となるProduct2のIdを表す
FROM
OrderItems -- 連結オブジェクトとなるOrderItemsのリレーション名を指定
)
FROM
Order
-- ↓ 1行バージョン
SELECT Id, (SELECT Product2.Id FROM OrderItems) FROM Order
これで多対多の関係を作るベースとなるSOQLが完成しました。
あとはここにSELECTするフィールドを追加したり、WHERE句を追加していくだけです。
3-2. 取得結果の使い方
ここまでは色々なサイトで説明されていますが、その際の利用方法まではあまり解説されていないのでこの記事で深掘りしていきましょう。
少しクエリに条件を追加してみましょう。
Apexで実行すると、List 型で結果が返ってきます。
取得したデータは、二重ループで処理するのが一般的です。
次のコードは「注文に含まれる商品情報をログに表示する」というプログラムです。
// SOQLクエリを実行
List<Order> ordersWithItems = [
SELECT Id, Name, (SELECT Product2.Name, Product2.ProductCode FROM OrderItems WHERE Product2.IsActive = true)
FROM Order
WHERE Id IN :orderIds
];
// 取得結果をループで処理
for (Order order : ordersWithItems) {
System.debug('注文名: ' + order.Name);
// ネストされたサブクエリの結果は、子リレーション名のプロパティに格納されている
if (ord.OrderItems != null) {
for (OrderItem orderItem : order.OrderItems) {
System.debug(' - 商品名: ' + orderItem.Product2.Name + ', 商品コード: ' + orderItem.Product2.ProductCode);
}
}
}
各Orderオブジェクトの OrderItems プロパティに、サブクエリで取得したOrderItemのリストが格納されています。
これにより、データベースへのクエリ回数を1回に抑えつつ、効率的に関連データを扱うことができます。
4. カスタムオブジェクトで見る多対多の関係
次に、カスタムオブジェクトで多対多リレーションを構築した場合を見てみましょう。
例として、「受講生 (Student__c)」と「講座 (Course__c)」オブジェクト、そしてそれらを繋ぐ連結オブジェクト「履修 (Enrollment__c)」を作成したとします。
1人の受講生は、複数の講座を履修できる
1つの講座には、複数の受講生が所属する
この関係は以下のようになります。
Student__c <--主従関係-- Enrollment__c --主従関係--> Course__c
4-1. 取得クエリの構築方法
「講座」を主軸に、その講座を履修している「受講生」の一覧を取得してみましょう。
ここでも親子リレーションクエリを使います。
SELECT
Name, -- 講座名
(SELECT Student__r.Name, Student__r.Email__c -- 履修(Enrollment__c)から受講生(Student__c)の項目を辿る
FROM Enrollments__r -- Courseから見たEnrollmentの子リレーション名
)
FROM Course__c
このクエリは、「全ての講座について、その講座を履修している受講生の氏名とメールアドレスを取得する」という意味になります。
補足: リレーション名はどこで確認・設定する?
SOQLでリレーションを扱う上で重要な「子リレーション名」と、逆引きに使う「親リレーション名」の確認方法を解説します。
・ 親から子を取得する「子リレーション名」
親オブジェクト (Course__c) から子のリスト (Enrollments__r) を取得する際に使うこの名前は、子オブジェクト側で設定します。
確認手順:
[設定] > [オブジェクトマネージャ] に移動します。
子オブジェクトである 履修 (Enrollment__c) を選択します。
[項目とリレーション] をクリックします。
親オブジェクトである 講座(Course__c) への主従関係項目をクリックします。
項目定義の詳細ページにある [子リレーション名] を確認します。ここに設定されている値(例: Enrollments)に __r を付けたものが、SOQLで使うリレーション名です。
・ 子から親を取得する「親リレーション名」
逆に、子 (Enrollment__c) から親 (Course__c) の情報を取得する場合、子オブジェクトに作成した参照項目のAPI参照名 を使います。
例えば、Enrollment__c
に Course__c
というAPI参照名で講座への参照項目がある場合、親リレーション名は Course__r
となります(末尾の __c を __r に置き換えます)。
4-2. 取得結果の使い方
標準オブジェクトの時と同様に、Apexで結果を処理できます。
List<Course__c> coursesWithStudents = [
SELECT Name, (SELECT Student__r.Name, Student__r.Email__c FROM Enrollments__r)
FROM Course__c
];
for (Course__c course : coursesWithStudents) {
System.debug('講座名: ' + course.Name);
// 子リレーション名 `Enrollments__r` のプロパティに結果が格納されている
if (course.Enrollments__r != null) {
for (Enrollment__c en : course.Enrollments__r) {
System.debug(' - 受講生: ' + en.Student__r.Name + ', Email: ' + en.Student__r.Email__c);
}
}
}
カスタムオブジェクトの場合も、__r が付いたリレーション名のプロパティにサブクエリの結果が格納されます。
まとめ
Salesforceにおける多対多リレーションのデータは、連結オブジェクトを介して親子リレーションクエリ(サブクエリ)を使うことで、1回のSOQLで効率的に取得できます。
基本は親オブジェクトから子のリレーション名を指定したサブクエリを発行する
標準オブジェクトのリレーション名(OrderItemsなど)とカスタムオブジェクトのリレーション名(Enrollments__rなど)の違いを意識する
取得結果は親オブジェクトのリストとして返り、子のデータはプロパティにネストされている
この方法をマスターすれば、ガバナ制限を回避しやすく、かつ可読性の高いコードを書くことができます。ぜひ活用してみてください。