Saltar al contenido

Lógica booleana en SQL: AND, OR, NOT y la trampa de NULL (lógica trivaluada)

SQL tiene AND, OR y NOT con la precedencia habitual, y un tercer valor de verdad, NULL, que cambia en silencio lo que una regla hace con las filas con datos ausentes. El listado de abajo es la exportación a PostgreSQL de la regla de entrada generada por el motor, con la guarda que genera contra exactamente eso.

LógicaSQLNota
NOT ANOT aNOT NULL es NULL
A AND Ba AND bFALSO gana a NULL
A OR Ba OR bVERDADERO gana a NULL
A XOR Ba <> b entre booleanosNULL si alguno es NULL
A -> BNOT a OR bNo hay operador de implicación
A <-> Ba = b entre booleanosNULL si alguno es NULL
TRUE, FALSETRUE, FALSEY NULL, el tercer valor

Sobre VERDADERO y FALSO, los operadores de SQL son los booleanos: NOT primero, luego AND, luego OR. La diferencia es NULL, que significa desconocido. NULL AND FALSE es FALSO y NULL OR TRUE es VERDADERO, porque el operando conocido decide; NULL AND TRUE, NULL OR FALSE y NOT NULL son todos NULL. Una cláusula WHERE conserva solo las filas cuya condición es VERDADERA, así que una condición NULL descarta la fila, y NOT (x = 1) no devuelve las filas en las que x es NULL.

La exportación no finge que NULL no existe. Su cabecera enuncia una precondición de entradas no nulas: cada fragmento está definido solo cuando las columnas de entrada que lee son booleanos no nulos, y la plantilla de guarda marca una fila con una entrada NULL como invalid_input, una fila que viola los supuestos declarados como out_of_scope, y rellena las columnas de salida solo para las filas ok. Advierte explícitamente contra envolver los fragmentos en COALESCE(…, FALSE), que elegiría un significado para entradas desconocidas que la regla nunca enunció.

El archivo es de solo lectura por construcción: solo WITH, VALUES y SELECT, sin DDL ni DML, sin procedimientos. La cabecera nombra el destino, PostgreSQL 16 o posterior con entradas booleanas tipadas, y la versión con la que se probó, y dice que no es una afirmación de compatibilidad con otros dialectos; IS TRUE, IS NOT TRUE y las columnas booleanas son donde más difieren los dialectos. La página del error sobre NULL recorre a OR NOT a, una tautología sobre dos valores que es NULL cuando a lo es.

La regla de entrada, exportada por el motor

allow_entry := (member OR invited) AND NOT banned
-- Tangibool PostgreSQL export (tangibool.postgresql/1)
-- Target: PostgreSQL 16 or later with typed boolean inputs; tested with PostgreSQL 18.3 (PGlite 0.5.8).
-- Not a claim of compatibility with other SQL dialects.
-- Model: not stated
-- Source revision: reference
-- Interpretation status: not stated
-- Exporter: 1.0.0 (engine 1.0.0; semantics tangibool-semantics/1)
-- Logic digest: f0daa82174670c788f59eb058d07f7601cd0ace4ed94d61a747cc26c41fd4e0d
-- Assumptions digest: d58ee1ad38e23e5093e4baa1047b5ae70d6972e08f555e34ac3fa16df63c4a78
-- Mode: decision
--
-- Read-only: this file contains only WITH / VALUES / SELECT statements. No DDL, DML,
-- procedures, credentials, network access, or database changes. The expressions are pure
-- Boolean expressions, so their result does not depend on SQL evaluation order.
--
-- NON-NULL INPUT PRECONDITION: NULL is not a truth value of this rule. Every fragment below
-- is defined only when every input column it reads is a non-NULL boolean. The guard template
-- (section 2) marks any NULL input as 'invalid_input' and any row that violates the
-- assumptions as 'out_of_scope', and fills output columns only for 'ok' rows; a NULL in an
-- output column of another row is an envelope marker, not a third truth value. Never wrap
-- the fragments in COALESCE(..., FALSE) or otherwise choose a meaning for unknown inputs.
-- This evaluates the supplied model; it does not show that the model is a complete, legal,
-- or safe statement of a real-world rule.
--
-- Input columns (stable ID: name -> column):
--   v_79df34978043: member -> "in_member"
--   v_8afde06f51e3: invited -> "in_invited"
--   v_60b2536f5dc8: banned -> "in_banned"
-- Output columns:
--   f_ae8686d89791: allow_entry -> "out_allow_entry"
-- Assumptions (scope K; exported separately and never folded into an output):
--   none; every complete input row is in scope.

