Salesforce：3階層くらいの関連したレコードをスクラッチ組織へインポートする方法


# 事の発端
sfdx force:data:tree:importのお話です。
普通は画面でデータ作ってdata:tree:exportしてそれをimportに渡したらいいんだけど、
<font color="Red" >
***データの関連が３階層まで及ぶと厄介である事に気づいてしまった・・・***
</font>

スクラッチ組織の環境構築で必要なテストデータ作るときに、困ったこと。
なんというか、SOQLが３階層の連携だとエラーになるの。。。

エラー例）

```
SELECT Id
     , mynamespace__AAA__c
     , mynamespace__BBB__c
     , mynamespace__CCC__c
     ,(
       SELECT Id
            , mynamespace__DDD__c
            , mynamespace__EEE__c
            , mynamespace__FFF__c
            , (
               SELECT Id
                    , mynamespace__GGG__c 
               FROM mynamespace__CObjects__r
               )
       FROM mynamespace__BObjects__r
       )
FROM mynamespace__AObject__c
```

<font color="Red">SOQL statements cannot query aggregate relationships more than 1 level away from the root entity object.
</font>
エラーメッセージ内容は書いている通り。

改めて、SOQLには「親-子リレーションの 1 つのレベルだけが 1 つのクエリ内で指定可能」という制約があった。

https://developer.salesforce.com/docs/atlas.ja-jp.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_relationships_query_limits.htm


純粋にApexクラスの中で処理を実装するだけならば、2回に分けて実装すれば解決する。

しかし、

『　スクラッチ組織上でテストデータを作った！
　　そのテストデータ環境をメンバー間で使いまわしたい！　』

という場合は、ちょっと面倒であることに気づいた。

## ちょっとSOQLを知らない人向けに補足


<details><summary>ちょっとSOQLを知らない人向けに補足（不要な人はスキップ）</summary><div>
<font color="Gray">
SalesforceではSQLとは言わず、SOQLと言います。

SOQLは、Salesforce Object Query Language の略で、
Salesforce独特のデータ問い合わせコマンドです。


**SQLに似ているけどSQLではないことがポイント**
取得した結果は、ただの配列という形ではなく、`JSON`形式のようにも取得できるし、`Map<Id,Record>`形式のように取得することも、`List`形式で取得する事のも可能。

なお、今回の事例は以下のように実装すると動きます。

```
SELECT Id
     , mynamespace__AAA__c
     , mynamespace__BBB__c
     , mynamespace__CCC__c
     ,(
       SELECT Id
            , mynamespace__DDD__c
            , mynamespace__EEE__c
            , mynamespace__FFF__c
       FROM mynamespace__BObjects__r
       )
FROM mynamespace__AObject__c
```
上記のSOQLを実行した結果は、以下のようになります。

```
mynamespace__AObject__c: [ 
  { Id: 'xxx'
     , mynamespace__AAA__c: 'xxx'
     , mynamespace__BBB__c: 'xxx'
     , mynamespace__CCC__c: 'xxx'
     , mynamespace__BObjects__r: [
         {  Id: 'bbb'
           , mynamespace__DDD__c: 'bbb'
           , mynamespace__EEE__c: 'bbb'
           , mynamespace__FFF__c: 'bbb'
         },
         {...略},
         {...略},
         {...略} ]
  },
  {...略},
  {...略},
  {...略} ]
```
SOQLは便利な反面、厳密なクエリ制限があるのです。
簡単な補足終了！</font>
</div></details>




# では、３階層のデータを入れる為には何をするのか。
ヒントは以下の公式サイトの一番したに記載があります。
https://developer.salesforce.com/docs/atlas.ja-jp.sfdx_dev.meta/sfdx_dev/sfdx_dev_test_data_example.htm
>もっと複雑な例をお探しですか? 　[easy-spaces-lwc](https://github.com/trailheadapps/easy-spaces-lwc/blob/develop/data/Plan2.json) 　サンプルアプリケーションには、アカウント、関連取引先責任者、および 3 レベルの深度のカスタムオブジェクトチェーンをインポートする方法を示すデータプランがあります。

そう！
これだけしか記載がないのである！！！
なんて、クレイジー！

## じゃあ [easy-spaces-lwc](https://github.com/trailheadapps/easy-spaces-lwc/blob/develop/data/Plan2.json)　で分かること

紐解いていくと、以下のような構成になっている。
![easy-spaces-lwcのデータ階層について (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/504068/a068062e-d052-2a89-471d-db634103b316.png)


私の求めている情報が正に、`Reservation__c`の存在だ。


さて、この指定されたリンク先の Plan2.jsonというファイルは、プラン実行ファイルである。

```easy-spaces-lwc/data/Plan2.json
[
    {
        "sobject": "Account",
        "saveRefs": true,
        "resolveRefs": false,
        "files": ["Accounts.json"]
    },
    {
        "sobject": "Contact",
        "saveRefs": true,
        "resolveRefs": true,
        "files": ["Contacts.json"]
    },
    {
        "sobject": "Market__c",
        "saveRefs": true,
        "resolveRefs": false,
        "files": ["Market__cs.json"]
    },
    {
        "sobject": "Space__c",
        "saveRefs": false,
        "resolveRefs": true,
        "files": ["Space__cs.json"]
    },
    {
        "sobject": "Reservation__c",
        "saveRefs": false,
        "resolveRefs": true,
        "files": ["Reservation__cs.json"]
    }
]
```

中身を読み解くと、以下の属性を持っている。

- `sobject`
- `saveRefs`
- `resolveRefs`
- `files`

`sobject`は、Salesforce上のオブジェクトマネージャーにも表示されたオブジェクトを表すキーワードであることがわかる。
`files`は、実際のテストデータを記載したファイルが含まれている。

```easy-spaces-lwc/data/Accounts.jsonの一部抜粋
        {
            "attributes": {
                "type": "Account",
                "referenceId": "AccountRef1"
            },
            "Name": "Thompson, Skidmore and Smith"
        },
```
```easy-spaces-lwc/data/Contacts.jsonの一部抜粋
        {
            "attributes": {
                "type": "Contact",
                "referenceId": "ContactRef1"
            },
            "Email": "phaidra.rayer@sw-global.com",
            "FirstName": "Phaidra",
            "LastName": "Rayer",
            "MailingCity": "San Bruno",
            "MailingCountry": "US",
            "MailingPostalCode": "94066",
            "MailingState": "CA",
            "MailingStreet": "87 Northland Place",
            "MobilePhone": "+1 (408) 505-1578",
            "AccountId": "@AccountRef1",
            "Reservation_Status__c": "Draft"
        },
```

```easy-spaces-lwc/data/Reservation__cs.jsonの一部抜粋
         {
            "attributes": {
                "type": "Reservation__c",
                "referenceId": "Reservation__cRef1"
            },
            "Contact__c": "@ContactRef1",
            "Market__c": "@Market__cRef6",
            "Start_Date__c": "2018-08-30",
            "End_Date__c": "2018-09-30",
            "Status__c": "Draft",
            "Total_Number_of_Guests__c": 14
        },
```
キーワードは`referenceId`である。そして、`referenceId`で指定した値を`@で始まる文字列`で指定しているところに注目

サンプルでは、
`Account`オブジェクトの`"referenceId": "AccountRef1"`を、
`Contact`オブジェクトの`"AccountId": "@AccountRef1"`という形式で参照している。
<img width="1065" alt="スクリーンショット 2019-09-27 16.51.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/504068/75052087-0e57-5109-a989-1b35f8d7511f.png">

また、`Contact`オブジェクトの`"referenceId": "ContactRef1"`を、
`Reservation__c`オブジェクトの`"Contact__c": "@ContactRef1"`という形式で参照している。
<img width="1125" alt="スクリーンショット 2019-09-27 16.52.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/504068/9a09cdeb-0dce-0e17-eb67-28766bd5e353.png">

改めて、３階層くらい関連したオブジェクトをスクラッチ組織にインポートするためには、このような関連情報を記載したファイルを作成すれば良いのである！！！

あとは、以下のようなSFDXコマンドの実行でOK！（以下は指定のサンプルに記載がありました）
>7.Load sample data:
>`sfdx force:data:tree:import -p ./data/Plan2.json`


### でも、じゃあ saveRef と resolveRef って何？
上記のプラン実行ファイルを眺めていると、`saveRef`と`resolveRef`ってキーワードが出てくる。これ何？って思ったから調べたことを以下に整理しました。
なお整理に当たっては、以下やりとりを参考にしております。
https://salesforce.stackexchange.com/questions/212034/what-are-saverefs-and-resolverefs?noredirect=1&lq=1

#### saveRef
　`referenceId`を保存するかどうかのフラグです。
　【具体例】
　基本的に、`sfdx force:data:tree:import -p ./data/Plan2.json`を実行したとき、
　テストデータの値に沿ってスクラッチ組織にデータを登録する。
　その際、登録することで正しいID（`0015000000abcde`）が発行される。
　　※内部では`"Id": "0015000000abcde"`が登録されている。
　このときスクラッチ組織上にて`referenceId`の情報を残すかどうかである。
　■"saveRefs": falseの場合
　　　　`"referenceId": "AccountRef1"`を削除する。
　　　　※ただし、後続のファイルで、ルックアップフィールドが`@で始まる値`（例`@AccountRef1`）を使用しようとしてもエラーとなります。
　■"saveRefs": trueの場合
　　　　`"referenceId": "AccountRef1"`を削除しない。
　　　　`"AccountRef1"=0015000000abcde`という情報が保存される。

#### resolveRef
　後続のファイルで、ルックアップフィールドが@で始まる値（例`@AccountRef1`）を使用したとき、以前に保存した参照を解決するかどうかのフラグです。
　【具体例】
　■"resolveRef": falseの場合
　　　　`@で始まる値`を参照情報として利用しない。入力値として扱う。
　　　　`"AccountId": "@AccountRef1"`という情報は、そのまま文字列として指定する。
　　　　※ただし、もしこれが参照制約のあるフィールドの場合、エラーになります。
　■"resolveRef": trueの場合
　　　　`@で始まる値`を参照情報として利用する。
　　　　`"AccountId": "@AccountRef1"`という情報は
　　　　`"AccountRef1"=0015000000abcde`という情報として取り扱う。

### 判断基準
改めておさらい。
<img width="977" alt="スクリーンショット 2019-09-27 17.34.23.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/504068/9f287ef6-f155-616f-7548-bee616ee22c7.png">


# まとめ
3階層くらいの関連したレコードをスクラッチ組織へインポートする方法は、
以下の3つを意識した テストデータJSONファイル と、 プラン実行ファイル を`import`すれば良い！

 - `referenceId`
    - 重複しないユニークなID文字列を任意に定義してあげる。
    - 別から参照するときは、@を接頭辞に付けてあげる。
 - `saveRefs`
    - 自身オブジェクトのIDが別オブジェクトから参照されるときTrueと指定が必要
 - `resolveRef`
    - @を使用して、別オブジェクトのIDを参照しているときTrueと指定が必要）

以上です。ではでは！