124

I have a bit of code where I've written a SOQL query that returns a list of SObject records, however I need a Set<Id> structure to pass to another method that I don't have control of (and thus can't change the method signature of).

How can I turn my returned List<SObject> into a Set<Id>? Is the best option just a for loop?

1
  • 6
    don't go with the for loop, use a map constr... oh, nevermind :P Feb 26, 2013 at 1:24

3 Answers 3

202

This trick only works on the Id field, but you can use the Map constructor that accepts an SObject list to do this without consuming a script statement for each element in the List.

For example:

List<SObject> results = Database.query(someSOQL);
Set<Id> resultIds = (new Map<Id,SObject>(results)).keySet();

What this second line does is create a new Map<Id,SObject> from the results list using a special constructor, and then take the map's set of keys via the keySet() method. Then the map falls out of scope and it's heap space is released, leaving you with a very governor-efficient set.

8
  • 4
    greased lightning! Feb 26, 2013 at 1:23
  • 22
    One thing to watch out for if you need to modify the set... You'll get a System.FinalException: Collection is read-only error if you try to add to the resultIds. To get around that you need to create a new set, e.g., Set<Id> resultIds = new Set<Id> (new Map<Id,SObject>(results).keySet()); Feb 26, 2013 at 1:27
  • 10
    or clone the Set Set<Id> resultIds = (new Map<Id,SObject>(results)).keySet().clone(); Feb 26, 2013 at 1:37
  • 3
    Worth noting is that if all that you are doing is using the Set<Id> in a bind expression then you can don't need to create the Set and can actually just use the List<sObject>. For example: List<Opportunity> opps = [Select Id,Name From Opportunity Where AccountId = :[Select Id, Name From Account Limit 100]]; works. Feb 26, 2013 at 16:05
  • 2
    Any workaround for a field different from the Id one? Jul 17, 2014 at 21:46
36

If you are not using Dynamic SOQL, you can skinny this down to a one-liner like this...

Set<Id> ids = (new Map<Id, Lead>([SELECT Id FROM Lead])).keySet();

This is how you would do it with Dynamic SOQL but you must cast...

Set<Id> ids = (new Map<Id, Lead>((List<Lead>)Database.query(query))).keySet();

This unresolved bug is why you must cast the returned List<SObject>

2
  • 3
    Seriously, that issue is still unresolved? I miss Rich Unger so much... Feb 26, 2013 at 16:02
  • 3
    You don't need to cast to get the keySet. You don't care about values at all. Just use new Map<Id, SObject>Database.query(/*query*/).keySet(). You don't need to wrap the Map in parentheses either.
    – Adrian Larson
    Sep 7, 2016 at 23:14
11

There is an issue with the accepted methodology that can come up if you are not sure each element is in the list only once. It won't matter if you are sure the input is a SOQL result, but if you are writing a utility where you need to get the desired Set<Id> without knowing the source of the List<SObject>, it may be necessary to adopt a different approach.

The bug can be demonstrated fairly simply, though I don't have a less contrived use case handy.

Lead record = [SELECT Id FROM Lead LIMIT 1];
List<Lead> recordsWithDuplicate = new List<Lead> { record, record };
Set<Id> recordIds = new Map<Id, Lead>(recordsWithDuplicate);

The above snippet results in:

System.ListException: Row with duplicate Id at index: 1

To avoid that, you can use putAll, though it may actually be slower than just using a for loop.

Map<Id, Lead> recordsMap = new Map<Id, Lead>();
recordsMap.putAll(recordsWithDuplicate);
Set<Id> recordIds = recordsMap.keySet();

One more note, I take issue with the contention that the results of Database.query must be cast to get the Set<Id>. The following compiles and executes without issue:

public static Set<Id> getRecordIds(SObjectType token)
{
    return new Map<Id, SObject>(Database.query('SELECT Id FROM ' + token)).keySet();
}

You must log in to answer this question.

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