Salesforce Flow- Query records using List of Ids
Tags: salesforce, apex, flowbuilder
Published: 2022-01-11 00:22:06

## Intro

Fetching Records using a collection of Ids in Flow Builder should be a simple job, but somehow SF is still missing this ability. This limits the enforcing of best practices since sometimes we might have no choice but use GET elements inside a flow-loop.

The idea with this article is to show a simple implementation of an Apex Action to handle this limitation.

## Basic Implementation

I'm assuming that the reader is already familiar with Invocable Methods/Variables and how to call Apex from a Flow.

That being said, we have this apex class that can solve our issue. Let's assume we want to fetch Contact records.

```java
public with sharing class FlowQueryHelper{


    @InvocableMethod(label = 'GET Contacts where Ids in Collection')
    public static List<Output> getContactsInCollection(List<Input> params){
        String ids = params[0].ids;
        List<Contact> contacts = [
                SELECT Id, Name 
                FROM Contact 
                WHERE Id IN :ids];
        
        Output output = new Output();
        output.contacts = contacts;
        
        return new List<Output>{ output };
    }  

    public class Input{
        @InvocableVariable(label = 'Text collection variable (Ids only)' required = true)
        public List<String> ids;
    }

    public class Output{
        @InvocableVariable(label = 'Contacts Collection Variable')
        public List<Contact> contacts;
    }
}
```

That works like a charm, but we can make it a little bit more generic in case we want to reutilize this class with other Object types ~~and you probably will~~.

## More Generic

Our class must be able to run for any custom or standard sObject. This means that it will be unaware of the sObject **API Name** and **Fields** in order to run for any collection of Ids we pass to it.

### API Name
We can get the API Name directly from the Id by using describe methods. More details [here](https://developer.salesforce.com/docs/atlas.en-us.236.0.apexref.meta/apexref/apex_methods_system_id.htm).
```java
    private static String getObjectName(String objId){
        return ((Id)objId).getSObjectType().getDescribe().getName();
    }
```

### Fields
Again we're making use of the well known Schema class and describe methods. More details [here](https://developer.salesforce.com/docs/atlas.en-us.236.0.apexref.meta/apexref/apex_class_Schema_SObjectType.htm).

```java
    private static String getObjectFields(String objectName) {
        List<String> fields = new List<String>(Schema.getGlobalDescribe().get(objectName).getDescribe().fields.getMap().keySet());
        return String.join(fields, ',');
    }    
```

### SOQL Query
And now we have the SOQL statement. We'll be using Dynamic SOQL to fetch our records. More details [here](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_dynamic_soql.htm).

```java
Database.query('SELECT ' + String.escapeSingleQuotes(objectFields) + ' FROM ' + String.escapeSingleQuotes(objectName) + ' WHERE Id IN :ids')
```
## Final Version
```java
public with sharing class FlowQueryHelper{


    @InvocableMethod(label = 'GET Records where Ids in Collection')
    public static List<Output> getRecordsInCollection(List<Input> params){

        String objectName = getObjectName(params[0].ids[0]);
        String objectFields = getObjectFields(objectName);
        List<String> ids = params[0].ids;

        List<sObject> records = Database.query('SELECT ' + String.escapeSingleQuotes(objectFields) + ' FROM ' + String.escapeSingleQuotes(objectName) + ' WHERE Id IN :ids');
        
        Output output = new Output();
        output.records = records;
        
        return new List<Output>{ output };
    }

    private static String getObjectName(String objId){
        return ((Id)objId).getSObjectType().getDescribe().getName();
    }

    private static String getObjectFields(String objectName) {
        List<String> fields = new List<String>(Schema.getGlobalDescribe().get(objectName).getDescribe().fields.getMap().keySet());
        return String.join(fields, ',');
    }

    public class Input{
        @InvocableVariable(label = 'Text Collection variable (Ids only)' required = true)
        public List<String> ids;
    }

    public class Output{
        @InvocableVariable(label = 'Record Collection Variable')
        public List<sObject> records;
    }
}
```
And that's it, folks. Screenshot below as example for Account object.


![Screenshot from Flow Builder screen showing new Custom Apex action with generic inputs and output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/62po9zv5arp1x1u8wy0v.png)
 
## Conclusion

This will do the trick at least until SF comes up with a more final/solid solution. It's important to notice that when calling this action you must choose the Object type for action output and assign it to a variable of the same type.