効率的なSOQLの書き方

## 背景
SOQLはSalesforce開発の要と言われ、効率的にSOQLを書くことによって、Apex処理のパフォーマンスに影響されてます。

## 注意点
### 選択的なフィルターを行うこと
ApexのSOQLを実行される際に、クエリの実行が失敗する可能性があります。そのため壮大なデータを持つオブジェクトをフィルターする際に、慎重にかつ選択的にフィルタリングを行う必要があります。
以下にいくつか選択的にフィルターリングする方法を説明します：
- **文字列の先頭にワイルドカードを使用しないこと**
Salesforceがクエリの実行を最適化するのに役立ち、スキャンする必要があるレコードの数を減らします。文字列の先頭にワイルドカードを使用することを避け、フルスキャンを防ぎましょう。
    **Good** （ パフォーマンス向上のためには、末尾に%を使用する）
    ```sql
    SELECT Name FROM Account WHERE Name LIKE 'A%'
    ```
    **bad** （文字列の先頭にワイルドカードを使用する）
    ```sql
    SELECT Name FROM Account WHERE Name LIKE '%Corp'
    ```
- **オブジェクトのインデックスを使うこと**
各Salesforceオブジェクトにはインデックス付きの項目が存在しています。SOQLを作成する際には、必ずこれらのインデックスを考慮する必要があります。
オブジェクト設定ページの項目とリレーションにはどの項目がインデックス付きなのかを確認することができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2262/b7dbe6d4-96fd-bcef-33a1-07e73bb2ec55.png)
- WHERE句にはインデックス付きの項目で使った方のパフォーマンスを向上できます。
    例：
    **Good** （WHEREにインデックス付きの項目を使う）
    ```sql
    SELECT Id FROM ACCOUNT WHERE Name LIKE 'A%'
    ```
    **Bad**（WHEREにインデックス付きの項目を使わない）
    ```sql
    SELECT Id FROM ACCOUNT WHERE ShippingAddress LIKE 'A%'
    ```
###  SELECT句でフィールドを制限する
SELECT句で必要なフィールドだけを取得しましょう。不要なフィールドを取得することはリソースを消費し、パフォーマンスに影響を及ぼす可能性があります。必要なデータだけを取得することで、クエリの実行時間を最小限に抑えます。

### LIMITおよびOFFSETを使用する
大規模なデータセットを扱う場合、LIMIT句を使用して取得するレコード数を制限することを検討してください。さらに、OFFSETを使用してページネーションクエリを実現できます。ただし、OFFSETは大規模なデータセットと使用する場合にはパフォーマンスに影響を与える可能性があるため注意が必要です。

```sql
-- レコード数を制限
SELECT Name FROM Account LIMIT 10
-- ページネーションのためにOFFSETを使用
SELECT Name FROM Account LIMIT 10 OFFSET 20
```

### 集計関数を使用する
データを集計するために集計関数を利用しましょう。SalesforceではCOUNT、SUM、AVG、MIN、MAXなどの一般的な集計関数がサポートされています。これらの関数を使用すると、すべてのレコードを取得せずに有益な洞察を得ることができます。

```sql
-- データを集計するために集計関数を使用
SELECT Industry, COUNT(Id) FROM Account GROUP BY Industry
```

### サブクエリを使用する
効率的に関連データを取得するためにサブクエリを活用しましょう。サブクエリは、関連オブジェクトからデータをアクセスするためにSELECT、FROM、WHERE句で使用できます。

```sql
-- SELECT句でのサブクエリの使用例
SELECT Name, (SELECT LastName FROM Contacts) FROM Account
```

### IDをハードコードしない
クエリにIDをハードコードすることは避けましょう。ハードコードされたIDは環境によって変更される可能性があります。代わりに動的な参照またはバインド変数を使用しましょう。

```java
// IDをハードコードせずに動的な参照を使用
SELECT Name FROM Account WHERE Id = :recordId
```

### テストと最適化
定期的にSOQLクエリをテストし、特に大規模なデータセットを扱う場合は注意してください。デベロッパーコンソールのクエリプランツールを使用して、Salesforceがクエリをどのように実行しているかを理解し、必要に応じて最適化を行いましょう。

## クエリプラン
### クエリプランとは
クエリプランとは開発者コンソール上で実行できるSOQLのパフォーマンス評価ツールです。

### 設定
クエリプラン機能はデフォルトでは無効のため、下記手順に従って有効化する必要があります。
①「開発者コンソール＞Help＞Preferences」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2262/823dc6e9-ed9a-ecbb-e509-4df540aa9e63.png)
②「Enable Query Plan」にチェックを付けて保存
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2262/79516849-9077-69ca-adfb-02248c140e33.png)

### 実行したクエリをクエリプランで分析
Query Planを有効化すると、開発者コンソール下部の「Query Editor」タブ内に「Query Plan」ボタンが表示されるようになります。
SOQL文を入力し、「Query Plan」ボタンを押すと、結果が確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2262/42d0b1cc-1b5a-3fea-aa9e-67c7a72051ae.png)
- Cardinality：返ってくるレコード数（の予想）
- Fields：クエリオプティマイザにより利用されるインデックス項目。クエリ時にインデックスを使用していない場合、値はnullとなる。
- Leading Operation Type：クエリ最適化のためにSalesforceが利用する操作種別。
- Index：クエリでインデックスを使用している
- Sharing：クエリで共有ルールに基づいたインデックスを使用している
- Table Scan：クエリ対象オブジェクトの全レコードがスキャンされている
- Other：Salesforceの内部的な最適化が適用されている
- Cost：（クエリオプティマイザの選択的閾値と比較した場合の）クエリのコスト。値が1を超えるクエリは決して選択的（Selective）とならない。
- SObject Cardinality：クエリ対象のオブジェクトのレコード数
## まとめ
高性能なSOQLクエリを書くことはSalesforce開発者や管理者にとって重要なスキルです。これらのベストプラクティスとヒントに従うことで、クエリのパフォーマンスを向上させ、リソースの消費を最小限に抑え、効率的にデータを取得できます。常にガバナー制限を考慮し、Salesforce環境で最適なパフォーマンスを確保するために選択的でターゲットなクエリを心掛けましょう。

## 参考リンク
- [SOQL クエリを選択的にする](https://help.salesforce.com/s/articleView?id=000385218&type=1)
- [SOQL クエリのパフォーマンスの改善 - 選択性とカスタムインデックス](https://help.salesforce.com/s/articleView?id=000387172&type=1)
- [クエリプランツールの FAQ](https://help.salesforce.com/s/articleView?id=000386864&type=1)