limits using AggregateResult in batch APEX - salesforce

I've just recently run into a governor limit using Batch Apex with the AggregateResult object and I was hoping someone could clearly explain this limit. I think batch APEX could support up to 50 million records, but when I use AggregateResult, I hit governor limits even though the total records in the object was about 250,000.
How does the AggregateResult limit apply within batch APEX?
Any explanation on how this limit is applied and how to overcome it would be appreciated. Should I simply avoid using AggregateResult within batch APEX? Or, if I do use it, how do I determine when this limit will be hit?
Here is a simple batch program I wrote using AggregateResult. This runs monthly and I'm aggregating Tasks related to a case by user. How can I determine if this will hit limits?
global class MemberCaseAchievementBatch implements Database.Batchable<sObject> {
public string query = 'select Id, Enhancement_Value__c from Case where RecordType.DeveloperName = \'SF_Enhancement\' AND Stage__c = \'Deployed\' AND ClosedDate = THIS_MONTH';
public Set<Id> caseIds = new Set<Id>();
public Set<Id> allCaseIds = new Set<Id>();
Integer totalCompletedCases = 0;
Integer totalAssignedCases = 0;
Double totalCompletedValue = 0;
global database.querylocator start(Database.BatchableContext BC)
{
return Database.getQueryLocator(query);
}
global void execute(Database.BatchableContext BC, Sobject[] scope)
{
for(Case c : (List<Case>)scope) {
caseIds.add(c.Id);
totalCompletedCases++;
if(c.Enhancement_Value__c == null) {
totalCompletedValue = c.Enhancement_Value__c;
}
else {
totalCompletedValue += c.Enhancement_Value__c;
}
}
for(Case c : [select Id from Case where RecordType.DeveloperName = 'SF_Enhancement' AND CreatedDate = THIS_MONTH]) {
allCaseIds.add(c.Id);
totalAssignedCases++;
}
Map<Id, Member_Goal__c> memberGoalsMap = getMemberGoals();
List<Member_Case_Achievement__c> achievementsToInsert = new List<Member_Case_Achievement__c>();
AggregateResult[] results = getMemberAchievementMetrics(caseIds);
Map<String, AggregateResult> tasksAssigned = getMemberTotalTasksAssigned(allCaseIds);
Date firstDayOfMonth = System.today().toStartOfMonth();
Date lastDayOfMonth = firstDayOfMonth.addDays(Date.daysInMonth(firstDayOfMonth.year(), firstDayOfMonth.month()) - 1);
for(AggregateResult ar : results) {
Member_Case_Achievement__c mca = new Member_Case_Achievement__c();
if(memberGoalsMap.containsKey(String.valueOf(ar.get('OwnerId')))) mca.Member_Goal__c = memberGoalsMap.get(String.valueOf(ar.get('OwnerId'))).Id;
AggregateResult aggResult = tasksAssigned.get(String.valueOf(ar.get('OwnerId')));
mca.Total_Assigned_Tasks__c = (Integer)aggResult.get('expr0');
mca.Total_Completed_Tasks__c = (Integer)ar.get('expr1');
mca.Total_Completed_Task_Value__c = (Double)ar.get('expr0');
mca.Total_Assigned_Cases__c = totalAssignedCases;
mca.Total_Completed_Cases__c = totalCompletedCases;
mca.Total_Completed_Value__c = totalCompletedValue;
mca.Period_Start_Date__c = firstDayOfMonth;
mca.Period_End_Date__c = lastDayOfMonth;
achievementsToInsert.add(mca);
}
if(!achievementsToInsert.isEmpty() && achievementsToInsert.size() > 0) {
insert achievementsToInsert;
}
}
global void finish(Database.BatchableContext BC) {
}
private AggregateResult[] getMemberAchievementMetrics(Set<Id> caseids) {
AggregateResult[] groupedResults = [select OwnerId, SUM(Task_Value__c), Count(Id) from Task where WhatId in: caseids AND Subject in ('Requirements','Design','Develop / Config','Unit Testing') group by OwnerId];
return groupedResults;
}
private Map<String, AggregateResult> getMemberTotalTasksAssigned(Set<Id> caseids) {
Map<String, AggregateResult> aggregateResultMap = new Map<String, AggregateResult>();
for(AggregateResult ar : [select OwnerId, Count(Id) from Task where WhatId in: caseids AND Subject in ('Requirements','Design','Develop / Config','Unit Testing') group by OwnerId]) {
aggregateResultMap.put(String.valueOf(ar.get('OwnerId')), ar);
}
return aggregateResultMap;
}
private Map<Id, Member_Goal__c> getMemberGoals() {
Map<Id, Member_Goal__c> memberGoalMap = new Map<Id, Member_Goal__c>();
for(Member_Goal__c mg : [select Id, Member__c from Member_Goal__c where Goal_Period__r.Period_Start_Date__c = THIS_MONTH]) {
memberGoalMap.put(mg.Member__c, mg);
}
return memberGoalMap;
}
}
Is the limit determined by the scope? Or, is it determined by the AggregateResult query?

