0

I am trying find an optimal solution where you can get top parent information when you pass the child Id. This code is working fine, but this code will break when when we have more than 100 records in the parent-child hierarchy.

Is there any way to optimize this code?

    public static CustomObject__c findTopParent(Id childId) {
        CustomObject__c custObj = [select Id, ParentId__c from CustomObject__c where Id =:childId];
        if(custObj.ParentId__c != null) {
           findTopParent(custObj.ParentId__c);
        }
        return custObj;
    }
1
  • A slight change to the data structure might go a long way here (may not be feasible in your setup): adding a field that holds a given value for all records in a given hierarchy. Then you could just query by that field and where ParentId__c = null. Sorting this out for existing data will be a pain, but if this is new code maybe you don't have said data yet.
    – Matt Lacey
    Commented Oct 6, 2021 at 23:08

3 Answers 3

2

The first thing to ask yourself is "am I ever going to have a record hierarchy that has more than 100 levels?" If the answer is no, then it doesn't make much sense to spend time trying to work out an alternative approach. If this code is called from a trigger context though, effort spent in reducing queries is pretty easy to justify.

Assuming that you do have a valid reason to seek an alternative approach...

I ran into a similar problem when developing an IPAM (IP Address Management) solution for my org.

Given a subnet of IP addresses like 10.0.32.128/25 (the CIDR (Classless Inter-Domain Routing) representation for the range of addresses 10.0.32.128 - 10.0.32.255), what is the largest subnet that it belongs to? (10.0.0.0/8)

Each subnet had 1 record, with the prefix length decreasing by 1 for each level of parent. The parent record for 10.0.32.128/25 would be 10.0.32.0/24. The grandparent would be 10.0.32.0/23, and so on.

Instead of querying in a loop to get to the top-level record, the approach I took was to store the top-level record id in every record. That approach works well for me since my top-level records are effectively set in stone. It still leaves us with the problem of propagating that top-level record Id down to the children though.

My solution to that problem was to query all of my records (with the parent relationship field) and then build the object hierarchy in memory. Something like

// We can use a Map to help us recursively traverse a record hierarchy
// This is a lot more friendly on Heap space than storing entire records in a wrapper class
Map<Id, List<Id>> parentIdToChildrenIds = new Map<Id, List<Id>>();

// We should also keep track of which Ids are our top-level Ids (gotta start the
//   recursion we'll get to later at the top)
List<Id> topLevelRecordIds = new List<Id>();

// Build the hierarchy in our map (be mindful of query selectivity and the 50k row limit)
for(IP_Address__c ipAddr :[SELECT Id, Parent_IP_Block__c FROM IP_Address__c]){
    if(!parentIdToChildrenIds.containsKey(ipAddr.Parent_IP_Block__c)){
        parentIdToChildrenIds.put(ipAddr.Parent_IP_Block__c, new List<Id>());
    }

    parentIdToChildrenIds.get(ipAddr.Parent_IP_Block__c).add(ipAddr.Id);

    // If there's no parent, that makes it a top-level record
    if(String.isBlank(ipAddr.Parent_IP_Block__c)){
        topLevelRecordIds.add(ipAddr.Id);
    }
}
// For ease and my comfort, I'm writing this as a recursive helper method that would
//   live in some class.
public Map<Id, IP_Address__c> propagateToChildren(Id topLevelId, Id currentRecordId, Map<Id, List<Id>> parentIdToChildrenIds){
    // We always want to add the current Id to the list of things we return
    Map<Id, IP_Address__c> ipAddrToUpdate = new Map<Id, IP_Address__c>{
        currentRecordId => new IP_Address__c(Id = currentRecordId, Top_Level_IP_Block__c = topLevelId)
    };

    // Base case: current record has no children
    // Just return what we've created so far
    if(!parentIdToChildrenIds.containsKey(currentRecordId)){
        return ipAddrToUpdate;
    }

    // Recursive case: current record has children
    // Recursively call the method, and add the results to our current results
    for(Id childId :parentIdToChildrenIds.get(currentRecordId)){
        ipAddrToUpdate.putAll(propagateToChildren(topLevelId, childId, parentIdToChildrenIds);
    }

    return ipAddrToUpdate;
}

This approach also needs to keep the governor limit on DML rows in mind. If you have a large enough dataset, it should be fairly easy to adapt this to be Batchable or Queueable.

2

Disclaimer: I can't write a complete and tested code at the moment code but I can hint you to the right direction. Let me know if you have questions.

You can utilize the 5 depth relationship that SOQL provides and do something like below. This should save you a lot of query calls and bump up your limit to ~500 depending on how you implement it

CustomObject__c record = [SELECT Parent__r.Parent__r.Parent__r.Parent__r.Parent__c FROM CustomObject__c WHERE Id = :myRecordId];

Id topMostParentId = null;
Boolean foundTopMost = false;
if(record.Parent__c != null) {
    topMostParentId = record.Parent__c;
    if(topMostParentId != null && record.Parent__r.Parent__c != null) {
        topMostParentId = record.Parent__r.Parent__c;
        // Keep drilling until you find the top most
    } else {
        foundTopMost = true;
    }
}

if(foundTopMost) {
    return topMostParentId;
} else {
    // Continue recursion
}

You can even make the drilling part another recursive function for better readability instead if nested if-else conditions

0

I assume, you are just trying to query all nodes in an hierarchy (depth greater than 100).

You need an hierarchy identifier for all records in that hierarchy.

Store the top level parent id in all records in the hierarchy (assume field name in top_parent__c). Then you just have to query all records with top_parent__c = currentrecord.top_parent__c.

This can be implemented using formula field.

top_parent__c = IF( ISBLANK(ParentId__c), Id, ParentId__r.top_parent__c )

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .