Bulk APIで参照項目を含むＣＳＶファイルをUpsertする方法

# はじめに
最近では外部データを取り込む際に、データ連携ツールのデータローダーやＥＴＬを利用するプロジェクトが多く、ツールが提供している標準の補助機能によってBulk APIの考慮事項を意識せず実装できます。
手組みで実装する場合、具体的に主従や参照項目が設定されている親子関係のオブジェクトで、親の外部ＩＤを指定して子レコードを登録する項目の対応付けなど、明確に定義せず設計時に見落とされがちだと感じます。
ここでは、BulkAPIでSFIDを使わずに親の外部ＩＤでこのレコードを登録する方法を説明します。

# 概要
API開発ツールのPostmanを使用してBulk APIにアクセスします。BulkAPIの処理の流れは、ジョブ登録、データ項目の対応付け登録、バッチ実行の順に実行します。今回はジョブの終了を割愛し、サードパーティーのデータを想定してデータ項目の対応付けの登録を行います。

# 目次
1. オブジェクト構成
2. 登録データ
3. ジョブの登録
4. データ項目の対応付けの登録
5. バッチの登録
6. 結果の確認
7. 考慮事項
8. まとめ

# 1. オブジェクト構成
契約に取引先名が参照項目として設定されており、取引先と契約は親子関係になっている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76270/e6cbd699-7402-eeb1-23c5-82d28aa87302.png)


# 2. 登録データ(CSV)の説明
　サードパーティーのデータからSalesforceのリレーションを設定する場合、SFIDではなくサードパーティーのキー項目として外部ＩＤで登録するのが一番簡単な方法です。
　今回利用する登録データ（CSV)のヘッダー項目のAccountIDには、親オブジェクトの取引先の外部ＩＤの契約番号Ａ（ContractA__c）の値が設定されています。※AccountIdはCSVのヘッダー項目であり、Salesforceとは関係がない項目です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76270/74b92abf-9709-a778-a366-68532a17ee28.png)

### リレーションの説明
各参照項目毎に項目作成時に親オブジェクトのリレーションが作成されます。リレーションを識別する最も確実な方法は、describeSObjects() コールを実行することですが、Enterprise WSDLやAJAX Toolkitでも確認することができます。
今回、標準の取引先オブジェクトと契約オブジェクトを利用し、契約オブジェクトの取引先名（AccountId）は標準項目で、取引先オブジェクトへの親リレーションは”Account”が利用できます。その他、カスタムの参照項目の場合、”__r”のサフィックスが付きます。

# 3. ジョブの登録
BulkAPIで、データを処理する場合ジョブの作成、項目の対応付け（任意）、バッチの登録、ジョブの終了の処理を順次実行します。まず最初に、ジョブの作成を行います。
以下のエンドポイントを利用します。
```https://instance_name—api.salesforce.com/services/async/APIversion/job```
[ジョブの作成:Salesforcehelp](https://developer.salesforce.com/docs/atlas.ja-jp.api_asynch.meta/api_asynch/asynch_api_jobs_create.htm)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76270/bfb19bf2-4cec-fc4e-2021-82171a8e415d.png)
ジョブの内容は、下記の４つを記載します。
1. JSON形式でレコード操作（insert|update|delete|upsert）
2. 登録オブジェクト
3. レコードのキー項目（外部ＩＤ）（※upsert時にのみ）
4. 登録データ形式
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76270/9c95a3a7-6e4e-11d2-2d45-6cce11773c20.png)
正常に登録されると、下図のレスポンスが返ってきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76270/3583ee77-e8c4-686d-09ca-e00c105ec07d.png)
Salesforceからも確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76270/15141c48-cec0-b0cc-5b1a-e9717f49689a.png)

# 4. 項目の対応付けの登録
サードパーティのデータを利用する場合、登録データのヘッダー行はSalesforceの項目名で設定されてない場合、Salesforceの項目と登録データのヘッダー項目を紐づけるために、項目の対応付けを登録します。
```https://instance_name—api.salesforce.com/services/async/APIversion/job/jobid/spec
```
[項目の対応付け：SalesforceHelp]()
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76270/bfea64a9-afb3-3bac-9372-13b461b21659.png)
CSV形式で項目の対応付けの登録します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76270/7b632e15-8b3c-2b7d-48af-2f77f8ebe031.png)

