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
| # | a | always_true |
|---|---|---|
| 0 | FALSE | TRUE |
| 1 | TRUE | TRUE |
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
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
- Logical equivalence checker: two expressions, exact verdict, counterexample
- Truth table generator: any Boolean expression, up to 12 variables, with intermediate columns
See also
- Boolean logic in SQL: AND, OR, NOT and the NULL three-valued trap
- A nullable flag rule in SQL: why (a OR NOT a) is not always true
- An unassigned input is not FALSE: incomplete assignments
- Is 1 true or false? 1 = TRUE, 0 = FALSE in Boolean logic
- Complement law: A AND NOT A = FALSE, A OR NOT A = TRUE
- Boolean expression to Python, SQL, SMT-LIB and LaTeX
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.