SalesforceのApexメソッド、Database.query()とDatabase.getQueryLocator()の違いとそれぞれの使いどころ

Salesforceでデータのセレクト結果を返すメソッドに2種類あり、どういうときに使い分けた方が良いのかが「あやふや」だったので調査したことをまとめておきます。

Database.Query()とDatabase.getQueryLocator()の違い

Database.Query()・・・このメソッドは、実行時に動的に生成したSOQLで検索を行いたいときに使用します。最大50,000件のレコードを取得することができます。もし、BatchApexなどで使用しても、50,000件以上処理することは出来ません。以下のような例でSOQLを動的に組み立てられます。

String fieldName = ',Name,Email';
String dynQuery = 'select Id ' + fieldName + ' From Account';
Database.query(dynQuery);

Database.getQueryLocator()・・・ApexBatchとVisualForceページで使用されることが多いですが、使う場所によって扱える件数が異なります。ApexBatchのなかで使用すると、最大5,000万件までのデータを扱うことができます。ただし、一回でまとめて取ってくるわけではなく、指定したバッチサイズの件数(デフォルト200件)ごとに取ってきて、それを繰り返しで処理します。データベースでいうところのカーソルのフェッチみたいな仕組みをイメージすると良いかもしれません。一回で取得する件数を抑えながらも、最終的には大量のデータを扱うことができるので、Batch Apexの処理内で使われるのは納得ですよね。

もうひとつの使いどころは、StandardSetControllerを使ったVisualForcePageで、主にページング処理を実現するのに使用されます。ここで使用するときには、取得できる最大の件数は10,000件に制限されます。

VisualForcePageでの使用イメージ

ここで、VisualForcePageのページング処理での使用イメージを載せておきます。

StandardSetControllerのコンストラクタは以下の2種類を持っています。

前者はsObjectのリストを渡す形式、後者はQueryLocatorを渡す形式です。

sObjectのリストを渡す形式

List<account> accountList = [SELECT Name FROM Account LIMIT 20];
ApexPages.StandardSetController ssc = new ApexPages.StandardSetController(accountList);

QueryLocatorを渡す形式

ApexPages.StandardSetController ssc = 
new ApexPages.StandardSetController(Database.getQueryLocator([SELECT Name,CloseDate FROM Opportunity]));

さて、StandardSetController のインスタンス化の際に受け取ることの出来るレコード数は10,000件に制限されています。ただし10,000件以上のレコードがHitして、コンストラクタに渡ったときの振る舞いは両者で異なります。

前者のsObjectのリストを渡したときには10,000件でレコードが切り捨てられ、エラーは出ません。しかし、後者のQueryLocatorの場合は、10,001件以上だとエラーになります。(Too many query locator rows)。

なお、ApexではStandardSetControllerを使用することで簡単にページネーション機能を実装することができますが、大量な件数を表示する場合は、ページのプロパティの中に、readOnly=trueを設定します。これで、通常は1000件までのレコード表示が50,000件まで行えるようになります。(でも、上で述べたように、QueryLocatorの場合は、10,001件以上だとエラーになります。)

違いを表にまとめると、、、

Database.query()Database.getQueryLocator()
50,000レコードまで扱える10,000レコードまで扱える
Batch Apexで使ったとしたら、レコード数は50,000レコードまでに制限されるBatch Apexで使ったとしたら、レコード数は5千万レコードまで扱える
VFページでread onlyを使わないのであればこちらを使う。VFページでread onlyを使うのであればこちらを使う。

のようになると思います。