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
- Què és
NULLi què no és - Els tres
NULLde BotigaVerda i què signifiquen - La lògica de tres valors:
TRUE,FALSEiUNKNOWN - Les taules de veritat completes
IS NULLiIS NOT NULLIS DISTINCT FROMiIS NOT DISTINCT FROM- On
NULLsí que s'agrupa ambNULL NULLi la restriccióUNIQUENULLen concatenacions i en aritmètica- La resolució dels casos pendents
- Dissenyar amb
NULL, amb sentinelles o ambNOT NULL - Errors habituals i consells
- Exercicis
- Conclusió
- Què és
NULL i què no és
NULL i què no ésNULL é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çó:
NULLno significa "buit", significa "desconegut". I tot el que compares amb alguna cosa desconeguda produeix un resultat desconegut.
- Els tres
NULL de BotigaVerda i què signifiquen
NULL de BotigaVerda i què signifiquenEn 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.
| 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 |
Sí |
| Dada desconeguda | Un cost que el proveïdor no ha comunicat |
Sí |
| Frontera estructural | cap_id de la direcció general |
Sí |
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ó |
- La lògica de tres valors:
TRUE, FALSE i UNKNOWN
TRUE, FALSE i UNKNOWNFora d'SQL, una condició és certa o falsa. En SQL hi ha tres resultats possibles, perquè una comparació amb NULL no es pot decidir:
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:
WHEREconserva únicament les files la condició de les quals s'avalua aTRUE. Les que donenFALSEes descarten, i les que donenUNKNOWNtambé.
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.
- 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 iestat <> 'lliurat'en dona 6, i la taula en té 20, la lògica tanca perquèestatésNOT NULL. Ambempleat_id = 4(4 files) iempleat_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ó:
PostgreSQL podria avaluar la divisió primer. La forma segura és CASE o NULLIF, tots dos del mòdul 6.
IS NULL i IS NOT NULL
IS NULL i IS NOT NULLCom 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.
| 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 sí 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
= NULLes comporti comIS NULL—SQL Server ho fa ambSET 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.
IS DISTINCT FROM i IS NOT DISTINCT FROM
IS DISTINCT FROM i IS NOT DISTINCT FROMDe 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:
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 aIS NOT DISTINCT FROM. SQL Server no tenia res fins al 2022, que va introduirIS [NOT] DISTINCT FROM; en versions anteriors cal escriure la forma llarga ambOR ... IS NULL.
- On
NULL sí que s'agrupa amb NULL
NULL sí que s'agrupa amb NULLAquí 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:
| 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:
| 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):
| 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 |
Sí | Comparació segura davant dels nuls |
DISTINCT / DISTINCT ON |
Sí | Tots els nuls col·lapsen en un |
GROUP BY |
Sí | Els nuls formen un grup |
ORDER BY |
Sí | Van tots junts (NULLS FIRST/LAST) |
UNION, INTERSECT, EXCEPT |
Sí | 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.
NULL i la restricció UNIQUE
NULL i la restricció UNIQUEConseqüè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 UNIQUE —categories.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.
NULL en concatenacions i en aritmètica
NULL en concatenacions i en aritmèticaLa 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à.
- 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 <> 4 → UNKNOWN → 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.
- Dissenyar amb
NULL, amb sentinelles o amb NOT NULL
NULL, amb sentinelles o amb NOT NULLQuan 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:
- 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. - L'absència significa alguna cosa per al negoci? Si sí,
NULLi documenta-ho. "Comanda web" és una informació valuosa, no un buit. - Estàs temptat de fer servir
-1,0,'N/A'o'9999-12-31'? Compte: això és un sentinella disfressat.AVGen farà la mitjana,MINel retornarà com a mínim iCOUNTel comptarà. BotigaVerda seria un desastre si el canal web s'hagués codificat comempleat_id = 0: qualsevol "mitjana de comandes per comercial" quedaria enverinada.
La postura sensata: els
NULLsón part del model relacional i amagar-los amb sentinelles no elimina el problema, només el fa invisible. Fes servirNOT NULLsempre que puguis justificar un valor real per defecte; fes servirNULLquan 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
= NULLo<> NULL. Zero files sempre. ÉsIS NULL/IS NOT NULL. - Creure que
NULLés zero o cadena buida.NULL + 5ésNULL;0 + 5és5.NULL || 'a'ésNULL;'' || 'a'és'a'. - Oblidar que
<>,NOT IN,NOT LIKEiNOT BETWEENdescarten les files nul·les. Comprova sempre que la condició i la seva negació sumin el total. - Fer servir
NOT INamb una llista que pot portar unNULL. Zero files, garantides (04-02). - Posar una condició sobre la taula dreta al
WHEREd'unLEFT JOIN. Els nuls fabricats no la superen i elLEFTes degrada aINNER(03-03). - Suposar que si
AésNULLaleshoresNOT Aés cert.NOT UNKNOWNésUNKNOWN. - Interpretar una cel·la buida d'un informe com un zero. Pot ser "no aplica".
COALESCE(06-04) ho fa explícit. - Esperar que
UNIQUEimpedeixi diverses files nul·les. No ho fa, llevat deNULLS NOT DISTINCTa PostgreSQL 15+ o a SQL Server, que va a l'inrevés. - Confiar en l'ordre d'avaluació d'un
ANDper protegir-se d'un nul o d'una divisió per zero. El planificador reordena. Fes servirCASEoNULLIF(mòdul 6). - Consell: en llegir un esquema, mira primer quines columnes admeten
NULL.\d taulaapsqlho 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 FROMper defecte en comparar dues columnes que admeten nuls. T'estalvia l'OR ... IS NULLi 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:
- Quants clients van arribar per recomanació i quants pel seu compte? Escriu dues consultes i comprova que sumen 15.
- 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.)
- 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 |
- Què està mesurant realment aquella consulta i per què el 0 no demostra el que ell es pensa?
- Escriu la versió que compta de debò les comandes l'
empleat_idde les quals no és ni 4, ni 5, ni 6 (comptant els nuls com a "no és cap dels tres"). Quantes en són? - Escriu la versió que compta les comandes amb un
empleat_idpresent 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 sí 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:
| clients_referits |
|---|
| 8 |
| 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)*:
- El
LEFT JOINno va trobar parella. Per als set clients ambreferit_per_idnul, la condicióc.referit_per_id = ref.iddonaUNKNOWNi no casa amb cap fila, així que el motor omple totes les columnes derefambNULL(03-03, secció 3). - La concatenació propaga el nul. Encara que només una de les dues columnes fos nul·la,
ref.nom || ' ' || ref.cognomsdonariaNULLigualment, 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 altreNULL. Significa desconegut.- A BotigaVerda hi ha tres
NULLamb tres significats de negoci diferents —canal web, client espontani, cim de l'organigrama— més els que fabrica el motor a cadaLEFT JOIN. - SQL fa servir lògica de tres valors:
TRUE,FALSEiUNKNOWN.WHERE,ONiHAVINGconserven només el que ésTRUE, així queUNKNOWNes descarta igual queFALSE— però en negar-los es comporten diferent, perquèNOT UNKNOWNcontinua sentUNKNOWN. - Les dues cel·les que cal memoritzar són
TRUE OR UNKNOWN=TRUEiTRUE AND UNKNOWN=UNKNOWN. D'elles en surt tota l'asimetria entreINiNOT IN. IS NULL/IS NOT NULLsón predicats, no comparacions, i sí que parteixen la taula en dues meitats exactes.IS DISTINCT FROMcompara 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ò aDISTINCT,GROUP BY,ORDER BYi a les operacions de conjunts els nuls sí que es consideren iguals entre si. En canvi, una restriccióUNIQUEels considera diferents i n'admet diversos. - El nul es propaga per l'aritmètica i la concatenació:
preu * NULLésNULL, no zero.COALESCE,NULLIFiCASE(06-04 i 06-05) són les eines per substituir-lo en presentar. - Els cinc casos pendents del curs —
= NULL,<> 4, l'INNER JOINque perdia comandes, elWHEREque degradava elLEFT JOINi elNOT INamb nul— són el mateix cas: una comparació que donaUNKNOWNi una fila que desapareix sense avís. - En dissenyar, tria conscientment entre
NULL, valor sentinella iNOT 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
- Què és SQL?
- Configurar el teu entorn SQL
- Sintaxi bàsica de SQL
- Entendre bases de dades i taules
- El model relacional: claus primàries i foranes
- La base de dades del curs: BotigaVerda
Mòdul 2: Consultes bàsiques de SQL
- Instrucció SELECT
- Àlies, expressions i columnes calculades
- Filtrar dades amb WHERE
- DISTINCT i eliminació de duplicats
- Ordenar dades amb ORDER BY
- Limitar resultats amb LIMIT
Mòdul 3: Treballar amb múltiples taules
- Operacions JOIN
- INNER JOIN
- LEFT JOIN
- RIGHT JOIN
- FULL OUTER JOIN
- SELF JOIN i CROSS JOIN
- Unions de conjunts: UNION, INTERSECT i EXCEPT
Mòdul 4: Filtratge avançat de dades
- Utilitzar LIKE per a coincidència de patrons
- Operadors IN i BETWEEN
- Valors NULL i IS NULL
- Funcions d'agregació: COUNT, SUM, AVG, MIN i MAX
- Agregar dades amb GROUP BY
- Clàusula HAVING
Mòdul 5: Manipulació de dades
- Crear taules i restriccions amb CREATE TABLE
- Instrucció INSERT
- Instrucció UPDATE
- Instrucció DELETE
- Instrucció UPSERT (MERGE)
- Modificar l'esquema: ALTER TABLE i migracions segures
Mòdul 6: Funcions avançades de SQL
- Funcions de cadena
- Funcions numèriques
- Funcions de data i hora
- Conversió de tipus i tractament dels NULL: CAST i COALESCE
- Expressions condicionals
Mòdul 7: Subconsultes i consultes imbricades
- Introducció a les subconsultes
- Subconsultes correlacionades
- EXISTS i NOT EXISTS
- Utilitzar subconsultes en les clàusules SELECT, FROM i WHERE
- Subconsultes o JOIN: quin triar
Mòdul 8: Índexs i optimització del rendiment
- Entendre els índexs
- Creació i gestió d'índexs
- Tipus d'índex i quan no indexar
- Tècniques d'optimització de consultes
- Anàlisi del rendiment de les consultes
Mòdul 9: Transaccions i concurrència
- Introducció a les transaccions
- Propietats ACID
- Instruccions de control de transaccions
- Nivells d'aïllament i anomalies de concurrència
- Gestió de la concurrència: bloquejos i interbloquejos
Mòdul 10: Temes avançats
- Vistes
- Expressions de taula comunes (CTE)
- Funcions de finestra
- Procediments emmagatzemats
- Disparadors (triggers)
- JSON i dades semiestructurades
Mòdul 11: SQL a la pràctica
- Casos d'ús al món real
- Bones pràctiques
- Seguretat: injecció SQL, permisos i rols
- SQL per a l'anàlisi de dades
- SQL en el desenvolupament web
