SOQLの高速化

## SOQLを高速にする方法

1. クエリをセレクティブにする(本ページで説明)。
2. 不要な項目を取得しない。
3. 数式項目を取得しない。
4. リレーションクエリを使用しない。
5. WHERE句に数式項目を使用しない。
6. 検索結果の件数が少なくなるように条件を設定する。

など

## セレクティブなクエリとは
テーブルの完全スキャンではなく、インデックスを使用するようにする(セレクティブなクエリ)と検索が高速になる。大規模データベースで極めて効果的。
### セレクティブなフィルタ
インデックスが付与された項目のフィルタで次の範囲内にレコードが絞り込まれるとき、セレクティブなフィルタとなる。

* 標準インデックス:最初の100万レコードで30%、それ以降で15%。ただし、最大100万レコードまで。
* カスタムインデックス:最初の100万レコードで10%、それ以降で5%。ただし、最大333,333レコードまで。
 
標準インデックスとカスタムインデックスについては後述。

ただし、次の場合、セレクティブなフィルタにはならない。

* 検索条件の演算子が、`!=`、`NOT CONTAINS`、`NOT STARTS WITH`などの否定演算子。
* 検索条件に CONTAINS 演算子が使用され、スキャンされる行数が 333,000 を超える。
* 空の値またはNULLと比較している(NULLを含まないインデックスの場合)。
    * `IN`で比較されるコレクションに空の値が含まれるとセレクティブにならない。
* 中間一致、後方一致
* 数値、日付、日時以外の範囲検索(`<`、`>`、`<=`、`>=`)

### セレクティブなクエリ
クエリの条件句が次の条件を満たすとき、セレクティブなクエリとなり、実行速度が高速化される。

* シンプルフィルタ:いずれかのフィルタがセレクティブであること。
* 複合フィルタ:すべてのフィルタがセレクティブであること

シンプルフィルタとは、ANDで接続された条件句のこと。`WHERE CreatedDate = this_year AND Status__c = 'Available' `
複合フィルタとは、ORで接続された条件句のこと。`WHERE CreatedDate = this_year OR Status__c = 'Available' `

実際にセレクティブなクエリかどうかは開発者コンソールのクエリプランツールを使用する(後述)。