Batch apex allows you to run batches of up to 50 million records. So in your case you could batch over 50M Cases. Normal Apex limits still apply however inside of each batch.
You could try to limit the scope size of each batch via Database.executeBatch(yourBatch, scopeSize); where scopeSize is an Integer between 1 and 2000. Setting this to a lower number (200 is the default) would run fewer Cases per batch, in effect hopefully decreasing the size of your aggregate result too.

Related

Guys I don't understand how to write tests

I have a class in which I get the number of ids per year with a soql query on a custom object:
public static Integer numberRecords(Integer year) {
List<AggregateResult> numbersOfRecords = [
SELECT Keeper__c
FROM Month_Expense_Application__c
WHERE calendar_year(MonthDate__c) =: year
];
Set<Id> keepers = new Set<Id>();
for (AggregateResult result : numbersOfRecords) {
keepers.add((Id)result.get('Keeper__c'));
}
Integer value = keepers.size();
return value;
}
I'm trying to write a test for this method by creating an object and filling in the fields it needs, then I try to get the id with a soql request and compare:
#isTest
public class ExpenseAdminControllerTests {
#isTest
public static void numberRecordsTests() {
Month_Expense_Application__c monthExpenseTest = new Month_Expense_Application__c(
Balance__c = 12.3,
Income__c = 11,
Keeper__c = '0034x00001K7kGCAAZ',
MonthDate__c = date.newInstance(2022, 11, 21)
);
Integer year = 2022;
List<Month_Expense_Application__c> numbersOfRecords = [
SELECT Keeper__c
FROM Month_Expense_Application__c
WHERE calendar_year(MonthDate__c) =: year AND Id =: monthExpenseTest.Id
];
Set<Id> keepers = new Set<Id>();
keepers.add(numbersOfRecords[0].Keeper__c);
Integer value = keepers.size();
System.assertEquals(1, value);
}
}
But i cant tell me what am i doing wrong
I guess you just forgot to insert "monthExpenseTest" in actual database, by means that you have just created a object by using below lines as you mentioned
Month_Expense_Application__c monthExpenseTest = new Month_Expense_Application__c(
Balance__c = 12.3,
Income__c = 11,
Keeper__c = '0034x00001K7kGCAAZ',
MonthDate__c = date.newInstance(2022, 11, 21)
);
by writing above lines you just created object which is present in memory only. it is not stored in database, so that it is not fetched when you try to fetch it via SOQL Query.
Now you may want to add one more line after above code.which is given as below
insert monthExpenseTest ;
so after inserting your object is stored in database. now you can fetch it via SOQL query.
So your test class will be looks like below
#isTest
public class ExpenseAdminControllerTests {
#isTest
public static void numberRecordsTests() {
Month_Expense_Application__c monthExpenseTest = new Month_Expense_Application__c(
Balance__c = 12.3,
Income__c = 11,
Keeper__c = '0034x00001K7kGCAAZ',
MonthDate__c = date.newInstance(2022, 11, 21)
);
insert monthExpenseTest;
Integer year = 2022;
Integer keepers = ExpenseAdminController.numberRecords(year);
System.assertEquals(1, keepers);
}
}
So when you are testing ExpenseAdminController then you just need to call that method because you want to test it. in test class which is mentioned in question it not testing numberRecords() method instead of you are rewriting logic.

I am trying to add all the opportunity amount on quota actual amount field

