Aquesta és la lliçó del deute. Des de 01-03 véns ensopegant amb el mateix fenomen amb disfresses diferents: empleat_id = NULL va retornar zero files quan n'hi havia deu; empleat_id <> 4 en va retornar sis en lloc de setze; les deu comandes web van desaparèixer d'un INNER JOIN; una condició sobre la taula dreta al WHERE va degradar un LEFT JOIN a INNER; i ahir mateix, NOT IN (4, 5, NULL) no va retornar exactament res. Cada vegada et vam prometre que "s'explica a 04-03".

Aquí ho tens. Tots aquells casos són la mateixa cosa, i aquella cosa es diu lògica de tres valors. En acabar aquesta lliçó no hauràs après cinc regles noves: n'hauràs après una de sola, i tots aquells símptomes deixaran de sorprendre't perquè els podràs predir. També veuràs la part que gairebé ningú no explica: que l'estàndard SQL tracta els NULL d'una manera al WHERE i d'una altra de diferent a DISTINCT, GROUP BY i ORDER BY, i que conèixer aquella incoherència és el que separa qui pateix els nuls de qui els fa servir.

Contingut

  1. Què és NULL i què no és
  2. Els tres NULL de BotigaVerda i què signifiquen
  3. La lògica de tres valors: TRUE, FALSE i UNKNOWN
  4. Les taules de veritat completes
  5. IS NULL i IS NOT NULL
  6. IS DISTINCT FROM i IS NOT DISTINCT FROM
  7. On NULL sí que s'agrupa amb NULL
  8. NULL i la restricció UNIQUE
  9. NULL en concatenacions i en aritmètica
  10. La resolució dels casos pendents
  11. Dissenyar amb NULL, amb sentinelles o amb NOT NULL
  12. Errors habituals i consells
  13. Exercicis
  14. Conclusió

  1. Què és NULL i què no és

NULL és l'absència de valor. No és un valor especial: és la marca que en aquella cel·la no n'hi ha cap.

La confusió més estesa consisteix a tractar-lo com si fos un valor concret. No ho és, i la diferència es fa visible tan bon punt compares:

NULL no és… Per què importa
Zero 0 és un número: significa "cap unitat". NULL significa "no sé quantes unitats". 0 + 5 = 5; NULL + 5 = NULL
Cadena buida '' '' és una cadena de longitud 0, un valor real. LENGTH('') és 0; LENGTH(NULL) és NULL
FALSE FALSE és una resposta. NULL és la manca de resposta
Un altre NULL Dues absències no són iguals: no saps què hi havia a cap de les dues

Aquest últim punt és el cor de tot. Si a productes.cost hi ha dues files amb NULL, són iguals els seus costos? No ho saps. Podrien ser 3,20 € i 47 €. SQL, amb criteri, es nega a afirmar que són iguals; i també es nega a afirmar que són diferents.

La frase que resumeix la lliçó: NULL no significa "buit", significa "desconegut". I tot el que compares amb alguna cosa desconeguda produeix un resultat desconegut.

  1. Els tres NULL de BotigaVerda i què signifiquen

En un esquema ben dissenyat, cada columna que admet nuls ho fa per una raó de negoci concreta. BotigaVerda en té tres, i les tres signifiquen coses diferents:

Columna Files amb NULL Què significa en el negoci
comandes.empleat_id 10 de 20 Comanda entrada per la web, sense comercial assignat. No és que s'hagi perdut la dada: és que no existeix
clients.referit_per_id 7 de 15 Client que va arribar pel seu compte, sense programa de referits
empleats.cap_id 1 de 8 La direcció general: Rosa Alcázar Vives no té cap perquè és a dalt de l'organigrama

Anem a veure'ls.

SELECT id,
       client_id,
       data_comanda,
       estat,
       empleat_id
FROM comandes
WHERE empleat_id IS NULL
ORDER BY id;
id client_id data_comanda estat empleat_id
1 1 2025-03-04 lliurat (null)
3 3 2025-04-02 lliurat (null)
5 1 2025-05-07 lliurat (null)
7 6 2025-06-11 lliurat (null)
9 8 2025-07-15 lliurat (null)
11 2 2025-09-09 lliurat (null)
13 11 2025-10-22 lliurat (null)
15 1 2025-12-02 lliurat (null)
17 7 2026-01-13 enviat (null)
19 6 2026-02-09 pagat (null)

10 files, el 50 % del canal de venda.

SELECT id,
       nom || ' ' || cognoms AS client,
       data_registre,
       referit_per_id
FROM clients
WHERE referit_per_id IS NULL
ORDER BY id;
id client data_registre referit_per_id
1 Lucía Martínez Soler 2025-01-10 (null)
4 Javier Ortega Ruiz 2025-02-14 (null)
6 Pau Llorens Vidal 2025-03-09 (null)
7 Sofia Moreira Costa 2025-03-21 (null)
9 Camille Dubois 2025-04-18 (null)
12 Diego Ramos Herrera 2025-06-01 (null)
14 Hugo Iglesias Pardo 2025-09-12 (null)

7 files. Els altres 8 clients van arribar recomanats.

SELECT id,
       nom || ' ' || cognoms AS empleat,
       carrec,
       cap_id
FROM empleats
WHERE cap_id IS NULL;
id empleat carrec cap_id
1 Rosa Alcázar Vives Directora general (null)

1 fila. Aquest NULL no és una dada absent ni una dada desconeguda: és una afirmació estructural ("aquí s'acaba la jerarquia"), i per això el SELF JOIN de 03-06 necessitava un LEFT JOIN per no perdre la Rosa.

I encara hi ha un quart tipus de NULL que ja has vist i que no és a cap taula: els que fabrica el motor en construir un LEFT JOIN (03-03, secció 3). Aquells no eren a les dades; apareixen perquè no hi va haver parella.

Origen del NULL Exemple És al disc?
Dada que no existeix (regla de negoci) empleat_id d'una comanda web
Dada desconeguda Un cost que el proveïdor no ha comunicat
Frontera estructural cap_id de la direcció general
Generat per un LEFT JOIN sense parella co.estat de la Núria Bosch No, el crea la consulta
Resultat d'una operació amb nuls preu * NULL No, el crea l'expressió

  1. La lògica de tres valors: TRUE, FALSE i UNKNOWN

Fora d'SQL, una condició és certa o falsa. En SQL hi ha tres resultats possibles, perquè una comparació amb NULL no es pot decidir:

5 = 5        → TRUE
5 = 4        → FALSE
5 = NULL     → UNKNOWN
NULL = NULL  → UNKNOWN
NULL <> NULL → UNKNOWN

UNKNOWN no és un tercer estat exòtic: és exactament el que diu la paraula. "L'empleat de la comanda 1 és el número 4?" — no ho sabem, perquè no hi ha cap empleat registrat.

I ara la regla que ho governa tot, ja enunciada a 02-03 i que convé repetir textualment:

WHERE conserva únicament les files la condició de les quals s'avalua a TRUE. Les que donen FALSE es descarten, i les que donen UNKNOWN també.

El mateix val per a l'ON d'un JOIN i per al HAVING que veuràs a 04-06. Un sol criteri, tres clàusules.

flowchart TD
    A["Condició avaluada<br/>sobre una fila"] --> B{"Quin resultat?"}
    B -->|"TRUE"| C["✅ La fila passa"]
    B -->|"FALSE"| D["❌ La fila es descarta"]
    B -->|"UNKNOWN"| E["❌ La fila es descarta<br/>(igual que FALSE)"]

Aquí hi ha la clau de per què FALSE i UNKNOWN semblen el mateix mirant el resultat d'una sola consulta: tots dos descarten la fila. La diferència només es fa visible quan negues la condició, perquè NOT FALSE és TRUE però NOT UNKNOWN continua sent UNKNOWN.

  1. Les taules de veritat completes

AND

AND TRUE FALSE UNKNOWN
TRUE TRUE FALSE UNKNOWN
FALSE FALSE FALSE FALSE
UNKNOWN UNKNOWN FALSE UNKNOWN

Les dues cel·les destacades són les importants: FALSE AND UNKNOWN és FALSE, no UNKNOWN. Si un dels factors ja és fals, el conjunt és fals encara que l'altre sigui desconegut. Tant se val el que no sàpigues: la conjunció ja està decidida.

OR

OR TRUE FALSE UNKNOWN
TRUE TRUE TRUE TRUE
FALSE TRUE FALSE UNKNOWN
UNKNOWN TRUE UNKNOWN UNKNOWN

Simètricament: TRUE OR UNKNOWN és TRUE. Si una alternativa ja és certa, la disjunció és certa.

Aquestes dues cel·les expliquen per si soles l'asimetria de 04-02:

Operador S'expandeix a Cel·la que decideix Resultat amb un NULL a la llista
IN OR encadenats TRUE OR UNKNOWN = TRUE Funciona amb normalitat
NOT IN AND encadenats TRUE AND UNKNOWN = UNKNOWN Zero files sempre

NOT

Entrada NOT entrada
TRUE FALSE
FALSE TRUE
UNKNOWN UNKNOWN

Negar el desconegut continua sent desconegut. D'aquí en surt l'observació pràctica més útil de tota la lliçó:

Una condició i la seva negació no cobreixen totes les files. Si estat = 'lliurat' dona 14 files i estat <> 'lliurat' en dona 6, i la taula en té 20, la lògica tanca perquè estat és NOT NULL. Amb empleat_id = 4 (4 files) i empleat_id <> 4 (6 files), 4 + 6 = 10 ≠ 20: les 10 que falten són les nul·les. Aquesta suma és el teu millor detector de nuls.

I un advertiment sobre l'ordre d'avaluació

Les taules de veritat descriuen el significat, no l'ordre en què PostgreSQL avalua les condicions. El motor pot reordenar lliurement els operands d'un AND si això abarateix el pla. Per això no pots fer servir un AND com a protecció:

-- ⚠️ NO garanteix que la divisió no s'executi mai amb zero
WHERE stock <> 0 AND (100 / stock) > 2

PostgreSQL podria avaluar la divisió primer. La forma segura és CASE o NULLIF, tots dos del mòdul 6.

  1. IS NULL i IS NOT NULL

Com que = NULL mai no és TRUE, SQL ofereix un predicat específic. IS NULL no és una comparació: és una pregunta sobre l'estat de la cel·la, i per això retorna sempre TRUE o FALSE, mai UNKNOWN.

SELECT NULL = NULL      AS igualtat,
       NULL IS NULL     AS es_nul,
       NULL IS NOT NULL AS no_es_nul;
igualtat es_nul no_es_nul
(null) true false

La primera columna és NULL (és a dir, UNKNOWN); les altres dues són booleans de debò. Aquesta és tota la diferència, i explica per què WHERE empleat_id IS NULL funciona i WHERE empleat_id = NULL no.

Escriptura Tipus de resultat Retorna files?
columna = NULL UNKNOWN sempre Mai
columna <> NULL UNKNOWN sempre Mai
columna IS NULL TRUE / FALSE Sí, les nul·les
columna IS NOT NULL TRUE / FALSE Sí, les no nul·les

I la parella IS NULL / IS NOT NULL que parteix la taula en dues meitats exactes:

SELECT COUNT(*)                                      AS total,
       COUNT(*) FILTER (WHERE empleat_id IS NULL)     AS sense_comercial,
       COUNT(*) FILTER (WHERE empleat_id IS NOT NULL) AS amb_comercial
FROM comandes;
total sense_comercial amb_comercial
20 10 10

10 + 10 = 20. Tanca. (FILTER i COUNT són el tema de la lliçó següent; aquí només fan d'instrument de mesura.)

Nota de dialecte: alguns motors permeten configurar que = NULL es comporti com IS NULL —SQL Server ho fa amb SET ANSI_NULLS OFF, avui obsolet—. No ho activis mai. Converteix el teu SQL en una cosa que només funciona al teu servidor i trenca la lògica que acabes d'aprendre.

  1. IS DISTINCT FROM i IS NOT DISTINCT FROM

De vegades sí que vols comparar tractant NULL com "un valor més": que dos nuls es considerin iguals i que un nul es consideri diferent de qualsevol valor. Per a això existeix aquesta parella d'operadors, que mai no retornen UNKNOWN.

Expressió = / <> IS [NOT] DISTINCT FROM
5 = 5 / 5 IS NOT DISTINCT FROM 5 TRUE TRUE
5 = 4 / 5 IS NOT DISTINCT FROM 4 FALSE FALSE
5 = NULL / 5 IS NOT DISTINCT FROM NULL UNKNOWN FALSE
NULL = NULL / NULL IS NOT DISTINCT FROM NULL UNKNOWN TRUE

Amb dades reals, la diferència salta a la vista. "Totes les comandes que no va gestionar l'Óscar Peris (empleat 4)":

-- ⚠️ INCORRECTA per a la pregunta: perd les comandes web
SELECT id, client_id, empleat_id, estat
FROM comandes
WHERE empleat_id <> 4
ORDER BY id;
id client_id empleat_id estat
4 4 5 lliurat
8 7 5 lliurat
12 10 5 lliurat
14 12 6 lliurat
18 5 5 pagat
20 9 6 pendent

6 files. És el resultat que et va sorprendre a 02-03.

-- ✅ CORRECTA: una comanda sense comercial tampoc no la va gestionar l'Óscar
SELECT id, client_id, empleat_id, estat
FROM comandes
WHERE empleat_id IS DISTINCT FROM 4
ORDER BY id;

16 files: les 6 anteriors més les 10 comandes web. Ara sí, 4 + 16 = 20.

La forma llarga equivalent seria WHERE empleat_id <> 4 OR empleat_id IS NULL, que és el que vas escriure a mà a la solució 3 de 02-03. IS DISTINCT FROM diu el mateix en quatre paraules i sense risc d'oblidar el segon terme.

El seu ús més valuós, però, apareix en comparar dues columnes que poden ser nul·les totes dues:

-- Han canviat les dades entre dues versions d'una fila?
WHERE nou.cost IS DISTINCT FROM antic.cost

Amb <>, una fila el cost de la qual passa de NULL a 12.00 no es detectaria com a canvi (la comparació donaria UNKNOWN). Amb IS DISTINCT FROM, sí. És l'operador de referència per a detecció de canvis, comparació de versions i processos de sincronització.

Nota de dialecte: IS DISTINCT FROM és SQL estàndard i existeix a PostgreSQL, SQLite i Oracle 23ai. MySQL fa servir l'operador <=> ("null-safe equal"), que equival a IS NOT DISTINCT FROM. SQL Server no tenia res fins al 2022, que va introduir IS [NOT] DISTINCT FROM; en versions anteriors cal escriure la forma llarga amb OR ... IS NULL.

  1. On NULL sí que s'agrupa amb NULL

Aquí arriba la part que descol·loca molta gent: l'estàndard SQL no és coherent amb si mateix. Acabes d'aprendre que NULL = NULL és UNKNOWN, i tanmateix:

SELECT DISTINCT empleat_id
FROM comandes
ORDER BY empleat_id;
empleat_id
4
5
6
(null)

4 files. Deu comandes tenen empleat_id nul i DISTINCT les ha col·lapsat en una sola fila. És a dir: per a DISTINCT, aquells deu nuls sí que són iguals entre si.

El mateix passa en agrupar:

SELECT empleat_id,
       COUNT(*) AS comandes
FROM comandes
GROUP BY empleat_id
ORDER BY empleat_id;
empleat_id comandes
4 4
5 4
6 2
(null) 10

4 grups, i els NULL en formen un de propi amb 10 files. Això és enormement útil —de fet és el que permet comptar el canal web d'un cop d'ull— i ho desenvoluparà la lliçó 04-05.

I en ordenar, els nuls també s'agrupen entre si, encara que la seva posició sigui configurable (02-05):

SELECT id, empleat_id, estat
FROM comandes
ORDER BY empleat_id NULLS FIRST, id
LIMIT 12;
id empleat_id estat
1 (null) lliurat
3 (null) lliurat
5 (null) lliurat
7 (null) lliurat
9 (null) lliurat
11 (null) lliurat
13 (null) lliurat
15 (null) lliurat
17 (null) enviat
19 (null) pagat
2 4 lliurat
6 4 lliurat

(12 primeres de 20 files.)

La taula que convé tenir a mà

Context Dos NULL es consideren iguals? Conseqüència pràctica
WHERE, ON, HAVING amb = No La fila es descarta
IS NOT DISTINCT FROM Comparació segura davant dels nuls
DISTINCT / DISTINCT ON Tots els nuls col·lapsen en un
GROUP BY Els nuls formen un grup
ORDER BY Van tots junts (NULLS FIRST/LAST)
UNION, INTERSECT, EXCEPT Dedupliquen els nuls com qualsevol valor
Restricció UNIQUE No (per defecte) S'admeten diversos nuls a la columna
Funcions d'agregació S'ignoren AVG fa la mitjana només dels no nuls (04-04)
COUNT(*) Es compten Compta files, no valors

La justificació oficial de l'estàndard és que WHERE respon preguntes sobre fets (i d'un desconegut no en pots afirmar res), mentre que GROUP BY i DISTINCT responen preguntes sobre agrupació de files (i allà "sense valor" és una categoria tan vàlida com qualsevol altra). És una explicació raonable, però no canvia el fet que el mateix símbol es comporti de dues maneres. Aprèn-te la taula; no intentis deduir-la.

  1. NULL i la restricció UNIQUE

