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
S_BatmanS_Batman 

How to count checked field on related list?

I have a checkbox field called "Primary Contact" on the account level.

This field also shows up on the Contact Related list on the Account Object.

I want to create a custom field on the Account object, which counts and tells me how many contacts have "Primary Contact" box checked.

Could someone help me with the trigger to create that please?
Best Answer chosen by S_Batman
Mahesh DMahesh D
Please check if you have any other triggers on Account or Contact, if yes, please provide me that information as well.

Also take the below latest code:

 
trigger PrimaryContactCount on Contact (after insert, after delete, after undelete,after update) {
    set<Id> accIds = new set<Id>();
    
    if(trigger.isinsert || trigger.isUpdate || trigger.Isundelete){
        for(Contact con: Trigger.new){
			if(Trigger.isInsert || Trigger.isUndelete || (con.Primary_Contact_del__c != Trigger.oldMap.get(con.Id).Primary_Contact_del__c))
				accIds.add(con.AccountId);            
        }
    }
    
    if(trigger.isUpdate || trigger.isDelete) {
        for(Contact con: Trigger.old){
            if(Trigger.isDelete || (con.Primary_Contact_del__c != Trigger.oldMap.get(con.Id).Primary_Contact_del__c))
				accIds.add(con.AccountId);
        }
    }    
    
    List<Account> accList = [select id, Number_of_Primary_Contacts__c, (Select Id, Primary_Contact_del__c from Contacts) from Account Where ID IN: accIds];
    
    for(Account acc : accList){
        system.debug('Contacts--->'+acc.contacts.size());
        acc.Number_of_Primary_Contacts__c = 0;
        for(Contact con : acc.Contacts) {
			if(con.Primary_Contact_del__c)
				acc.Number_of_Primary_Contacts__c++;
        }
    }
    update accList;
}

Regards,
Mahesh

All Answers

REVNAREVNA
List<Contact> con = new List<Contact>();
    Account a=new Account();
    a.id='00190000016xxx';
    con=[Select id,Call_Me__c from Contact where Accountid=:a.id and Call_Me__c=true];
        a.NumberofChecks__c=con.size();
system.debug('----'+a.NumberofChecks__c);

I just created Call_ME__c on Contact and NumberofChecks__C on Account.
S_BatmanS_Batman
Sorry I am really new to APEX coding

would you be able to explain what each line does; so I have a better understanding?

What's the purpose of the Call Me field?
Mahesh DMahesh D
Basically you are looking for a custom roll-up summary kind of functionality.

Please check the below information:

There are multiple ways:


You can do the same with Process builder. Please check below post i hope that will help you
-------------------------------------
Option 1:- Process builder
1) https://developer.salesforce.com/forums/?id=906F0000000BHvJIAW
2) https://developer.salesforce.com/forums/?id=906F0000000B2C0IAK
-------------------------------------
Option 2:- Trigger
1) https://developer.salesforce.com/forums/?id=906F00000008yWuIAI
2) https://github.com/abhinavguptas/Salesforce-Lookup-Rollup-Summaries
3) http://www.anthonyvictorio.com/salesforce/roll-up-summary-utility/
4) http://www.iterativelogic.com/developing-custom-apex-rollup-summary-field-logic/
5) http://blog.jeffdouglas.com/2011/08/23/salesforce-trigger-when-rollups-summaries-not-possible/
------
For the option 2:

To calculate Rollup Summary using the Trigger manually, you need to follow below:

Need to consider all DML operations like insert, update, delete and undelete of child records.

Insert of Child record:
     --> Need to write an after insert trigger to update the Parent record with calculated sum value.
Update of Child record:
     --> Make sure that the Parent information got changed.
     --> Need to write an after update trigger and get both old and new Parent information and perform the re-calculation of sum.
Delete of Child record:
     --> Need to write an after delete trigger to update the Parent record with re-calculated sum value.
Undelete of Child record:
     --> Need to write an after undelete trigger to update the Parent record with re-calculated sum value.

Please do let me know if this helps you.

Regards,
Mahesh
venkat-Dvenkat-D
Its more complex than it looks.
You need to have a trigger on contact on after insert, after update , before delete and after undelete

You need to query for contacts where Primary_contact__c = true and store count in field on contact.

Refer to this for more information along with trigger
https://success.salesforce.com/answers?id=90630000000h3mNAAQ 
 
Vijay NagarathinamVijay Nagarathinam
Hi Sutha,

You can use aggregate function to get the count of the primary contacts,

AggregateResult[] groupedResults = [SELECT AccountId,SUM(Primary_Contact__c) FROM Contact GROUP BY accountId]; 

Let me know if you need any help regarding this.

Thanks,
Vijay
REVNAREVNA
Call Me field is just to give you an example. You can replace it with your checkbox field.

