Skip to main content
New tool CRON Expression Builder — preview next run times before you schedule Apex. Open the builder →
Apex

SOQL NOT IN and NOT EQUAL (!=): Exclusion Syntax and Null Rows

SOQL excludes records three ways: NOT IN for lists and subqueries, != for one value, and (NOT field LIKE) for patterns. Unlike SQL, all three keep rows where the field is null.

The short answer

Use field NOT IN ('A', 'B') to exclude a list, field != 'A' to exclude one value, and (NOT field LIKE '%a%') to exclude a pattern. Unlike SQL, all three return rows where the field is null. Add AND field != null when you want only rows that have a value.

Key takeaways NOT IN takes a list, an Apex collection or a subquery. != and <> take a single value and return identical results. SOQL keeps null rows in every exclusion. Industry != 'Banking' also returns accounts with no Industry, the opposite of SQL. Exclude nulls with != null. SOQL has no IS NULL or IS NOT NULL, and both are parse errors. Pattern exclusion is written (NOT Name LIKE '%x%'). Name NOT LIKE '%x%' does not compile. Negative filters are not selective, so pair them with an indexed positive filter on large objects.

SOQL gives you three ways to exclude records: NOT IN for a list or a subquery, != (or <>) for a single value, and (NOT field LIKE ...) for a pattern. The detail that catches people coming from SQL is null handling. In SOQL, every one of these returns rows where the field is null.

Every query on this page was run against a live org with 8 accounts, 4 of them with a blank Industry. The counts below are what came back.

NOT IN: exclude a list of values

// Literal list
List<Account> accounts = [
  SELECT Id, Name FROM Account
  WHERE Industry NOT IN ('Banking', 'Technology')
];

// Apex collection bind: the form you will write most often
Set<Id> excludedIds = new Set<Id>{ '001000000000001AAA', '001000000000002AAA' };
List<Contact> contacts = [
  SELECT Id FROM Contact WHERE AccountId NOT IN :excludedIds
];

The collection type has to match the field: Set<Id> or List<Id> for ID and lookup fields, Set<String> or List<String> for text and picklists.

In the test org, one account was in Technology and none in Banking. Industry NOT IN ('Banking', 'Technology') returned 7 of 8 accounts. That includes the 4 accounts with no Industry at all.

NOT IN with a subquery (anti-join)

To exclude records based on another object, put a subquery inside NOT IN:

// Accounts that have no contacts
List<Account> lonely = [
  SELECT Id, Name FROM Account
  WHERE Id NOT IN (SELECT AccountId FROM Contact)
];

Salesforce calls this an anti-join, and it has a few rules:

  • The outer field must be an ID or a reference (lookup or master-detail) field.
  • The subquery selects exactly one field, and it must be an ID or a reference field.
  • Nulls are ignored on both sides. Contacts with no AccountId do not break the query the way a null does in SQL's NOT IN, and outer rows with a null value are returned.

That last rule is easy to test. The org had 11 contacts, 2 with no account. SELECT COUNT() FROM Contact WHERE AccountId NOT IN (SELECT Id FROM Account WHERE Name LIKE 'zzqq%') returned all 11, including the 2 orphans.

!= and <>: exclude one value

List<Account> accounts = [
  SELECT Id, Name FROM Account WHERE Industry != 'Banking'
];

<> is accepted too and returns the same rows. I use != because it matches Apex.

No account in the org was in Banking, and Industry != 'Banking' returned all 8, including the 4 with a blank Industry. SQL would have returned only the 4 that had a value.

NOT LIKE: exclude a pattern

SOQL has no NOT LIKE operator. Negate the whole LIKE condition and wrap it in parentheses:

// Accounts whose website does not contain "test"
List<Account> accounts = [
  SELECT Id, Name FROM Account
  WHERE (NOT Website LIKE '%test%')
];

WHERE Website NOT LIKE '%test%' is a parse error. Like the other two exclusions, the negated LIKE keeps null rows: 6 of the 8 test accounts had no website, and all 8 came back. Wildcards and escaping are covered in the SOQL LIKE and NOT LIKE guide.

Nulls: SOQL works the opposite way to SQL

In SQL, NULL never matches anything, so Industry <> 'Banking' silently drops blank rows. SOQL treats a blank field as a value that simply is not 'Banking', so the row stays in.

Filter Returns rows where the field is null?
Industry != 'Banking' Yes
Industry NOT IN ('Banking', 'Technology') Yes
(NOT Website LIKE '%test%') Yes
Id NOT IN (SELECT AccountId FROM Contact) Yes (outer nulls kept, inner nulls ignored)
Industry = 'Banking' or Industry IN (...) No

