function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
phoenix7788phoenix7788 

Apex Batchのエラーについて

お世話になっております。

以下のようなApex Batchを書いていますが、

  global Iterable<AggregateResult> start(Database.BatchableContext bc) {
    AggregateResult[] sg= [SELECT Phone FROM Account WHERE Phone <> null GROUP BY Phone HAVING COUNT(Phone) >= 2];
    return sg;
  }

なぜかToo many query rowsのエラーが出ました。

09:39:03.0 (22546931)|SOQL_EXECUTE_BEGIN|[32]|Aggregations:0|SELECT Phone FRO Account WHERE Phone != NULL GROUP BY Phone HAVING COUNT(Phone) >= 2
09:39:03.0 (570162894)|SOQL_EXECUTE_END|[32]|Rows:44
09:39:03.0 (570212289)|EXCEPTION_THROWN|[32]|System.LimitException: Too many query rows: 50001
09:39:03.0 (570386384)|HEAP_ALLOCATE|[32]|Bytes:30
09:39:03.0 (570430741)|SYSTEM_MODE_EXIT|false 09:39:03.0 (570514055)|FATAL_ERROR|System.LimitException: Too many query rows: 50001

Apex Batch最初のデータ取得で件数制限ないと思いますが、件数制限エラーが出ました。
(ちなみに、Name項目だと問題なく実行できました。
× System.LimitException: Too many query rows: 50001
AggregateResult[] sg= [SELECT Phone FROM Account WHERE Phone <> null GROUP BY Phone HAVING COUNT(Phone) >= 2];
○ No error
AggregateResult[] sg= [SELECT Name FROM Account WHERE Name <> null GROUP BY Name HAVING COUNT(Name) >= 2];
)

なぜでしょうか??
Taiki YoshikawaTaiki Yoshikawa
ApexバッチのStartで大量件数取得のクエリを実行するときは、Database.getQueryLocatorをつかって実行します。
ちなみにPhoneとNameでエラーの有無の件ですが、WHEREの条件的にたまたま50000件を超えていなかったという理由だと思います。
global class SearchAndReplace implements Database.Batchable<sObject>{

   global final String Query;
   global final String Entity;
   global final String Field;
   global final String Value;

   global SearchAndReplace(String q, String e, String f, String v){

      Query=q; Entity=e; Field=f;Value=v;
   }

   global Database.QueryLocator start(Database.BatchableContext BC){
      return Database.getQueryLocator(query);
   }

   global void execute(Database.BatchableContext BC, List<sObject> scope){
     for(sobject s : scope){
     s.put(Field,Value); 
     }
     update scope;
    }

   global void finish(Database.BatchableContext BC){
   }
}

Using Batch Apex
https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_batch_interface.htm
phoenix7788phoenix7788
ご返答ありがとうございます。

>>ApexバッチのStartで大量件数取得のクエリを実行するときは、Database.getQueryLocatorをつかって実行します。
ちなみに、Database.getQueryLocator以外の方法で大量データを取得する場合、50000件制限に引っかかるんですか?

「SELECT Phone FROM Account WHERE Phone <> null GROUP BY Phone HAVING COUNT(Phone) >= 2」のSOQLを実行するため、Database.getQueryLocatorでは対応できないみたいです。
Taiki YoshikawaTaiki Yoshikawa
Apex Guideに、"QueryLocator オブジェクトを使用する場合、SOQL クエリによって取得されるレコード合計数に対するガバナ制限は無視されます。 " と記載がありましたので、普通にクエリを実行してしまうとエラーになると思います。

Apex の一括処理の使用
https://developer.salesforce.com/docs/atlas.ja-jp.200.0.apexcode.meta/apexcode/apex_batch_interface.htm?search_text=Database.getQueryLocator
Taiki YoshikawaTaiki Yoshikawa
取引先に登録された電話番号が2件以上重複しているかのチェックという感じでしょうか。

ちょっとうまくいくかわかりませんが、次の手順でいけそうな気がします。
// startではGroupで絞り込まない
global Database.QueryLocator start(Database.BatchableContext BC) {
    String query = 'SELECT Id FROM Account WHERE Phone != null';
    return Database.getQueryLocator(query);
}

global void execute(Database.BatchableContext BC, List<Account> scope){
    // 取引先IDで絞り込んで重複している電話番号を取得
    AggregateResult[] sg= [SELECT Phone FROM Account WHERE Id IN scope GROUP BY Phone HAVING COUNT(Phone) >= 2];

    // 取得した電話番号をMap変数にセットして判定に利用するなどの処理・・・
}

これでガバナ制限に引っかからないようにクエリが実行されます。
AggregateResultリストにある電話番号はMap変数などに格納すれば取引先情報との紐付けもできると思います。

 
phoenix7788phoenix7788
>>取引先に登録された電話番号が2件以上重複しているかのチェックという感じでしょうか。
はい、そうです。

一つ気になっていることはList<Account> scopeに200件データが渡された場合、
200件目と201件目重複する場合、取引先IDで絞り込んで重複している電話番号を取得する際、正しく取得できない気がしますが、
つまり、200件目は渡されていすが、201件目は渡されていない可能性があります、すると重複されていない判断になります。
如何でしょうか?

やはり、50000件の制約条件で開発しかないかもしれません。
Taiki YoshikawaTaiki Yoshikawa
あ・・・すみません。。
IDで絞り込むと確かに正しく取得できてませんでした。

IDでなく電話番号を絞込条件に指定した場合はどうでしょうか。
Set<String> phoneList = new Set<String>();
for (Account a : accounts) {
    phoneList.add(a.Phone);
}
SELECT Phone FROM Account WHERE Phone IN: phoneList Group By Phone Having Count(Phone) >= 2