I need to add all(SUM) the amount from the opportunity of all closed won opportunity which lies between the start and end date of quota.
Opportunity should be closed won
closed date should be within the start and end date of quota
assigned to the user which is a look up field on quota which is equal to
owner of quota
Below is the code
public with sharing class listopponquota {
public list<Opportunity> oppList{get;set;}
public listopponquota()
{
String selectedVal ='';
oppList = new list<Opportunity>();
oppList();
}
*public pageReference oppList() {
Id qId = (Id) ApexPages.currentPage().getParameters().get('id');
system.debug('Id-->'+qId);
List<Opportunity> oppList1 = new List<Opportunity>();
oppList1 = [Select Id, Name, CloseDate, StageName,OwnerId,Amount From Opportunity where StageName = 'Closed Won'];
system.debug('quotaList1-->'+quotaList1);
if(quotaList1.size() > 0){
for(quota__c q : quotaList1){
map<id,double> amtmap = new map<id,double>();
for(aggregateresult ag : [SELECT SUM(Amount) sum,Quota__c FROM Opportunity where Quota__c in :quotaList1 and StageName = 'Closed Won' group by Quota__c ]){
amtmap.put((ID)ag.get('Quota__c'), double.valueof(ag.get('SUM')));
{
amtmap.put((ID)ag.get('Quota__c'), double.valueof(ag.get('SUM')));
}
list<Quota__c>Qtalist = new list<Quota__c>();
for(id iid : QtaIds){
Quota__c quot = new Quota__c(id=iid);
if(amtmap.containskey(iid)){
q.Actual_Amount__c = amtmap.get(iid);
}else{
q.Actual_Amount__c = 0;
}
Qtalist.add(quot);
}
if(Qtalist.size()>0){
update Qtalist;
return null;
}
}
}
}
}
}*
Part of the issue may be that you are not defining quotaList1. You should also probably pull that SOQL query out of the for loop. Grab all the records you need prior to entering the loop.
A quick modification to your SOQL query:
[SELECT SUM(Amount) sum,Quota__c FROM Opportunity where Quota__c in :quotaList1 and IsWon = true group AND CloseDate >= Quota__r.StartDate__c AND CloseDate <= Quota__r.EndDate__c AND Quota__r.OwnerId =: System.UserInfo.getUserId() by Quota__r.Name ]
Most importantly though, how do the objects relate? Are the opportunities children of the Quota__c object, or is there also a field on the Quota__c defining the opp it is tied to?

How to get the code coverage required to deploy Apex Class