When you want only rows that have a value, say so:

List<Account> accounts = [
  SELECT Id, Name FROM Account
  WHERE Industry != 'Banking' AND Industry != null
];

Use = null and != null. SOQL rejects IS NULL and IS NOT NULL with a parse error, which is the second-most common mistake I see from SQL developers after expecting nulls to drop out.

Performance on large objects

The query optimizer looks for a selective filter it can drive from an index. Negative filters (!=, NOT IN, negated LIKE) are not selective, so on their own they push the optimizer toward a full scan. On an object with millions of rows that can mean a slow query or a non-selective query error in a trigger.

Two fixes work well:

  1. Add a positive, indexed filter alongside the exclusion, such as a CreatedDate range, a status field, or a lookup to a specific parent. The optimizer narrows the set with the index first.
  2. Split the work. Query the IDs you want to exclude, then filter the main result in Apex with Set.contains(). It is more code, but you can see the row count at each step.
Set<Id> staleAccountIds = new Set<Id>();
for (Contact c : [SELECT AccountId FROM Contact WHERE LastActivityDate < LAST_N_DAYS:90]) {
  staleAccountIds.add(c.AccountId);
}
List<Account> active = new List<Account>();
for (Account a : [SELECT Id FROM Account WHERE CreatedDate = LAST_N_DAYS:365]) {
  if (!staleAccountIds.contains(a.Id)) active.add(a);
}

Quick reference

You want to... Write
Exclude one value field != 'X'
Exclude several values field NOT IN ('X', 'Y')
Exclude an Apex collection field NOT IN :mySet
Exclude records found by another query Id NOT IN (SELECT ParentId__c FROM Child__c)
Exclude a pattern (NOT field LIKE '%X%')
Drop blank values too add AND field != null
Find only blank values field = null

Common mistakes

  • Expecting SQL null behavior. Exclusions in SOQL keep blank rows. Add AND field != null when that matters.
  • Writing IS NOT NULL. It does not parse. Use != null.
  • Writing field NOT LIKE. It does not parse. Use (NOT field LIKE ...).
  • Reaching for SELECT DISTINCT. SOQL has no DISTINCT. Use GROUP BY field to get unique values.
  • Running an unfiltered NOT IN on a large object inside a trigger. Pair it with a selective filter.
Free tool SOQL Query Builder Build SOQL queries visually, browse objects and fields from your live Salesforce org.

Frequently asked questions

How do you write a SOQL NOT IN query?

Put the values in parentheses after NOT IN: SELECT Id FROM Account WHERE Industry NOT IN ('Banking', 'Insurance'). In Apex you can bind a collection instead: WHERE Id NOT IN :excludedIds. You can also exclude against a subquery: WHERE Id NOT IN (SELECT AccountId FROM Contact).

What is the not equal operator in SOQL?

SOQL accepts both != and <>, and they return the same rows. SELECT Id FROM Account WHERE Industry != 'Banking' returns every account whose Industry is not Banking, including accounts where Industry is blank.

Does SOQL NOT IN return null values?

Yes. This is where SOQL differs from SQL. WHERE Industry NOT IN ('Banking') and WHERE Industry != 'Banking' both return records where Industry is null. If you want only records that have a value, add AND Industry != null.

How do I exclude null values in SOQL?

Use != null, for example WHERE Email != null. SOQL does not support IS NULL or IS NOT NULL; both cause a parse error. Use = null to find blank values and != null to exclude them.

What's the difference between NOT IN and != in SOQL?

!= compares against one value. NOT IN compares against a list of values, an Apex collection, or a subquery. Both treat null the same way: a null field counts as not matching, so the row is returned.

Can SOQL NOT IN use a subquery?

Yes, as an anti-join: SELECT Id FROM Account WHERE Id NOT IN (SELECT AccountId FROM Contact). The outer field must be an ID or a reference field, and the subquery must select a single ID or reference field. Rows whose outer field is null are kept.

Why is my SOQL NOT IN query slow?

Negative filters such as NOT IN and != are not selective, so the query optimizer usually cannot use an index for them. Add a selective positive filter, such as a date range or an indexed status field, so the optimizer has a smaller set to work through.

Newsletter

One email every Tuesday

New guides, tool updates, and the release-note changes that break things.

No spam. Unsubscribe in one click.

Comments

Loading comments...

Leave a Comment