(参考) 
[SOQL クエリを選択的にする](https://help.salesforce.com/articleView?id=000325257&language=ja&type=1&mode=1)

## インデックス
### 標準インデックス
* Id
* Name
* RecordTypeId
* Division
* Email(取引先責任者とリード)
* CreatedDate
* SystemModStamp
* LastModifiedDate (LastModifiedDate < DateField となるときはインデックスは使用されない)

(参考) 
[SOQL 実行時の LastModifiedDate と SystemModStamp のインデックス利用について](https://help.salesforce.com/articleView?id=000318519&language=ja&type=1&mode=1)

### カスタムインデックス
* 参照関係項目
* 主従関係項目
* 外部IDが設定された項目
* ユニークが設定された項目
* カスタムインデックスが付与された項目(Salesforceに依頼が必要)

#### 外部ID項目について
外部IDが設定できる項目: テキスト、数値、メール、自動採番
一意でなくても外部IDは設定できる。ただし、重複があるとき、upsertのキーに使用すると失敗する。

#### ユニークについて
ユニークが設定できる項目: テキスト、数値、メール

#### 2 列のカスタムインデックス
> 2 列のカスタムインデックスは、Salesforce プラットフォームの特殊機能です。これは、表示するレコードを選択するために 1 つの項目を使用し、それらのレコードを並び替えるために別の項目を使用するようなリストビューやその他の状況で便利です。たとえば、State で選択し、City で並び替える取引先リストビューでは、最初の列に State、2 番目の列に City を使用する 2 列インデックスを使用できます。

> 2 つの項目の組み合わせがクエリ文字列の一般的な検索条件であるとき、レコードの並び替えと表示に 2 列インデックスが一般的に役立ちます。たとえば、擬似コードに見られる次の SOQL の場合、f1__c,f2__c の 2 列インデックスは、f1__c and f2__c の単一列インデックスよりも効率的です。

> 2 列インデックスには、単一列インデックスと同じ制限が適用されます。ただし、例外が 1 つあります。2 列インデックスの 2 番目の列に null を使用できますが、単一列インデックスでは null を含めるオプションを Salesforce カスタマーサポートで明示的に有効にしていない限り使用できません。

2列インデックスは設定画面上から確認することはできない。
サポートに問い合わせてもどの項目に2列インデックスが設定されているか確認することはできない。
後述のクエリプランツールを使用して、2列インデックスが働いていることを確認することは可能。


* [インデックス | 大量のデータを使用するリリースのベストプラクティス | Salesforce Developers](https://developer.salesforce.com/docs/atlas.ja-jp.salesforce_large_data_volumes_bp.meta/salesforce_large_data_volumes_bp/ldv_deployments_infrastructure_indexes.htm)

#### カスタムインデックスの依頼について
カスタムインデックスを設定するには問い合わせのケースを作成する。以下の情報が必要

1. 組織ID
2. 対象のSOQL全文(※)
3. 実行ユーザ名(メール形式)
4. カスタムインデックスの有効性を確認した後、即時に付与を行って良いか [ はい/いいえ ]
5. 対象オブジェクトのAPI名
6. 対象項目のAPI名
7. その他
  * NULLをインデックスに含めるか否か
  * チェックボックス項目の場合、trueとfalseのどちらをインデックスに含めるか(通常はレコード数が10%以下の値を設定)

※カスタムインデックスを付与する場合はSalesforce社でSOQLの審査が行われる。依頼するSOQLは下記の点に注意。

* 変数を含まないこと(含む場合は具体的な値で置き換える)
* サブクエリを含まないこと


(参考)
[カスタムインデックスが必要な場合の依頼方法について](https://help.salesforce.com/articleView?id=000335072&type=1&mode=1)

#### インデックスを設定できない項目
以下の項目はカスタムインデックスを付与できない。

* 複数選択リスト
* マルチ通貨組織の通貨項目
* ロングテキスト項目
* 暗号化項目
* リッチテキストエリア項目
* 非決定性数式項目(下記のリンク参照)

(参考)
[Force.com SOQL Best Practices: Nulls and Formula Fields | Developer Force Blog](https://developer.salesforce.com/blogs/engineering/2013/02/force-com-soql-best-practices-nulls-and-formula-fields.html)


### インデックスの留意事項
* インデックスが増えるとデータベースへの書き込みに時間がかかる。

## 測定方法
クエリプランツールを使用する。

### 使用方法
1. 開発者コンソールで、Help > Preferences で表示される設定画面の Enable Query Plan をtrue に変更。
2. Query Editor にクエリを記入。
3. Query Plan ボタンをクリック。

### 測定結果について
* Reading Operation Type が「TableScan」か「Index」か確認する
* Query Plan ツールは与えられたクエリに対し、検索方法の候補を提示する。
* Fieldsにはインデックス化された項目が表示される。
* Costが1以上だとフィルタがセレクティブでないことを表す。

(参考)
[クエリプランツールのFAQ](https://help.salesforce.com/articleView?id=000334796&language=en_U&type=1&mode=1)

## クエリ速度に関する制限
### Apexトリガでの大量レコードのクエリ制限
Apexトリガで、クエリ対象が100万件を超えるオブジェクトに対し、セレクティブではないクエリを発行するとエラーになります。

>caused by: System.QueryException: Non-selective query against large object type (more than 1000000 rows). Consider an indexed filter or contact salesforce.com about custom indexing.
Even if a field is indexed a filter might still not be selective when:
1. The filter value includes null (for instance binding with a list that contains null) 
2. Data skew exists whereby the number of matching rows is very large (for instance, filtering for a particular foreign key value that occurs many times)

(参考)
[非常に大きい SOQL クエリの処理 | Apex開発者ガイド](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/langCon_apex_SOQL_VLSQ.htm)
[System.QueryException: Non-selective query against... エラーについて | Salesforceヘルプ](https://help.salesforce.com/articleView?id=000330208&language=ja&type=1&mode=1)


## 補足
インデックスは自動で設定されることがある。設定される条件は「プラットフォーム内部仕様のためお答えすることができません(サポート)」とのこと。
[自動的に作成されるカスタムインデックスについて](https://help.salesforce.com/articleView?id=000339697&language=ja&type=1&mode=1)

## 参考
* [カスタムインデックスが必要な場合の依頼方法について](https://help.salesforce.com/articleView?id=000335072&type=1&mode=1)
* [セレクティブ SOQL クエリを使用するカスタムインデックスによるパフォーマンスの向上](https://help.salesforce.com/articleView?id=000325247&language=ja&type=1&mode=1)
* [SOQL クエリの処理速度への考慮事項について](https://help.salesforce.com/articleView?id=000336897&language=ja&type=1&mode=1)
* [効率的なクエリの作成 単元 | Salesforce Trailhead](https://trailhead.salesforce.com/ja/content/learn/modules/database_basics_dotnet/writing_efficient_queries)
* [インデックス | 大量のデータを使用するリリースのベストプラクティス | Salesforce Developers](https://developer.salesforce.com/docs/atlas.ja-jp.salesforce_large_data_volumes_bp.meta/salesforce_large_data_volumes_bp/ldv_deployments_infrastructure_indexes.htm)
* [クエリプランツールのFAQ](https://help.salesforce.com/articleView?id=000334796&language=en_U&type=1&mode=1)
* [SOQL 実行時の LastModifiedDate と SystemModStamp のインデックス利用について](https://help.salesforce.com/articleView?id=000318519&language=ja&type=1&mode=1)
* [SOQL クエリを選択的にする](https://help.salesforce.com/articleView?language=en_US&type=1&mode=1&id=000325257)
* [大量データを扱う際のクイックTips インデックス&スキニーテーブル編-](https://www.slideshare.net/DeveloperForceJapan/tips-30066265)
* [レポートパフォーマンスの向上](https://help.salesforce.com/articleView?id=improving_report_performance.htm&type=5)
    * セレクティブな検索条件を設定することでレポートの実行速度も上がる。
* [インデックスの基礎知識](http://www.hi-ho.ne.jp/tsumiki/doc_1.html)