1 --> @VEnky There is not need to write on delete. Cox even if the record is deleted it wont matter. The query will pick only the records that have field value = true.

2--> @Mahesh - We cannt use Process builder , because it does not allow on check box field. (I may be wrong here. But this is my little knowledge.Correct me if I am wrong)

3--> @Vijay We cannot use SUM() on the field type picklist. 
venkat-Dvenkat-D
How will you calculate no of primary contacts if one of them is deleted without delete trigger.
S_BatmanS_Batman
@nayana revenna 4 - i tried duplicating your trigger, i got a compile error - unexpected token: List
Mahesh DMahesh D
Hi Sutha,

Here is the code, please change the Field Names according to your setup:
 
trigger PrimaryContactCount on Contact (after insert, after delete, after undelete,after update) {
    set<Id> accIds = new set<Id>();
    
    if(trigger.isinsert || trigger.isUpdate || trigger.Isundelete){
        for(Contact con: Trigger.new){
            accIds.add(con.AccountId);            
        }
    }
    
    if(trigger.isUpdate || trigger.isDelete) {
        for(Contact con: Trigger.old){
            accIds.add(con.AccountId);
        }
    }    
    
    List<Account> accList = [select id, Total_Primary_Contacts__c, (Select Id, Primary_Contact__c from Contacts) from Account Where ID IN: accIds];
    
    for(Account acc : accList){
        system.debug('Contacts--->'+acc.contacts.size());
        acc.Total_Primary_Contacts__c = 0;
        for(Contact con : acc.Contacts) {
			if(con.Primary_Contact__c)
				acc.Total_Primary_Contacts__c++;
        }
    }
    update accList;
}

Please do let me know if it helps you.

Regards,
Mahesh
S_BatmanS_Batman
Hi Mahesh,

I recreated this trigger,

Instead of Total_Primary_Contacts__c I have Number_of_Primary_Contacts__c on the Account level;

When I try to save it I am an error - 

Compile Error: No such column 'Number_of_Primary_Contacts__c' on entity 'Account'. If you are attempting to use a custom field, be sure to append the '__c' after the custom field name. Please reference your WSDL or the describe call for the appropriate names. at line 16 column 45
Mahesh DMahesh D
Please check the API Name, if possible take a print screen and show it here.

Regards,
​Mahesh
S_BatmanS_Batman
User-added image
Mahesh DMahesh D
Please provide the Field Name and its API Name.

 
S_BatmanS_Batman
Account Level:
Field Name - Number_of_Primary_Contacts
API Name - Number_of_Primary_Contacts__c

Contact Level:
Field Name: Primary_Contact_del
API Name: Primary_Contact_del__c


I really appreciate your assistance!
Mahesh DMahesh D
Could you please take a printscreen of field by going to Account --> Fields --> Number_of_Primary_Contacts click on it and take a print screen of it.
S_BatmanS_Batman
User-added image

When I researched this error, one blog was talking about field  level security; right now it is set to visible to just System Admin.
I am not sure if that is relevant at all?
Mahesh DMahesh D
Hi Sutha,

You can try below:

Step 1: First try to save by commenting the error code.
Step 2: Now uncomment it and try to save it. 
If the error still exists, then
Step 3: Enable the field to all profiles and see.
If the error still exists then
Step 4: Go to Workbench.developerforce.com --> SOQL --> try to write and execute simple SOQL query on Account object.
Step 5: Then copy our query and try to execute it in Work bench.
Step 6: Get the executed code and try to save it again.

Regards,
Mahesh
Mahesh DMahesh D
And also making sure that the field is available in the same org where you are writing the code.

When I see your Screenshot it looks like it is from Production.

Regards,
Mahesh
S_BatmanS_Batman
I assumed because the Number of Contact was on my production org that it would be in my sandbox; it wasn't so I added that and cleared that error.

Now I have another error:
Error: Compile Error: Incorrect SObject type: Contact should be Account at line 1 column 1

The Number of Primary Contact field is on the Account level; could that be causing the error?
Mahesh DMahesh D
Paste your full trigger here.
S_BatmanS_Batman
trigger PrimaryContactCount on Contact (after insert, after delete, after undelete, after update) {
    set<Id> accIds = new set<Id>();
    
    if(trigger.isinsert || trigger.isUpdate || trigger.Isundelete){
        for(Contact con: Trigger.new){
            accIds.add(con.AccountId);
            }
            }
            
            if(trigger.isUpdate || trigger.isDelete){
                for(Contact con: Trigger.old){
                    accIds.add(con.AccountId);
                    }
                    }
                    
                    List<Account> accList = [select id, Number_of_Primary_Contacts__c, (Select Id,  Primary_Contact_del__c from Contacts) from Account Where ID IN: accIds];
                    
                    for(Account acc: accList){
                        system.debug('Contacts--->'+acc.contacts.size());
                        acc.Number_of_Primary_Contacts__c = 0;
                        for(Contact con : acc.Contacts) {
                        if(con.Primary_Contact_del__c)
                        acc.Number_of_Primary_Contacts__c++;
                        }
                        }
                        update accList;
                        }
                      


I am also  creating this trigger on the Account level; should be on the contact level instead?
Mahesh DMahesh D
:) you have to create this trigger on the Contact object Sutha. :(
S_BatmanS_Batman
Sorry Mahesh!

This is literrally my first trigger in Salesforce!

I created the trigger in the Contact level;
but 
I want the field Number of Primary contact on the account level to equal to how many Contacts on the related list have Primary Contact check box (the Primary Contact field is a field on the Contact level)

when I created the trigger and tried it in my sanbox, i got this error;

Error:Apex trigger PrimaryContactCount caused an unexpected exception, contact your administrator: PrimaryContactCount: execution of AfterUpdate caused by: System.DmlException: Update failed. First exception on row 0 with id 001230000040bYQAAY; first error: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, updatemytest: maximum trigger depth exceeded Contact trigger event AfterUpdate for [003230000027P24] Account trigger event AfterUpdate for [001230000040bYQ] Contact trigger event AfterUpdate for [003230000027P24, 003230000027P29] Account trigger event AfterUpdate for [001230000040bYQ] Contact trigger event AfterUpdate for [003230000027P24, 003230000027P29] Account trigger event AfterUpdate for [001230000040bYQ] Contact trigger event AfterUpdate for [003230000027P24, 003230000027P29] Account trigger event AfterUpdate for [001230000040bYQ] Contact trigger event AfterUpdate for [003230000027P24, 003230000027P29] Account trigger event AfterUpdate for [001230000040bYQ] Contact trigger event AfterUpdate for [003230000027P24, 003230000027P29] Account trigger event AfterUpdate for [001230000040bYQ] Contact trigger event AfterUpdate for [003230000027P24, 003230000027P29] Account trigger event AfterUpdate for [001230000040bYQ] Contact trigger event AfterUpdate for [003230000027P24, 003230000027P29] Account trigger event AfterUpdate for [001230000040bYQ]: []: ()
Mahesh DMahesh D
Please check if you have any other triggers on Account or Contact, if yes, please provide me that information as well.

Also take the below latest code:

 
trigger PrimaryContactCount on Contact (after insert, after delete, after undelete,after update) {
    set<Id> accIds = new set<Id>();
    
    if(trigger.isinsert || trigger.isUpdate || trigger.Isundelete){
        for(Contact con: Trigger.new){
			if(Trigger.isInsert || Trigger.isUndelete || (con.Primary_Contact_del__c != Trigger.oldMap.get(con.Id).Primary_Contact_del__c))
				accIds.add(con.AccountId);            
        }
    }
    
    if(trigger.isUpdate || trigger.isDelete) {
        for(Contact con: Trigger.old){
            if(Trigger.isDelete || (con.Primary_Contact_del__c != Trigger.oldMap.get(con.Id).Primary_Contact_del__c))
				accIds.add(con.AccountId);
        }
    }    
    
    List<Account> accList = [select id, Number_of_Primary_Contacts__c, (Select Id, Primary_Contact_del__c from Contacts) from Account Where ID IN: accIds];
    
    for(Account acc : accList){
        system.debug('Contacts--->'+acc.contacts.size());
        acc.Number_of_Primary_Contacts__c = 0;
        for(Contact con : acc.Contacts) {
			if(con.Primary_Contact_del__c)
				acc.Number_of_Primary_Contacts__c++;
        }
    }
    update accList;
}

Regards,
Mahesh
This was selected as the best answer
S_BatmanS_Batman
Works great!!
Now just looking into how to deploy it into production :)!!

Thank you sooo much for your help!! Really appreciate it!
Mahesh DMahesh D
If you want to deploy it into Production, you have to write a Test Class to it and make sure that the code coverage is above 75%.

Below are some of the information about Test Classes:

1) http://amitsalesforce.blogspot.com/search/label/Test%20Class
2) http://amitsalesforce.blogspot.com/2015/06/best-practice-for-test-classes-sample.html

Please follow below salesforce Best Practice for Test Classes :-

