Skip to content

NULL is not FALSE: three-valued logic in SQL

a OR NOT a is a tautology on two values, and the engine proves it on both rows. In SQL, where a can be NULL, the same expression is NULL, and a WHERE clause drops the row. NULL is a third value, not a spelling of FALSE.

The check below is the two-valued one: a OR NOT a holds on every row, there are two rows, and the contradiction check finds the witness a = FALSE where the formula is TRUE. That is the whole story in Boolean logic, and it is the story the engine tells, because its semantics have exactly two values.

SQL's three-valued logic adds NULL, meaning unknown. NOT NULL is NULL; NULL OR NULL is NULL; so a OR NOT a is NULL when a is. The rules for the known operand still apply, NULL OR TRUE is TRUE and NULL AND FALSE is FALSE, which is why a condition sometimes works on NULL rows and sometimes silently excludes them. WHERE keeps only TRUE rows, so a NULL condition behaves like FALSE there, while CHECK constraints accept NULL as a pass; the same expression, two outcomes.

The engine's SQL export does not pretend otherwise. Its guard template marks any row with a NULL input as invalid_input and fills output columns only for rows whose inputs are all non-NULL Booleans, and its header forbids wrapping the fragments in COALESCE(…, FALSE), which would pick a meaning the rule never stated. The SQL language page describes the export.

always_true := a OR NOT a · output

Truth table

NULL is not FALSE: three-valued logic in SQL
#aalways_true
0FALSETRUE
1TRUETRUE

Plain-language reading

Beginneralways_true is true exactly when a is true or a is false (or both).
In every other case, always_true is false.
About the operators:
- OR is inclusive: it is true when at least one part is true, including when several are.
- NOT reverses a value: true becomes false and false becomes true.
Intermediatealways_true is true exactly when a is true or a is false (or both).
In every other case, always_true is false.
Advancedalways_true is true exactly when a is true or a is false (or both) [inclusive disjunction ∨].
In every other case, always_true is false.
Formal notation: a ∨ ¬a
NULL is not FALSE: three-valued logic in SQL NULL is not FALSE: three-valued logic in SQL a always_true OR NOT
NULL is not FALSE: three-valued logic in SQL NULL is not FALSE: three-valued logic in SQL a always_true OR NOT

The common slip

Testing a rule in SQL with WHERE NOT banned and concluding that unbanned users are admitted. Users whose banned is NULL are neither admitted nor rejected by that clause; they are missing from both result sets, and only a count of the rows reveals it.

Questions

Can I model NULL in Tangibool?

Not as a value. The engine has two truth values by design. Model “unknown” explicitly as a separate input (banned_known AND NOT banned), and the two-valued checks then cover the three cases honestly.

Is IS NOT TRUE the same as NOT?

No. x IS NOT TRUE is TRUE when x is FALSE or NULL, while NOT x is NULL when x is. IS predicates are the SQL way to make NULL behave like a value.

Related

Guides

Worked examples

Tools

See also

Pitfalls

Generated from the Tangibool engine, semantics tangibool-semantics/1, engine 1.0.0, build . Every table, map, simplification and verdict on these pages was computed by the Tangibool engine when the site was built, and every claim was re-checked by the same exact checker the app uses. Limits are stated where they apply. © 2026 Foreranger LTD.