Improve Performance of SOQL Queries using a Custom Index
SOQL queries must be selective, particularly for queries inside triggers for the best performance. To avoid long execution times, non-selective SOQL queries may be terminated by the system.
Selective SOQL Query Criteria
A query is selective when one of the query filters is on an indexed field and the query filter reduces the resulting number of rows below a system-defined threshold. The performance of the SOQL query improves when two or more filters used in the WHERE clause meet the mentioned conditions.
Custom Index Considerations for Selective SOQL Queries
The following fields are indexed by default:
- Primary keys (ID, Name, and Owner fields).
- Foreign keys (lookup or master-detail relationship fields).
- Audit dates (such as SystemModStamp).
- Custom fields marked as External ID or Unique.
A Custom Index can’t be created on these types of fields:
- Multi-select Picklists.
- Currency fields in a Multicurrency Organization.
- Long text fields.
- Binary fields (fields of type blob, file, or encrypted text).
Examples of Selective SOQL Queries
To better understand whether a query on a large object is selective or not, let’s analyze some queries. For these queries, we will assume there are more than 200,000 records (including soft-deleted records, that is, deleted records that are still in the Recycle Bin) for the Account sObject.
Query 1:
SELECT Id FROM Account WHERE Id IN (<list of account IDs>)
The WHERE clause is on an indexed field (Id). If SELECT COUNT() FROM Account WHERE Id IN (<list of account IDs>) returns fewer records than the selectivity threshold, the index on Id is used. This will typically be the case since the list of IDs only contains a small amount of records.
Query 2:
SELECT Id FROM Account WHERE Name != ''
Since Account is a large object even though Name is indexed (primary key), this filter returns most of the records, making the query non-selective.
Query 3:
SELECT Id FROM Account WHERE Name != '' AND CustomField__c = 'ValueA'
Here we have to see if each filter, when considered individually, is selective. As we saw in the previous example the first filter isn’t selective. So let’s focus on the second one. If the count of records returned by SELECT COUNT() FROM Account WHERE CustomField__c = ‘ValueA’ is lower than the selectivity threshold, and CustomField__c is indexed, the query is selective.
Query 4:
SELECT Id FROM Account WHERE FormulaField__c = 'ValueA'
The following rules have to be true in order to index a Formula Field:
- The formula contains fields from a single object only (not relationship fields).
- The formula field doesn’t reference any non-deterministic functions (e.g. SYSDATE).
- The formula field doesn’t reference any non-supported fields for including in indexes. This list isn’t documented anywhere specifically (there are lots of special cases), but in Spring 12(176), createdById was non-supported, but in Summer 12 (178), it is supported. Same story for CreatedDate.
- The formula field doesn’t contain references to Primary Keys (e.g Id)
- The formula field does not use TEXT(<picklist-field>) function
- If the formula references any Lookup fields, the field must not have the option “What to do if the lookup record is deleted?” set to “Clear the value of this field.”