1. Test class must start with @isTest annotation if class class version is more than 25
2. Test environment support @testVisible , @testSetUp as well
3. Unit test is to test particular piece of code working properly or not .
4. Unit test method takes no argument ,commit no data to database ,send no email ,flagged with testMethod keyword .
5. To deploy to production at-least 75% code coverage is required
6. System.debug statement are not counted as a part of apex code limit.
7. Test method and test classes are not counted as a part of code limit
9. We should not focus on the  percentage of code coverage ,we should make sure that every use case should covered including positive, negative,bulk and single record .
Single Action -To verify that the the single record produces the correct an expected result .
Bulk action -Any apex record trigger ,class or extension must be invoked for 1-200 records .
Positive behavior : Test every expected behavior occurs through every expected permutation , i,e user filled out every correctly data and not go past the limit .
Negative Testcase :-Not to add future date , Not to specify negative amount.
Restricted User :-Test whether a user with restricted access used in your code .
10. Test class should be annotated with @isTest .
11 . @isTest annotation with test method  is equivalent to testMethod keyword .
12. Test method should static and no void return type .
13. Test class and method default access is private ,no matter to add access specifier .
14. classes with @isTest annotation can't be a interface or enum .
15. Test method code can't be invoked by non test request .
16. Stating with salesforce API 28.0 test method can not reside inside non test classes .
17. @Testvisible annotation to make visible private methods inside test classes.
18. Test method can not be used to test web-service call out . Please use call out mock .
19. You can't  send email from test method.
20.User, profile, organization, AsyncApexjob, Corntrigger, RecordType, ApexClass, ApexComponent ,ApexPage we can access without (seeAllData=true) .
21. SeeAllData=true will not work for API 23 version eailer .
22. Accessing static resource test records in test class e,g List<Account> accList=Test.loadData(Account,SobjectType,'ResourceName').
23. Create TestFactory class with @isTest annotation to exclude from organization code size limit .
24. @testSetup to create test records once in a method  and use in every test method in the test class .
25. We can run unit test by using Salesforce Standard UI,Force.com IDE ,Console ,API.
26. Maximum number of test classes run per 24 hour of period is  not grater of 500 or 10 multiplication of test classes of your organization.
27. As apex runs in system mode so the permission and record sharing are not taken into account . So we need to use system.runAs to enforce record sharing .
28. System.runAs will not enforce user permission or field level permission .
29. Every test to runAs count against the total number of DML issued in the process.

Regards,
Mahesh
S_BatmanS_Batman
When I deployed it, my only issue is with some accounts where Primary Contacts are already marked, the field (Number of Contacts) has not populated.  Is there a way to update the accounts that already have primary contacts marked?
Mahesh DMahesh D
First check how many records exists then we can decide what we can do.

If you have large amount of records then we have to export and update them so that it will be calculated.
If you have less amount of records then we can touch them manually.

Regards,
Mahesh
S_BatmanS_Batman
Hi Mahesh, thank you so much for your help! My apologies for the late reply :)
S_BatmanS_Batman
Is there a way to modify this trigger so that it only counts Primary Contacts that do NOT have Do Not Email box checked?

 
Mahesh DMahesh D
Hi 

I replied to your other post:

https://developer.salesforce.com/forums/#!/feedtype=SINGLE_QUESTION_DETAIL&dc=Developer_Forums&criteria=ALLQUESTIONS&id=906F0000000DC11IAG

Also use the code which I provided in the above post. It considered all positive and negative scenarios.

Regards,
Mahesh
Mahesh DMahesh D
trigger PrimaryContactCount on Contact (after insert, after delete, after undelete,after update) {
    set<Id> accIds = new set<Id>();
    
    if(trigger.isinsert || trigger.isUpdate || trigger.Isundelete){
        for(Contact con: Trigger.new){
			if(Trigger.isInsert || Trigger.isUndelete || (con.Primary_Contact_del__c != Trigger.oldMap.get(con.Id).Primary_Contact_del__c))
				accIds.add(con.AccountId);            
        }
    }
    
    if(trigger.isUpdate || trigger.isDelete) {
        for(Contact con: Trigger.old){
            if(Trigger.isDelete || (con.Primary_Contact_del__c != Trigger.newMap.get(con.Id).Primary_Contact_del__c))
				accIds.add(con.AccountId);
        }
    }    
    
    List<Account> accList = [select id, Number_of_Primary_Contacts__c, (Select Id, Primary_Contact_del__c from Contacts) from Account Where ID IN: accIds];
    
    for(Account acc : accList){
        system.debug('Contacts--->'+acc.contacts.size());
        acc.Number_of_Primary_Contacts__c = 0;
        for(Contact con : acc.Contacts) {
			if(con.Primary_Contact_del__c)
				acc.Number_of_Primary_Contacts__c++;
        }
    }
    update accList;
}

I re-posted the same above trigger again here because I just changed one line of code.

if(Trigger.isDelete || (con.Primary_Contact_del__c != Trigger.oldMap.get(con.Id).Primary_Contact_del__c))

to

if(Trigger.isDelete || (con.Primary_Contact_del__c != Trigger.newMap.get(con.Id).Primary_Contact_del__c))


Regards,
Mahesh