Salesforce Dictionary - Free Salesforce GlossarySalesforce Dictionary
All errors
SOQL

System.QueryException: Non-selective query against large object type

A query in trigger context (or a similar latency-sensitive context) is missing an indexed filter on a high-volume object. The platform refuses non-selective queries on big tables to protect everyone. Add a filter on Id, Owner, or another indexed field.

Also seen asNon-selective query·non-selective query against large object·selective query trigger

On objects with more than ~200,000 rows, the platform requires queries to use a selective filter — a WHERE clause that narrows by an indexed field. Without one, the query forces a table scan, which on a large table takes minutes and grinds the org's database.

The platform refuses these queries with this error in trigger context, where milliseconds matter.

What counts as selective

A filter is selective if at least one filter clause uses an indexed field with the right operator:

Indexed fieldsSelective operators
Id=, IN, NOT IN
Name=, IN, LIKE 'foo%' (leading text)
External ID fields=, IN
Foreign keys (lookup fields)=, IN
OwnerId=, IN
RecordTypeId=, IN
Custom-indexed fieldsdepends on the index type

!=, LIKE '%foo' (leading wildcard), and OR clauses with non-indexed fields are NOT selective — they force a full scan.

The shape of the bug

trigger AccountTrigger on Account (after update) {
    // Non-selective on a 500k-Account org
    List<Contact> kids = [SELECT Id FROM Contact WHERE Status__c = 'Active'];
}

Status__c (custom, non-indexed) is the only filter. On a big Contact table, this errors.

Fix 1: filter by an indexed field

Set<Id> accountIds = new Map<Id, Account>(Trigger.new).keySet();
List<Contact> kids = [
    SELECT Id FROM Contact
    WHERE AccountId IN :accountIds       -- AccountId is indexed (FK)
      AND Status__c = 'Active'
];

The platform picks the best available index to evaluate the query. With AccountId IN :small_set, it scans only Contacts under those Accounts — fast.

Fix 2: add a custom index

If you frequently filter by a non-indexed custom field, ask Salesforce Support to add an index. Setup → Object Manager → Field → Edit, and look for "External ID" or contact support to add a 1-tier index. Don't index every field — too many indexes slow down DML.

Fix 3: use a different storage strategy

If you're filtering by a status that's true for a small fraction of rows, denormalize:

  • Add a Boolean field Is_Active__c
  • Populate it when status is "Active"
  • Index it (or use it as a filter combined with another indexed field)

This pattern works because indexes on Boolean fields with skewed distribution can be selective.

A subtle gotcha: IN with empty set

Set<Id> ids = new Set<Id>{};   // empty
List<Contact> kids = [SELECT Id FROM Contact WHERE AccountId IN :ids];

The platform may treat this as "no filter at all" and throw non-selective. Guard:

if (ids.isEmpty()) return new List<Contact>();
List<Contact> kids = [SELECT Id FROM Contact WHERE AccountId IN :ids];

When the query is selective but still slow

Selective != fast. A query selecting 100,000 rows under one indexed filter is selective but heavy. Use LIMIT to bound the result, or move to Batch Apex.

Diagnose with Query Plan

Setup → Developer Console → Query Editor → click the query → Query Plan. The plan shows which index the platform used, the cost, and the cardinality estimate. If the cost is >2 (out of a 0-1 ideal), your query is suboptimal. Add or change indexes accordingly.

Related dictionary terms