Conseqüència directa de la penúltima fila de la taula anterior: a PostgreSQL, una columna amb restricció UNIQUE admet tants NULL com vulguis, perquè dos nuls no es consideren duplicats.

-- Si productes tingués una columna 'codi_ean' UNIQUE que admetés nuls:
-- aquestes dues files conviurien sense problema
INSERT INTO productes (nom, codi_ean, ...) VALUES ('Producte A', NULL, ...);
INSERT INTO productes (nom, codi_ean, ...) VALUES ('Producte B', NULL, ...);

Això sorprèn, i de vegades és justament el que vols ("l'EAN és únic, però encara no el tenen tots els productes") i de vegades no ("només hi pot haver un client sense verificar"). PostgreSQL 15 va afegir la manera d'exigir el contrari:

-- PostgreSQL 15+: els NULL compten com a duplicats entre si
ALTER TABLE productes
  ADD CONSTRAINT productes_ean_uk UNIQUE NULLS NOT DISTINCT (codi_ean);

A BotigaVerda les dues columnes UNIQUEcategories.nom i clients.email— són a més NOT NULL, així que la qüestió no s'arriba a plantejar. Però és un detall de disseny que cal decidir conscientment, no descobrir en producció.

Nota de dialecte: aquest és un dels punts on els motors més divergeixen. PostgreSQL, MySQL, SQLite i Oracle admeten diversos nuls en una columna UNIQUE; SQL Server només n'admet un (tracta tots els nuls com el mateix valor a efectes de l'índex únic). Una taula que funciona a PostgreSQL pot fallar en migrar-la a SQL Server per aquest motiu exacte. Les restriccions s'estudien al mòdul 5.

I recorda que una clau primària no té aquest debat: PRIMARY KEY implica NOT NULL, sempre i a tots els motors.

  1. NULL en concatenacions i en aritmètica

La regla és d'una simplicitat brutal: gairebé qualsevol operació amb NULL retorna NULL. Es diu que el nul es propaga.

SELECT 100 + NULL      AS suma,
       100 * NULL      AS producte,
       'Hola' || NULL  AS concatenacio,
       UPPER(NULL)     AS majuscules;
suma producte concatenacio majuscules
(null) (null) (null) (null)

Ho vas veure a 02-02 amb la concatenació, i a 03-03 amb els LEFT JOIN. Aquí hi ha el perquè: si no saps quin és el segon sumand, no pots saber quina és la suma.

Sobre dades reals, l'efecte en un LEFT JOIN:

SELECT c.id,
       c.nom || ' ' || c.cognoms AS client,
       co.id           AS comanda_id,
       co.despeses_enviament,
       co.despeses_enviament * 2 AS enviament_doble
