ODataのクエリオプションについてメモ書き【$expand編】

# はじめに
ODataのクエリオプションの一つである$expandの操作方法について、確認した内容を備忘録として記載します。
確認は[postman](https://www.postman.com/)で行い、ODataは[SAP SuccessFactorsのsandbox](https://api.sap.com/themes/SAPSF?searchterm=&filter=(Type%20eq%20(API%20Package)))を使用しました。
参考：[URI Conventions (OData Version 2.0)](https://www.odata.org/documentation/odata-version-2-0/uri-conventions/)

## $expandとは
![図1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/486466/6282b24c-4364-25bb-74e8-35d639ecd8c1.png)
上図のようなカテゴリとその製品を特定したい場合、/カテゴリ(1)と/カテゴリ(1)/製品の２つのリクエストを実行することになります。これを1つのリクエストで取得可能にするのがクエリオプションの\$expandです。
この場合ですと、/カテゴリ(1)?$expand=製品でリクエストをするとカテゴリ(1)とその製品（A,B）まで取得することができます。

# 実際にODataを操作してみる

```url:今回使用するAPI
https://sandbox.api.sap.com/successfactors/odata/v2/PaymentInformationV3
```

項目が少なさそうなこちらのAPIを使用してみます。
試しに１件Json形式で取得してみましょう。

## ひとまずデータを取得してみる

```url:１件Json形式で取得
https://sandbox.api.sap.com/successfactors/odata/v2/PaymentInformationV3?$top=1&$format=json
```

```json:実行結果
{
    "d": {
        "results": [
            {
                "__metadata": {
                    "uri": "https://sandbox.api.sap.com:443/successfactors/odata/v2/PaymentInformationV3(effectiveStartDate=datetime'2015-01-21T00:00:00',worker='82096')",
                    "type": "SFOData.PaymentInformationV3"
                },
                "effectiveStartDate": "/Date(1421798400000)/",
                "worker": "82096",
                "effectiveEndDate": "/Date(253402214400000)/",
                "jobCountry": "USA",
                "lastModifiedDateTime": "/Date(1441808748000+0000)/",
                "createdDateTime": "/Date(1431719769000+0000)/",
                "mdfSystemVersionId": null,
                "mdfSystemStatus": "A",
                "mdfSystemEntityId": "EF2857313694441AAE32CC24F6689A16",
                "mdfSystemObjectType": "PaymentInformationV3",
                "lastModifiedDate": "/Date(1441794348000)/",
                "lastModifiedBy": "sfadmin",
                "lastModifiedDateWithTZ": "/Date(1441808748000+0000)/",
                "mdfSystemRecordStatus": "N",
                "mdfSystemTransactionSequence": "1",
                "createdDate": "/Date(1431705369000)/",
                "createdBy": "sfadmin",
                "mdfSystemRecordId": "1EAEAAA8B8A54C729908C7464650025C",
                "mdfSystemStatusNav": {
                    "__deferred": {
                        "uri": "https://sandbox.api.sap.com:443/successfactors/odata/v2/PaymentInformationV3(effectiveStartDate=datetime'2015-01-21T00:00:00',worker='82096')/mdfSystemStatusNav"
                    }
                },
                "toPaymentInformationDetailV3": {
                    "__deferred": {
                        "uri": "https://sandbox.api.sap.com:443/successfactors/odata/v2/PaymentInformationV3(effectiveStartDate=datetime'2015-01-21T00:00:00',worker='82096')/toPaymentInformationDetailV3"
                    }
                },
                "createdByNav": {
                    "__deferred": {
                        "uri": "https://sandbox.api.sap.com:443/successfactors/odata/v2/PaymentInformationV3(effectiveStartDate=datetime'2015-01-21T00:00:00',worker='82096')/createdByNav"
                    }
                },
                "jobCountryNav": {
                    "__deferred": {
                        "uri": "https://sandbox.api.sap.com:443/successfactors/odata/v2/PaymentInformationV3(effectiveStartDate=datetime'2015-01-21T00:00:00',worker='82096')/jobCountryNav"
                    }
                },
                "workerNav": {
                    "__deferred": {
                        "uri": "https://sandbox.api.sap.com:443/successfactors/odata/v2/PaymentInformationV3(effectiveStartDate=datetime'2015-01-21T00:00:00',worker='82096')/workerNav"
                    }
                },
                "lastModifiedByNav": {
                    "__deferred": {
                        "uri": "https://sandbox.api.sap.com:443/successfactors/odata/v2/PaymentInformationV3(effectiveStartDate=datetime'2015-01-21T00:00:00',worker='82096')/lastModifiedByNav"
                    }
                },
                "mdfSystemRecordStatusNav": {
                    "__deferred": {
                        "uri": "https://sandbox.api.sap.com:443/successfactors/odata/v2/PaymentInformationV3(effectiveStartDate=datetime'2015-01-21T00:00:00',worker='82096')/mdfSystemRecordStatusNav"
                    }
                },
                "wfRequestNav": {
                    "__deferred": {
                        "uri": "https://sandbox.api.sap.com:443/successfactors/odata/v2/PaymentInformationV3(effectiveStartDate=datetime'2015-01-21T00:00:00',worker='82096')/wfRequestNav"
                    }
                }
            }
        ]
    }
}
```

このようにデータが取得できました。
取得したデータの後半部分には"~Nav"となっている項目が複数あります。これらはPaymentInformationV3に関連するエントリです。
[$expandとは]内の図に当てはめると、PaymentInformationV3はカテゴリ、"~Nav"となっている各項目が製品となります。

## $expandを指定してみる
試しに$expandで"mdfSystemStatusNav"を指定してみましょう。

```url:$expandを指定して取得
https://sandbox.api.sap.com/successfactors/odata/v2/PaymentInformationV3?$top=1&$format=json&$expand=mdfSystemStatusNav
```

```json:実行結果
{
    "d": {
        "results": [
            {
                "__metadata": {
                    "uri": "https://sandbox.api.sap.com:443/successfactors/odata/v2/PaymentInformationV3(effectiveStartDate=datetime'2015-01-21T00:00:00',worker='82096')",
                    "type": "SFOData.PaymentInformationV3"
                },
                "effectiveStartDate": "/Date(1421798400000)/",
                "worker": "82096",
                "effectiveEndDate": "/Date(253402214400000)/",
                "jobCountry": "USA",
                "lastModifiedDateTime": "/Date(1441808748000+0000)/",
                "createdDateTime": "/Date(1431719769000+0000)/",
                "mdfSystemVersionId": null,
                "mdfSystemStatus": "A",
                "mdfSystemEntityId": "EF2857313694441AAE32CC24F6689A16",
                "mdfSystemObjectType": "PaymentInformationV3",
                "lastModifiedDate": "/Date(1441794348000)/",
                "lastModifiedBy": "sfadmin",
                "lastModifiedDateWithTZ": "/Date(1441808748000+0000)/",
                "mdfSystemRecordStatus": "N",
                "mdfSystemTransactionSequence": "1",
                "createdDate": "/Date(1431705369000)/",
                "createdBy": "sfadmin",
                "mdfSystemRecordId": "1EAEAAA8B8A54C729908C7464650025C",
                "mdfSystemStatusNav": {
                    "__metadata": {
                        "uri": "https://sandbox.api.sap.com:443/successfactors/odata/v2/MDFEnumValue(key='com.successfactors.genericobject.api.StatusEnum',value='A')",
                        "type": "SFOData.MDFEnumValue"
                    },
                    "value": "A",
                    "key": "com.successfactors.genericobject.api.StatusEnum",
                    "en_DEBUG": ":EMPFILE_HRIS_FOUNDATION_STATUS_ACTIVE:Active",
                    "ru_RU": "Активный",
                    "localized": "Active",
                    "pt_BR": "Ativo",
                    "fr_FR": "Actif",
                    "ja_JP": "有効",
                    "de_DE": "Aktiv",
                    "en_GB": "Active",
                    "zh_TW": "有效",
                    "ko_KR": "활성",
                    "en_US": "Active",
                    "es_ES": "Activo",
                    "zh_CN": "活动",
                    "nl_NL": "Actief",
                    "pt_PT": "Ativo"
                },
                "toPaymentInformationDetailV3": {
                    "__deferred": {
                        "uri": "https://sandbox.api.sap.com:443/successfactors/odata/v2/PaymentInformationV3(effectiveStartDate=datetime'2015-01-21T00:00:00',worker='82096')/toPaymentInformationDetailV3"
                    }
                },
                "createdByNav": {
                    "__deferred": {
                        "uri": "https://sandbox.api.sap.com:443/successfactors/odata/v2/PaymentInformationV3(effectiveStartDate=datetime'2015-01-21T00:00:00',worker='82096')/createdByNav"
                    }
                },
                "jobCountryNav": {
                    "__deferred": {
                        "uri": "https://sandbox.api.sap.com:443/successfactors/odata/v2/PaymentInformationV3(effectiveStartDate=datetime'2015-01-21T00:00:00',worker='82096')/jobCountryNav"
                    }
                },
                "workerNav": {
                    "__deferred": {
                        "uri": "https://sandbox.api.sap.com:443/successfactors/odata/v2/PaymentInformationV3(effectiveStartDate=datetime'2015-01-21T00:00:00',worker='82096')/workerNav"
                    }
                },
                "lastModifiedByNav": {
                    "__deferred": {
                        "uri": "https://sandbox.api.sap.com:443/successfactors/odata/v2/PaymentInformationV3(effectiveStartDate=datetime'2015-01-21T00:00:00',worker='82096')/lastModifiedByNav"
                    }
                },
                "mdfSystemRecordStatusNav": {
                    "__deferred": {
                        "uri": "https://sandbox.api.sap.com:443/successfactors/odata/v2/PaymentInformationV3(effectiveStartDate=datetime'2015-01-21T00:00:00',worker='82096')/mdfSystemRecordStatusNav"
                    }
                },
                "wfRequestNav": {
                    "__deferred": {
                        "uri": "https://sandbox.api.sap.com:443/successfactors/odata/v2/PaymentInformationV3(effectiveStartDate=datetime'2015-01-21T00:00:00',worker='82096')/wfRequestNav"
                    }
                }
            }
        ]
    }
}
```

PaymentInformationV3とそのmdfSystemStatusNavを１つのリクエストで取得できました。
カンマで区切れば複数指定ができるので、複数の関連するエントリを一括取得することも可能です。

## $expand使用時の注意点（ODataV2の場合）
### 指定したエントリに対して条件指定はできない
\$expandで指定したエントリ内に複数データがある場合、条件を指定しそれにあったデータを取得したくなりますが、それはできません。
[\$expandとは]内の図を例とすると、/カテゴリ(1)?\$expand=製品&$filter=製品eqAでカテゴリ(1)とその製品（A）だけを取得することはできません。
一致するデータがある場合は全件を取得し、一致するデータがない場合は全件取得しないといった意図しない動作をします。
なのでデータを取得後、必要なデータのみ抽出する処理が必要となります。