Overview
Thenot-in-subquery rule flags the use of NOT IN (SELECT ...) because it produces incorrect results when the subquery can return NULL values. This is one of the most common SQL gotchas.
Rule name: not-in-subquery
Default severity: Error
When This Rule Triggers
This rule triggers when:NOT INis used with a subquery:NOT IN (SELECT ...)
INis used with a subquery (withoutNOT)NOT INis used with a literal list:NOT IN (1, 2, 3)- Alternative constructs like
NOT EXISTSare used
Why It Matters
The NULL Problem
In SQL,NOT IN (SELECT ...) uses three-valued logic. When the subquery returns any NULL value:
- The entire
NOT INexpression evaluates toNULL(notTRUEorFALSE) - Rows are filtered out as if the condition were
FALSE - You get zero results, even when you expect some
Example of the Problem
banned.user_id contains NULL, the NOT IN evaluates to NULL for all rows.
Examples
Problematic: NOT IN with subquery
Problematic: NOT IN with subquery
banned.user_id can contain NULL, this query returns zero rows unexpectedly.Good: Use NOT EXISTS instead
Good: Use NOT EXISTS instead
NOT EXISTS handles NULL values correctly and has clearer semantics.Good: NOT IN with literal list
Good: NOT IN with literal list
Good: IN with subquery (without NOT)
Good: IN with subquery (without NOT)
IN (without NOT) handles NULL values as expected in most cases.Alternative: LEFT JOIN with NULL check
Alternative: LEFT JOIN with NULL check
NULL correctly.Implementation Details
The rule works by:- Walking the SQL AST to find
BoolExprnodes - Checking if the boolean expression is a
NOT_EXPR - Inspecting arguments to see if they contain
SubLinknodes withANY_SUBLINKtype - PostgreSQL’s parser represents
NOT IN (SELECT ...)asNOT (ANY (SELECT ...)) - Reporting an error at the location of the
NOTexpression
not_in_subquery.go