FROM clients AS c
LEFT JOIN comandes AS co ON co.client_id = c.id
WHERE c.id IN (12, 13, 14, 15)
ORDER BY c.id, co.id;
id client comanda_id despeses_enviament enviament_doble
12 Diego Ramos Herrera 14 4.95 9.90
13 Núria Bosch Ferrer (null) (null) (null)
14 Hugo Iglesias Pardo (null) (null) (null)
15 Inés Carrasco Vega (null) (null) (null)

enviament_doble val NULL, no 0.00, per als tres clients sense comandes. En un informe això pot aparèixer com una cel·la buida, i qui la llegeixi pot interpretar-la com un zero. No ho és: és "no aplica".

Les excepcions

No tot propaga el nul. Convé conèixer les excepcions perquè són precisament les eines per tractar-lo:

Construcció Amb NULL Comentari
IS NULL / IS NOT NULL Retorna booleà El predicat d'aquesta lliçó
IS DISTINCT FROM Retorna booleà Secció 6
COALESCE(a, b, c) Retorna el primer no nul 06-04
NULLIF(a, b) Converteix un valor en NULL 06-04
CASE WHEN ... IS NULL THEN ... Permet decidir 06-05
CONCAT('a', NULL, 'b') Ignora els nuls → 'ab' L'alternativa a ||, a 06-01
Funcions d'agregació Ignoren els nuls 04-04, la lliçó següent
COUNT(*) Compta la fila igualment 04-04

Les tres primeres files ja les manejes. Les de COALESCE, NULLIF i CASE —les que substitueixen un nul per un valor presentable— són el contingut de 06-04 i 06-05; allà escriuràs COALESCE(co.despeses_enviament, 0) perquè la cel·la de l'informe mostri 0.00. I la dels agregats és la primera cosa que veuràs demà.

  1. La resolució dels casos pendents

Anem a tancar, un a un, tots els caps solts del curs. Els cinc tenen la mateixa explicació.

Cas 1: WHERE empleat_id = NULL va retornar 0 files (02-03)

empleat_id = NULL s'avalua a UNKNOWN per a les vint files: per a les deu amb valor perquè no pots comparar un número amb el desconegut, i per a les deu nul·les perquè NULL = NULL tampoc no és cert. El WHERE descarta tot el que no sigui TRUE. Zero files, i no podria ser d'altra manera. El correcte és IS NULL.

Cas 2: WHERE empleat_id <> 4 va retornar 6 i no 16 (02-03)

Les deu comandes web avaluen NULL <> 4UNKNOWN → descartades. Queden les 10 amb comercial, menys les 4 de l'Óscar: 6 files. Si la pregunta era "les que no va gestionar l'Óscar", la resposta correcta té 16 files i s'escriu WHERE empleat_id IS DISTINCT FROM 4, com a la secció 6.

Cas 3: l'INNER JOIN amb empleats va perdre 10 comandes (03-02)

La condició ON co.empleat_id = e.id és una comparació com qualsevol altra. Per a les comandes web dona UNKNOWN, cap fila d'empleats no la satisfà, i l'INNER JOIN les descarta. L'ON segueix la mateixa regla que el WHERE.

Cas 4: una condició al WHERE va degradar un LEFT JOIN a INNER (03-03)

El LEFT JOIN fabrica una fila per a la Núria amb totes les columnes de comandes a NULL. Després, WHERE co.estat = 'lliurat' avalua NULL = 'lliurat'UNKNOWN → descartada. Per això 18 files es convertien en 14 i 15 clients en 12. La condició ha d'anar a l'ON, que actua abans de fabricar aquells nuls.

Cas 5: NOT IN (4, 5, NULL) va retornar 0 files (04-02)

S'expandeix a ... AND empleat_id <> NULL, i aquell factor és UNKNOWN per a tota fila. TRUE AND UNKNOWN = UNKNOWN (taula de veritat de la secció 4). Zero files garantides, per a qualsevol taula i qualsevol dada.

El patró comú

flowchart LR
    A["Un NULL entra<br/>en una comparació"] --> B["El resultat és UNKNOWN"]
    B --> C["WHERE / ON / HAVING<br/>només deixen passar TRUE"]
    C --> D["La fila desapareix<br/>sense error i sense avís"]
    D --> E["🔍 Símptoma: el filtre i la seva<br/>negació no sumen el total"]

Els cinc casos són el mateix cas. Un cop ho veus així, deixen de ser cinc regles per memoritzar i passen a ser-ne una per aplicar.

  1. Dissenyar amb NULL, amb sentinelles o amb NOT NULL

Quan et toqui dissenyar una taula (mòdul 5) hauràs de decidir, columna a columna, si admet nuls. Hi ha tres estratègies i cap no és universalment correcta.

Estratègia Exemple A favor En contra
Permetre NULL comandes.empleat_id Modela amb honestedat "no hi ha valor". No inventa dades. Els agregats l'ignoren sols Obliga a manejar la lògica de tres valors a cada consulta
Valor sentinella Un empleat fictici amb id = 0 anomenat "Web" Les consultes se simplifiquen: JOIN normals, sense LEFT, sense IS NULL El sentinella és una dada falsa: apareix als recomptes, als DISTINCT i als informes. Cal recordar-se d'excloure'l sempre
NOT NULL amb DEFAULT comandes.despeses_enviament NOT NULL DEFAULT 0 La columna no sorprèn mai. Sumar és sumar Només val quan existeix un valor per defecte verdader. 0.00 de ports és un fet; un cost a 0.00 seria mentida