```
Salesforce Field,Csv Header
ContractNum__c,ContractNum__c
Name,Name
Account.ContractB__c,AccountId
```
正常処理のレスポンスはありませんが登録できています。２回登録するとエラーが返却されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76270/2e102341-85e3-6022-c7c2-b0d2c02c8c6f.png)

# 5. バッチの登録
実際に登録データを送信します。
今回は物理ファイルではなく、ボディーにＣＳＶのテキストを張り付けて送信します。
```https://instance_name—api.salesforce.com/services/async/APIversion/job/jobid/batch```
[ジョブへのバッチの追加:SalesforceHelp](https://developer.salesforce.com/docs/atlas.ja-jp.api_asynch.meta/api_asynch/asynch_api_batches_create.htm)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76270/2d58f780-93b8-bdb8-63e1-cc4ff354edd6.png)
メッセージボディーにＣＳＶ形式のデータを貼り付けます。
データは、親の取引先の外部IDがあるパーターンとないパターンで作成しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76270/dd7308e4-f358-d3d2-2b29-156c44fe50f3.png)
正常に登録されると下図のレスポンスが返却されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76270/9abdbaf8-a9c3-1de4-8e8c-c8a61693b3c3.png)

# 6. 結果の確認
登録データからSalesforceでBulk実行される際の、要求と結果を確認できます。
Salesforceの[設定]画面 > クイック検索[一括データ読み込みジョブ] > 対象の[ジョブID]をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76270/86f0de45-1754-b0f9-24a8-c0bb70b72607.png)
登録されたデータがジョブ実行される際の要求と結果を確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76270/1474e409-ee6b-abe4-0c4c-aae04238dec2.png)
[要求]リンクをクリックすると、CSV形式のバッチ登録したデータが表示され、項目の対応づけで登録したマッピングデータでヘッダー部分が置換されていることが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76270/7e83b76c-87a8-1c98-5786-0597a1cef344.png)
[結果]リンクをクリックすると、CSVデータがレコードごとに登録の状態を確認できます。先ほど登録したCSVデータの２レコードは親の取引先の外部IDが存在するパターンと、しないパターンでしないパターンは、外部IDが見つからなかったとエラーでそのレコードが登録できなかったことが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76270/032c9c61-8ddc-3d1b-92dc-252b369820c1.png)
画面からも確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76270/44ad48b4-4774-69d9-dddd-8609f3412f56.png)

# 7. 考慮事項
レコードの登録・更新用の外部ＩＤと、リレーション設定用の外部ＩＤのCSV項目が同じものの場合、エラーが発生します。おそらく、項目の対応付けの影響で登録データ（CSV）のヘッダーがBulk実行時の要求で置き換わるのが影響していると考えています。

# 8. まとめ
いかがだったでしょうか？
外部データの連携では、データローダーやETLを利用することが多いため朧げに処理をイメージされている方がおおいいと思います。
BulkAPIでリレーションを登録するイメージを持てたことで設計時に考慮すべきポイントも明確になったと思います。
では、

# 参考資料
1. [Postmanホームページ]
(https://www.getpostman.com/)
2. [親子リレーションの識別:Salesforce Help](
https://developer.salesforce.com/docs/atlas.ja-jp.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_relationships_parent_child.htm#sforce_api_calls_soql_relationships_parent_child)
3. [データ項目の対応付け:Salesforce Help](https://developer.salesforce.com/docs/atlas.ja-jp.api_asynch.meta/api_asynch/asynch_api_mapping.htm?search_text=%E5%AF%BE%E5%BF%9C%E4%BB%98%E3%81%91)
4. [ヘッダー行のリレーション項目:Salesforce Help](https://developer.salesforce.com/docs/atlas.ja-jp.api_asynch.meta/api_asynch/datafiles_csv_rel_field_header_row.htm?search_text=%E3%83%AA%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3)