0

I need to group data in 3 layers but Im currently stuck at level 2. I need to Group data Layer 1 (accounts), Layer 2 (Lead Source - field on contact), Layer 3 (Contact Name). Grouping at 1st and 2nd level works fine, but at 3rd level Im getting repeated values. Im not sure if this is happening because of repeat tag on VF or my controller re-iteration. Any thoughts? (For example, please see screenshot below. Contact 'Sean Forbes' keeps on repeating)

public class GroupingExampleController 
{
    private List<Account> allAccs {get; set;}
    public String groupFieldName {get; set;}
    public List<SelectOption> groupOptions {get; set;}
    public List<String> selectedAccounts{get;set;}
    public AccountWrapper[] accounts { get; set; }
    public GroupingExampleController()
    {

        setupGrouping();
    }


    public void setupGrouping()
    {

        Map<Id, Account> accountMap = new Map<Id, Account>();
        Map<Id, Contact> contactMap = new Map<Id, Contact>();
        Contact [] contacts;
        Map<Id, Map<String, Contact[]>> contactsByAccountBySource =
            new Map<Id, Map<String, Contact[]>>();
        for(Contact record: [SELECT Account.Name, Name, LeadSource FROM Contact]) {
            accountMap.put(record.Account.Id, record.Account);
            contactMap.put(record.Id, record);
            Map<String, Contact[]> contactsBySource = contactsByAccountBySource.get(record.Account.Id);
            if(contactsBySource == null) {
                contactsByAccountBySource.put(record.Account.id, contactsBySource = new Map<String, Contact[]>());
            }
            contacts = contactsBySource.get(record.LeadSource);
            if(contacts == null) {
                contactsBySource.put(record.LeadSource, contacts = new Contact[0]);
            }
            contacts.add(record);
            system.debug('contacts>>' + contacts);

        }

        accounts = new AccountWrapper[0];
        AccountWrapper thisAccount;
        for(Id accountId: contactsByAccountBySource.keySet()) {
          accounts.add(thisAccount = new AccountWrapper(accounts.get(accountId)));
            for(String source: contactsByAccountBySource.get(accountId).keySet()) {
                thisAccount.sources.add(
                    new SourceWrapper(source, contactsByAccountBySource.get(accountId).get(source)));
            }
        }        


    }

    public class AccountWrapper {


        public Account record { get; set; }
        public SourceWrapper[] sources { get; set; }
        public AccountWrapper(Account record) {
            this.record = record;
            sources = new SourceWrapper[0];
        }
    }

    public class SourceWrapper {
        public String source { get; set; }
        public Contact[] contacts { get; set; }
        public SourceWrapper(String source, Contact[] contacts) {
            this.source = source;
            this.contacts = contacts;
        }
    }


}
  • 1
    Correct me if I'm wrong, but wouldn't you want it to be Account > Lead Source > Contacts? Presumably, each contact has only one lead source... – sfdcfox Apr 26 '18 at 19:48
  • Yes, that is correct. That is how I need it. Account > Lead Source > Contacts – Wendy Apr 26 '18 at 20:00
3

You have a query in a loop, resulting in all contacts in older accounts being applied to later accounts. Notice how all the contacts for United Oil & Gas Corp also appear in GenePoint, etc.

Instead, consider just querying a flat list of contacts and mapping them together. Perhaps something like this:

Map<Id, Map<String, Contact[]>> contactsByAccountBySource =
  new Map<Id, Map<String, Contact[]>>();
Map<Id, Account> accountMap = new Map<Id, Account>();
Map<Id, Contact> contactMap = new Map<Id, Contact>();
for(Contact record: [SELECT Account.Name, Name, LeadSource FROM Contact]) {
  accountMap.put(record.Account.Id, record.Account);
  contactMap.put(record.Id, record);
  Map<String, Contact[]> contactsBySource = contactsByAccountBySource.get(record.Account.Id);
  if(contactsBySource == null) {
    contactsByAccountBySource.put(record.Account.id, contactsBySource = new Map<String, Contact[]>());
  }
  Contact[] contacts = contactsBySource.get(record.LeadSource);
  if(contacts == null) {
    contactsBySource.put(record.LeadSource, contacts = new Contact[0]);
  }
  contacts.add(record);
}

Edit:

The actual Wrapper code would look something like this:

public AccountWrapper[] accounts { get; set; }
public class AccountWrapper {
  public Account record { get; set; }
  public SourceWrapper[] sources { get; set; }
  public AccountWrapper(Account record) {
    this.record = record;
    sources = new SourceWrapper[0];
  }
}
public class SourceWrapper {
  public String source { get; set; }
  public Contact[] contacts { get; set; }
  public SourceWrapper(String source, Contact[] contacts) {
    this.source = source;
    this.contacts = contacts;
  }
}

After populating the map (from above):

accounts = new AccountWrapper[0];
AccountWrapper thisAccount;
for(Id accountId: contactsByAccountBySource.keySet()) {
  accounts.add(thisAccount = new AccountWrapper(accounts.get(accountId)));
  for(String source: contactsByAccountBySource.get(accountId).keySet()) {
    thisAccount.sources.add(
      new SourceWrapper(source, contactsByAccountBySource.get(accountId).get(source)));
  }
}

After that, you just need to change your Visualforce page to use this new wrapper list.

  • I made some changes to my code and now I`m able to get grouping at level 2 (Lead Source) correct but now Im getting duplicate values at level 3 grouping (at Contact Name). I have updated question with controller, VF and screenshot. Could you please check and suggest? – Wendy Apr 27 '18 at 11:45
  • @Wendy It looks like you didn't read my answer at all, basically. You really do want a three-level collection like I demonstrated here. Until you start with something like that, you're going to continue to have problems. – sfdcfox Apr 27 '18 at 12:12
  • Sorry for diverting away from what you had already suggested. I did try to save the code that you provided but I kept on getting an error at last line 'contactsBySource.add(record);' Error: Method does not exist or incorrect signature: void add(Contact) from the type Map<String,List<Contact>> . Do you know why? – Wendy Apr 27 '18 at 12:31
  • @Wendy yes, I meant to add the contact to the list, not the map. I've made the adjustment in the code. – sfdcfox Apr 27 '18 at 12:40
  • Thank you , it helped. I was able to add this to the wrapper class and get rid of duplicates. However, if you see the screenshot, grouping doesn't quite work like a standard report. "Account Name" and "Lead Source" is repeating twice. I want this to group like a standard report where "Account Name" and "Lead Source" will appear only once and then contacts under each "Lead Source" (in screenshot above, "Edge communication" and "Trade Show" should show up only once and 2 contacts should show under Lead Source 'Trade Show'). Could you please suggest ? Thanks again! – Wendy Apr 29 '18 at 17:42
2

You have multiple problems in your code, but your biggest mistake is placing a query inside for loop. Following query is inside of your loop.

List < Contact> ConList = [SELECT ID, FirstName,LastName,Accountid from Contact WHERE AccountID IN :mysets];

On the other hand for layers, your set mySets is defined out of for loop so it keeps adding every account to your list in your inner account loop.

            For (Account A : gr.accs )  {
            groupedMap2.put(a.id,a);
            mysets.add(A.id);
        }

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.