Criteris per triar:

  1. Existeix un valor per defecte que sigui cert? Si sí, NOT NULL DEFAULT. Els ports gratuïts són 0,00 € de debò, no una absència.
  2. L'absència significa alguna cosa per al negoci? Si sí, NULL i documenta-ho. "Comanda web" és una informació valuosa, no un buit.
  3. Estàs temptat de fer servir -1, 0, 'N/A' o '9999-12-31'? Compte: això és un sentinella disfressat. AVG en farà la mitjana, MIN el retornarà com a mínim i COUNT el comptarà. BotigaVerda seria un desastre si el canal web s'hagués codificat com empleat_id = 0: qualsevol "mitjana de comandes per comercial" quedaria enverinada.

La postura sensata: els NULL són part del model relacional i amagar-los amb sentinelles no elimina el problema, només el fa invisible. Fes servir NOT NULL sempre que puguis justificar un valor real per defecte; fes servir NULL quan l'absència sigui legítima; i no facis servir sentinelles llevat que tinguis una raó molt concreta i la deixis escrita a la documentació de l'esquema.

Errors habituals i consells

  • Escriure = NULL o <> NULL. Zero files sempre. És IS NULL / IS NOT NULL.
  • Creure que NULL és zero o cadena buida. NULL + 5 és NULL; 0 + 5 és 5. NULL || 'a' és NULL; '' || 'a' és 'a'.
  • Oblidar que <>, NOT IN, NOT LIKE i NOT BETWEEN descarten les files nul·les. Comprova sempre que la condició i la seva negació sumin el total.
  • Fer servir NOT IN amb una llista que pot portar un NULL. Zero files, garantides (04-02).
  • Posar una condició sobre la taula dreta al WHERE d'un LEFT JOIN. Els nuls fabricats no la superen i el LEFT es degrada a INNER (03-03).
  • Suposar que si A és NULL aleshores NOT A és cert. NOT UNKNOWN és UNKNOWN.
  • Interpretar una cel·la buida d'un informe com un zero. Pot ser "no aplica". COALESCE (06-04) ho fa explícit.
  • Esperar que UNIQUE impedeixi diverses files nul·les. No ho fa, llevat de NULLS NOT DISTINCT a PostgreSQL 15+ o a SQL Server, que va a l'inrevés.
  • Confiar en l'ordre d'avaluació d'un AND per protegir-se d'un nul o d'una divisió per zero. El planificador reordena. Fes servir CASE o NULLIF (mòdul 6).
  • Consell: en llegir un esquema, mira primer quines columnes admeten NULL. \d taula a psql ho diu. Són exactament els llocs on les teves consultes poden perdre files.
  • Consell: quan una consulta retorni menys files de les esperades, sospita dels nuls abans que de les dades. És la causa més freqüent i la més silenciosa.
  • Consell: fes servir IS DISTINCT FROM per defecte en comparar dues columnes que admeten nuls. T'estalvia l'OR ... IS NULL i fa la intenció explícita.

Exercicis

Exercici 1

Sense executar res, prediu el resultat (true, false o *(null)*) de cada expressió. Després executa-les totes en una sola consulta i comprova-ho.

SELECT NULL = NULL                    AS a,
       NULL IS NULL                   AS b,
       NULL <> NULL                   AS c,
       NOT (NULL = NULL)              AS d,
       (1 = 1) OR (NULL = 1)          AS e,
       (1 = 1) AND (NULL = 1)         AS f,
       (1 = 2) AND (NULL = 1)         AS g,
       NULL IS NOT DISTINCT FROM NULL AS h,
       5 IS DISTINCT FROM NULL        AS i;

Explica en una frase cada resultat que t'hagi sorprès.

Exercici 2

El departament de màrqueting vol mesurar el programa de referits. Sobre clients:

  1. Quants clients van arribar per recomanació i quants pel seu compte? Escriu dues consultes i comprova que sumen 15.
  2. Escriu la consulta que retorna, per a cada client, el seu nom complet i el nom complet de qui el va recomanar, incloent-hi els que no van ser recomanats per ningú. (Pista: relació reflexiva, 03-06.)
  3. En aquella consulta, per què la columna del recomanador apareix com a *(null)* i no com una cadena buida? Dona'n les dues raons.

Exercici 3

Un company ha escrit aquest control de qualitat i afirma que "a BotigaVerda no hi ha cap comanda rara":

-- ⚠️ INCORRECTA
SELECT COUNT(*) AS comandes_sense_comercial_valid
FROM comandes
WHERE empleat_id <> 4
  AND empleat_id <> 5
  AND empleat_id <> 6;
comandes_sense_comercial_valid
0
  1. Què està mesurant realment aquella consulta i per què el 0 no demostra el que ell es pensa?
  2. Escriu la versió que compta de debò les comandes l'empleat_id de les quals no és ni 4, ni 5, ni 6 (comptant els nuls com a "no és cap dels tres"). Quantes en són?
  3. Escriu la versió que compta les comandes amb un empleat_id present però diferent d'aquests tres. Quantes en són i què significa aquell número?