-- ----------------------------------------------------------------------------------------------
-- 1. Expression fragments (parenthesised; non-NULL boolean inputs required)
-- ----------------------------------------------------------------------------------------------
--
-- [out_allow_entry] output allow_entry (stable ID f_ae8686d89791)
--   allow_entry := (member OR invited) AND NOT banned
--   PRECONDITION: "in_member", "in_invited", "in_banned" are non-NULL booleans.
--   (("in_member" OR "in_invited") AND (NOT "in_banned"))
--

-- ----------------------------------------------------------------------------------------------
-- 2. Guard template (read-only SELECT over an inputs list)
-- ----------------------------------------------------------------------------------------------
-- Replace the example rows between the markers with your own rows, or replace the inputs
-- CTE with a SELECT that provides the boolean columns listed in the header. status is
-- 'invalid_input' when any input is NULL, 'out_of_scope' when the assumptions are false, and
-- 'ok' otherwise; output columns are filled only for 'ok' rows (NULL elsewhere is an
-- envelope marker). Assumption columns are shown for rows with complete inputs.
WITH inputs ("row_id", "in_member", "in_invited", "in_banned") AS (
  -- tangibool:example-rows:begin
  VALUES
    (1, FALSE, FALSE, FALSE),
    (2, FALSE, FALSE, TRUE),
    (3, FALSE, TRUE, FALSE),
    (4, FALSE, TRUE, TRUE),
    (5, TRUE, FALSE, FALSE),
    (6, TRUE, FALSE, TRUE),
    (7, TRUE, TRUE, FALSE),
    (8, TRUE, TRUE, TRUE),
    (9, NULL::boolean, FALSE, FALSE)
  -- tangibool:example-rows:end
),
checked AS (
  SELECT
    inputs.*,
    CASE
      WHEN "in_member" IS NULL OR "in_invited" IS NULL OR "in_banned" IS NULL THEN 'invalid_input'
      ELSE 'ok'
    END AS "status"
  FROM inputs
)
SELECT
  "row_id",
  "in_member",
  "in_invited",
  "in_banned",
  "status",
  CASE WHEN "status" = 'ok' THEN (("in_member" OR "in_invited") AND (NOT "in_banned")) END AS "out_allow_entry"
FROM checked
ORDER BY "row_id";

El error habitual

Probar una regla con WHERE NOT banned y olvidar las filas en las que banned es NULL: no están ni vetadas ni no vetadas, así que desaparecen tanto del conjunto permitido como del rechazado. La guarda de la exportación las informa como invalid_input.

Preguntas

¿a OR NOT a es siempre VERDADERO en SQL?

Solo cuando a es VERDADERO o FALSO. Cuando a es NULL la expresión es NULL, así que un WHERE con esa condición descarta la fila. La página del error muestra la comprobación de dos valores junto al comportamiento de tres valores.

¿Por qué la exportación apunta solo a PostgreSQL?

Porque es contra lo que se probó, y la cabecera lo dice; el tipo booleano, IS TRUE y la sintaxis de WITH difieren en otros sistemas. Los fragmentos booleanos puros suelen poder trasladarse; la plantilla de guarda puede que no.

Relacionado

Véase también

Lenguajes

Generado a partir del motor de Tangibool, semántica tangibool-semantics/1, motor 1.0.0, compilación . Cada tabla, mapa, simplificación y veredicto de estas páginas lo calculó el motor de Tangibool al compilar el sitio, y cada afirmación la volvió a comprobar el mismo verificador exacto que usa la app. Los límites se indican donde aplican. © 2026 Foreranger LTD.