Exception: QUERY_TIMEOUT Your query request was running for too long

Take a look at Working with Very Large SOQL Queries. Querying for field != null will hurt your performance.

  • Typically, a custom index isn’t used in these cases.

    • The queried values exceed the system-defined threshold.
    • The filter operator is a negative operator such as NOT EQUAL TO (or !=), NOT CONTAINS, and NOT STARTS WITH.
    • The CONTAINS operator is used in the filter, and the number of rows to be scanned exceeds 333,333. The CONTAINS operator requires a full scan of the index. This threshold is subject to change.
    • You’re comparing with an empty value (Name != '').

I don't have 25M records to test against, but I believe you might have better luck creating a formula along the lines of Is_Missing_Account__c:

AND(ISNULL(AccountId), ISNULL(accountrefId__c))

If the above does not work, you can use an Apex Trigger, a Workflow Rule, or Process Builder to sync an analogous checkbox. You will need to run a back-fill batch, but once you do that, you can just filter on this custom checkbox instead and it should speed things up.

Another option is to populate an Account_Count__c (formula or otherwise) number field with 0, 1, or 2 depending on these parent relationships. Then filter Account_Count__c = 2.


Text field indexes do not index "null" values. In other words, the database can't reduce the cost for a query that filters for a null value in a text field. If most of your table has values, but a few do not, this can easily cause the query timeout exception you're receiving. Odds are, you'll probably need to resort to more drastic measures, such as perhaps manually performing a full table scan:

public class MyBatch implements Database.Batchable<SObject> {
    public Database.QueryLocator start(Database.BatchableContext context) {
        return Database.getQueryLocator([SELECT Id FROM Contact]);
    }
    public void execute(Database.BatchableContext context, Contact[] scope) {
        Contact[] records = [SELECT Id, AccountId, AccountRefId__c FROM Contact 
                             WHERE Id IN :scope AND 
                                   (AccountId = NULL OR AccountRefId__c = NULL)];
        // Now process these records.
    }
    public void finish(Database.BatchableContext context) {
    }
}

This will work for up to 50,000,000 rows, after which point you'd have to also implement batch chaining with large limits, etc.