Solucions

Solució 1

Expressió Resultat Raó
a: NULL = NULL (null) Dues absències no són comparables
b: NULL IS NULL true IS NULL no és una comparació: és una pregunta sobre l'estat
c: NULL <> NULL (null) Tampoc no pots afirmar que siguin diferents
d: NOT (NULL = NULL) (null) NOT UNKNOWN = UNKNOWN
e: (1=1) OR (NULL=1) true TRUE OR UNKNOWN = TRUE. Una alternativa certa ja n'hi ha prou
f: (1=1) AND (NULL=1) (null) TRUE AND UNKNOWN = UNKNOWN. És el cas de NOT IN
g: (1=2) AND (NULL=1) false FALSE AND UNKNOWN = FALSE. Ja està decidit
h: NULL IS NOT DISTINCT FROM NULL true Aquest operador que tracta els nuls com a iguals
i: 5 IS DISTINCT FROM NULL true I tracta un valor com a diferent d'un nul

Les tres que solen sorprendre són d, f i g. La d perquè un espera que negar alguna cosa la converteixi en certa; la f i la g perquè sembla contradictori que AND amb un UNKNOWN de vegades doni UNKNOWN i de vegades FALSE — i tanmateix és el lògic: si un factor ja és fals, no cal saber l'altre.

Solució 2

1. Les dues meitats:

SELECT COUNT(*) AS clients_referits
FROM clients
WHERE referit_per_id IS NOT NULL;
clients_referits
8
SELECT COUNT(*) AS clients_espontanis
FROM clients
WHERE referit_per_id IS NULL;
clients_espontanis
7

8 + 7 = 15. Tanca, perquè IS NULL i IS NOT NULL sí que particionen la taula. Si haguessis escrit WHERE referit_per_id <> 0 per als "referits", n'hauries obtingut 8 igualment per casualitat, i WHERE referit_per_id = 0 n'hauria donat 0 en lloc de 7.

2. Autounió amb LEFT JOIN:

SELECT c.id,
       c.nom || ' ' || c.cognoms AS client,
       ref.nom || ' ' || ref.cognoms AS recomanat_per
FROM clients AS c
LEFT JOIN clients AS ref ON c.referit_per_id = ref.id
ORDER BY c.id;
id client recomanat_per
1 Lucía Martínez Soler (null)
2 Carlos Ferrer Ibáñez Lucía Martínez Soler
3 Marta Sanchis Gil Lucía Martínez Soler
4 Javier Ortega Ruiz (null)
5 Ana Belmonte Roca Carlos Ferrer Ibáñez
6 Pau Llorens Vidal (null)
7 Sofia Moreira Costa (null)
8 Tiago Almeida Nunes Sofia Moreira Costa
9 Camille Dubois (null)
10 Julien Moreau Camille Dubois
11 Elena Navarro Puig Pau Llorens Vidal
12 Diego Ramos Herrera (null)
13 Núria Bosch Ferrer Ana Belmonte Roca
14 Hugo Iglesias Pardo (null)
15 Inés Carrasco Vega Lucía Martínez Soler

15 files, els 15 clients. Amb INNER JOIN en serien 8 i perdries els espontanis. Fixa't en el detall: la Lucía ha recomanat tres clients (2, 3 i 15), cosa que la converteix en la millor prescriptora de BotigaVerda.