Please Help!!!
I have this Apex controller class that I'm trying to deploy from Salesforce Sandbox to Production but I'm not getting the code coverage required!!
the class is consuming 3 objects, one is stranded and 2 are custom and constructing a hierarchy tree view for those three objects.
//Apex Class
public class TeeView {
/* Wrapper class to contain the nodes and their children */
public class cNodes
{
Public StandardObject gparent {get;set;}
public List<CustomObject1__c> parent {get; set;}
Public List<CustomObject2__c> child {get;set;}
public cNodes(StandardObject gp, List<CustomObject1__c> p, List<CustomObject2__c> c)
{
parent = p;
gparent = gp;
child = c;
}
}
/* end of Wrapper class */
Public List<cNodes> hierarchy;
Public List<cNodes> getmainnodes()
{
hierarchy = new List<cNodes>();
List<StandardObject> tempparent = [Select Id,Name , End_Date__c, Owner.Name Account.Name from Contract ];
for (Integer i =0; i< tempparent.size() ; i++)
{
List<CustomObject1__c> tempchildren = [Select Id,Name, Owner.Name , (select Id,Name, Owner.Name from CustomObject2__r) from CustomObject1__c where Related_Field__c = :tempparent[i].Id];
List<CustomObject2__c> tempchild = [Select Id,Name Owner.Name from CustomObject2__c where Related_Field__c= :tempparent[i].Id];
hierarchy.add(new cNodes(tempparent[i],tempchildren, tempchild));
}
return hierarchy;
}
}
//Test Class
#isTest
public class treeviewTest
{
static testMethod void test1()
{
test.startTest();
Account acc = new Account(Name = 'Unit test account');
insert acc;
StandardObject c = new StandardObject(
Name = 'test',
AccountId = acc.Id,
Status = 'Draft',
StartDate = System.today());
try
{
insert c;
}
catch (Exception ex)
{
ApexPages.addMessages(ex);
}
List<StandardObject> standard = [select Id, Name from StandardObject where Name = 'test'];
system.assertequals(standard.size(), 1);
CustomObject1__c s = new CustomObject1__c(
Related_StandardObjectField__c = c.Id,
Name = 'test'
);
try
{
insert s;
}
catch (Exception ex)
{
ApexPages.addMessages(ex);
}
List<CustomObject1__c> cus1 = [select Id, Name from CustomObject1__c where Name = 'test'];
system.assertequals(cus1.size(), 1);
insert new CustomObject2__c(Related_StandardObjectField__c = c.Id, Description__c = 'test');
List<CustomObject2__c> cus2 = [select Id, Name from CustomObject2__c where Description__c = 'test'];
system.assertequals(cus2.size(), 1);
insert new CustomObject2__c(Related_CustomObject1Field__c = s.Id, Description__c = 'test');
List<Mods__c> cus3 = [select Id, Name from Mods__c where Description__c = 'test'];
system.assertequals(cus3.size(), 1);
treeView view = new treeView();
view.getmainnodes();
test.stopTest();
}
}
Oh man, where do we even start... Looks like you copy-pasted some code at random trying to get the test to work? Your code works on Contract, CustomObject1__c, CustomObject2__c. Your unit test inserts Contract, Subaward__c, Mods__c. Was this some attempt at anonymizing your code? And what's this List<StandardObject> thing, it shouldn't even compile.
I think you need help of a developer with more experience, you've cobbled something together but it doesn't follow the Salesforce best practices for code...
About the main class
Smart use of subqueries will mean
you waste less SOQL (it's bad idea to blindly select all records without any filters but whatever),
don't run into errors related to "too many SOQL statements" (the limit is 100 so since you query in a loop - your code will crash and burn the moment you have around 50 Contracts)
lead to compact code occupying less lines (easier to maintain & cover with tests).
I'd go with something like that, query Contract & first related list in one go and then the parents + children in another query. Using a Map to link them for displaying.
List<Contract> levelOneAndTwo = [SELECT Id, Name, End_Date__c, Owner.Name, Account.Name,
(SELECT Id,Name, Owner.Name FROM Subawards__r)
FROM Contract];
Map<Id, SubAward__c> levelTwoAndThree = new Map<Id, Subaward__c>([SELECT Id,
(SELECT Id,Name Owner.Name FROM Mods__r)
FROM Subaward__c WHERE Contract__c = :levelOneAndTwo];
);
This wastes only 2 queries but fetches all contracts and their related data. Then you can loop over results building your wrapper object or just feed it directly to Visualforce for displaying.
About the unit test
You don't get the coverage because you're spread too thin. One unit test methods inserts only Contracts (so your main query will hit something, give you some coverage). Then another method (in completely different context) inserts some child objects - but they don't matter. In that other method there are 0 contracts so it won't even enter your main loop. Combine it into one?
Account acc = new Account(Name = 'Unit test account');
insert acc;
Contract c = new Contract(
Name = 'test contract',
AccountId = acc.Id,
Status = 'Draft',
StartDate = System.today()
);
insert c;
Subaward__c s = new Subaward(
Contract__c = c.Id,
Name = 'test'
);
insert s;
insert new Mod__c(Subaward__c = c.Id, Description__c = 'test');
Now you have nice tree hierarchy set up, from Account all the way down. Queries in the test should hit some data.
I was finally able to get %100 code coverage for the above class by adding these lines to the test class:
treeView view = new treeView();
view.getmainnodes();
system.assert(view.hierarchy.size() > 0);

Apex Batch Job Start and Execute are not ran (no debug statements from them showing up)

My batch job class is as follows:
global class GroupMemberSearch implements Database.Batchable<SObject>{
global String groupQuery;
global final Map<Id, Group> groupIdMap;
global GroupMemberSearch(String groupQuery){
system.debug(groupQuery);
this.groupQuery = groupQuery;
}
global Database.QueryLocator start(Database.BatchableContext BC){
system.debug('In batch start');
return Database.getQueryLocator(groupQuery);
}
global void execute(Database.BatchableContext BC, List<SObject> scope){
system.debug(scope);
List<groupInfo> grpMemberList = new List<groupInfo>();
for(Group s : (List<Group>)scope){
system.debug(s);
groupInfo newGroup = new groupInfo();
if(s.Name != null){
set<Id> memberIdSet = getGroupMembers(new set<Id>{s.Id}, 0);
if(memberIdSet.size() != 0){
newGroup.groupId = s.Id;
newGroup.groupName = s.Name;
newGroup.groupMemberIds = memberIdSet;
grpMemberList.add(newGroup);
}
}
}
system.debug(grpMemberList);
}
global void finish(Database.BatchableContext BC){}
private set<Id> getGroupMembers(set<Id> groupIds, Integer queryLimit){
set<Id> nestedIds = new set<Id>();
set<Id> returnIds = new set<Id>();
if(queryLimit < 5){
List<GroupMember> members = [SELECT Id, GroupId, UserOrGroupId FROM GroupMember WHERE GroupId IN :groupIds];
for(GroupMember member : members){
if(Schema.Group.SObjectType == member.UserOrGroupId.getSObjectType()){
nestedIds.add(member.UserOrGroupId);
} else{
returnIds.add(member.UserOrGroupId);
}
}
}
queryLimit++;
if(nestedIds.size() > 0){
returnIds.addAll(getGroupMembers(nestedIds, queryLimit));
}
return returnIds;
}
public class groupInfo{
public String groupId;
public String groupName;
public set<Id> groupMemberIds;
}
}
And in my apex controller, I call
Id batchProcessId = Database.executeBatch(new GroupMemberSearch('SELECT Id, OwnerId, Name, Email FROM Group WHERE Name != null'), 20);
which returns a batch Id, and I see the system.debug from the apex batch job constructor... However, I don't see any system.debugs from the start and execute methods... I restricted my log to show only debug statements and they were not in there anywhere. I know my query returns a list of groups, even if it didn't, I should still see the system.debug from the start method before the return...
I want this batch job ran when the page is loaded, thus it is in the constructor of the apex controller.
Any idea why the start and execute methods are not being ran??
Thank you for any help!

salesforce How to reach 75% apex test

I am at 71%, 4 lines of code cannot be run in the test for some reason.
When I test myself in Salesforce it works (those lines of code are running).
How can I get these lines of code to run in the test?
Lines not running, in second for loop
nextId=Integer.Valueof(c.next_id__c);
Lines not running, in third for loop
btnRecord.next_id__c = newid + 1;
btnRecord.last_id__c = newId;
btnRecord.last_assigned_starting_id__c = nextId;
btnRecord.last_assigned_ending_id__c = newId;
Below is my code:
trigger getNextId on tracking__c (before insert, before update) {
Integer newId;
Integer lastId;
Integer nextId;
newId=0;
lastId=0;
nextId =0;
//add the total accounts to the last_id
for (tracking__c bt: Trigger.new) {
//get the next id
List<tracking_next_id__c> btnxtid = [SELECT next_id__c FROM tracking_next_id__c];
for (tracking_next_id__c c : btnxtid )
{
nextId=Integer.Valueof(c.next_id__c);
}
newId = Integer.Valueof(bt.total_account__c) + nextId;
bt.starting_id__c = nextId;
bt.ending_id__c = newId;
tracking_next_id__c[] nextIdToUpdate = [SELECT last_id__c, next_id__c, last_assigned_starting_id__c, last_assigned_ending_id__c FROM tracking_next_id__c];
for(tracking_next_id__c btnRecord : nextIdToUpdate ){
btnRecord.next_id__c = newid + 1;
btnRecord.last_id__c = newId;
btnRecord.last_assigned_starting_id__c = nextId;
btnRecord.last_assigned_ending_id__c = newId;
}
update nextIdToUpdate ;
}
}
Even though test coverage is increased by using seeAllData=true, it is not best practice to use seeAllData unless and until it is really required. Please find the blog here for details.
Another way to increase the coverage is by creating test Data for tracking_next_id__c object.
#isTest
private class getNextIdTest {
static testMethod void validateOnInsert(){
tracking_next_id__c c = new tracking_next_id__c(next_id__c='Your next_id',
last_id__c='Your last_id', last_assigned_starting_id__c='Your last_assigned_starting_id',
last_assigned_ending_id__c='last_assigned_ending_id');
insert c;
tracking__c b = new tracking__c(total_account__c=Integer.Valueof(99));
System.debug('before insert : ' + b.total_account__c);
insert b;
System.debug('after insert : ' + b.total_account__c);
List<tracking__c> customObjectList =
[SELECT total_account__c FROM tracking__c ];
for(bid_tracking__c ont : customObjectList){
ont.total_account__c = 5;
}
update customObjectList;
}
}
I have added below line so that, when 2 queries get executed before FOR loops (which were not covered previously) it will fetch data as we have inserted it in test class now.
tracking_next_id__c c = new tracking_next_id__c(next_id__c='Your next_id',
last_id__c='Your last_id', last_assigned_starting_id__c='Your last_assigned_starting_id',
last_assigned_ending_id__c='last_assigned_ending_id');
insert c;
Just an observation, it is best to avoid SOQL query in FOR loop to avoid Runtime Exception (101:Too many SOQL query)
#isTest
private class getNextIdTest {
     static testMethod void validateOnInsert(){
tracking__c b = new tracking__c(total_account__c=Integer.Valueof(99));
System.debug('before insert : ' + b.total_account__c);
insert b;
System.debug('after insert : ' + b.total_account__c);
List<tracking__c> customObjectList =
 [SELECT total_account__c FROM tracking__c ];
for(bid_tracking__c ont : customObjectList){
ont.total_account__c = 5;
}
update customObjectList;
}
}
Added #isTest(SeeAllData=true) and it moved to 100%
https://developer.salesforce.com/forums/ForumsMain?id=9060G000000I5f8

Resources