3. Les dues raons per les quals recomanat_per surt *(null)*:

  1. El LEFT JOIN no va trobar parella. Per als set clients amb referit_per_id nul, la condició c.referit_per_id = ref.id dona UNKNOWN i no casa amb cap fila, així que el motor omple totes les columnes de ref amb NULL (03-03, secció 3).
  2. La concatenació propaga el nul. Encara que només una de les dues columnes fos nul·la, ref.nom || ' ' || ref.cognoms donaria NULL igualment, perquè || amb un operand nul retorna nul (02-02 i secció 9 d'aquesta lliçó).

Que sigui *(null)* i no '' és important: la cadena buida significaria "es diu així, amb zero caràcters". El nul significa "no hi ha ningú". Perquè l'informe mostri alguna cosa llegible —"Sense recomanador", per exemple— cal COALESCE, a 06-04.

Solució 3

1. Què mesura realment. Els tres factors estan units per AND, i per a les deu comandes web cadascun val UNKNOWN. UNKNOWN AND UNKNOWN AND UNKNOWN és UNKNOWN, així que aquelles deu files es descarten. Per a les altres deu, cada comanda té un empleat_id que és 4, 5 o 6, de manera que un dels tres factors és FALSE i la conjunció és FALSE.

Resultat: zero files. I aquell 0 no demostra el que ell es pensa, perquè la consulta és incapaç de veure la meitat de la taula: les deu comandes web no arriben mai a avaluar-se com a candidates. Si el criteri de "comanda rara" inclou "sense comercial assignat", aquesta consulta no ho detectaria mai, ni avui ni mai. Un control de qualitat que estructuralment no pot donar positiu no és un control de qualitat.

2. Comptant els nuls com a "no és cap dels tres":

-- ✅ CORRECTA
SELECT COUNT(*) AS comandes_sense_comercial_conegut
FROM comandes
WHERE empleat_id IS DISTINCT FROM 4
  AND empleat_id IS DISTINCT FROM 5
  AND empleat_id IS DISTINCT FROM 6;
comandes_sense_comercial_conegut
10

10 comandes: exactament les deu del canal web. IS DISTINCT FROM mai no retorna UNKNOWN, així que la conjunció es resol netament. La forma equivalent i més idiomàtica per a aquest cas concret seria:

SELECT COUNT(*) AS comandes_sense_comercial_conegut
FROM comandes
WHERE empleat_id IS NULL
   OR empleat_id NOT IN (4, 5, 6);

que retorna el mateix 10.

3. Només les que tenen comercial i no és cap d'aquests tres:

SELECT COUNT(*) AS comandes_comercial_desconegut
FROM comandes
WHERE empleat_id IS NOT NULL
  AND empleat_id NOT IN (4, 5, 6);
comandes_comercial_desconegut
0

0 comandes, i aquest zero sí que significa alguna cosa: no hi ha cap comanda assignada a un empleat que no sigui l'Óscar, la Laia o en Marc. És a dir, els altres cinc empleats —Rosa, Andrés, Beatriz, Irene i Daniel— no gestionen comandes, tal com descrivia 01-06.

La comprovació final: 10 (amb comercial 4, 5 o 6) + 10 (sense comercial) + 0 (amb un altre comercial) = 20. Ara la lògica tanca, i aquest és el criteri per saber que la consulta està ben escrita.

Conclusió

El deute queda saldat, i amb una sola idea:

  • NULL és l'absència de valor, no zero, ni cadena buida, ni fals, ni igual a un altre NULL. Significa desconegut.
  • A BotigaVerda hi ha tres NULL amb tres significats de negoci diferents —canal web, client espontani, cim de l'organigrama— més els que fabrica el motor a cada LEFT JOIN.
  • SQL fa servir lògica de tres valors: TRUE, FALSE i UNKNOWN. WHERE, ON i HAVING conserven només el que és TRUE, així que UNKNOWN es descarta igual que FALSE — però en negar-los es comporten diferent, perquè NOT UNKNOWN continua sent UNKNOWN.
  • Les dues cel·les que cal memoritzar són TRUE OR UNKNOWN = TRUE i TRUE AND UNKNOWN = UNKNOWN. D'elles en surt tota l'asimetria entre IN i NOT IN.
  • IS NULL / IS NOT NULL són predicats, no comparacions, i sí que parteixen la taula en dues meitats exactes. IS DISTINCT FROM compara tractant el nul com un valor més, i és l'eina correcta per comparar columnes que admeten nuls.
  • L'estàndard és incoherent a propòsit: NULL = NULL és desconegut, però a DISTINCT, GROUP BY, ORDER BY i a les operacions de conjunts els nuls sí que es consideren iguals entre si. En canvi, una restricció UNIQUE els considera diferents i n'admet diversos.
  • El nul es propaga per l'aritmètica i la concatenació: preu * NULL és NULL, no zero. COALESCE, NULLIF i CASE (06-04 i 06-05) són les eines per substituir-lo en presentar.
  • Els cinc casos pendents del curs —= NULL, <> 4, l'INNER JOIN que perdia comandes, el WHERE que degradava el LEFT JOIN i el NOT IN amb nul— són el mateix cas: una comparació que dona UNKNOWN i una fila que desapareix sense avís.
  • En dissenyar, tria conscientment entre NULL, valor sentinella i NOT NULL DEFAULT. Els sentinelles no eliminen el problema: l'amaguen dins de les mitjanes i els recomptes.

Amb això s'acaba la primera meitat del mòdul. Ja saps filtrar amb precisió: per patrons, per llistes, per rangs i per absència de valor. A la lliçó següent, funcions d'agregació, comença la segona meitat i canvia la naturalesa del que fas: fins ara cada fila del resultat venia d'una fila de la taula; a partir d'ara moltes files hi entraran i en sortirà un sol valor. Veuràs COUNT, SUM, AVG, MIN i MAX, i la primera cosa que n'aprendràs és que totes ignoren els NULL —totes menys COUNT(*)—, cosa que converteix comandes.empleat_id en el millor banc de proves possible: 20, 10 i 3 segons com comptis. I resoldrem, per fi, l'avís repetit tres vegades al mòdul 3 sobre sumar despeses d'enviament després d'unir amb el detall.

Curs de SQL

Mòdul 1: Introducció a SQL

Mòdul 2: Consultes bàsiques de SQL

Mòdul 3: Treballar amb múltiples taules

Mòdul 4: Filtratge avançat de dades

Mòdul 5: Manipulació de dades

Mòdul 6: Funcions avançades de SQL

Mòdul 7: Subconsultes i consultes imbricades

Mòdul 8: Índexs i optimització del rendiment

Mòdul 9: Transaccions i concurrència

Mòdul 10: Temes avançats

Mòdul 11: SQL a la pràctica

Mòdul 12: Projecte final

© Copyright 2026. Tots els drets reservats