Les funcions de cadena servien per presentar. Les numèriques serveixen per calcular, i aquí el marge d'error deixa de ser estètic: un arrodoniment mal fet no queda lleig, costa diners. Aquesta lliçó cobreix les funcions escalars que treballen amb números i, sobretot, les dues trampes que separen un informe correcte d'un que no quadra per cèntims: la divisió entera i l'arrodoniment del .5. Ja has fet servir ROUND des del mòdul 2 sense explicació; aquí toca entendre'l de debò, inclosa la part incòmoda: a PostgreSQL, ROUND(2.5) i ROUND(2.5::DOUBLE PRECISION) no retornen el mateix.

Contingut

  1. Arrodonir i truncar: ROUND, TRUNC, CEIL, FLOOR
  2. L'arrodoniment del .5: NUMERIC enfront de DOUBLE PRECISION
  3. Aritmètica: ABS, SIGN, MOD, POWER, SQRT, logaritmes
  4. GREATEST i LEAST no són MAX i MIN
  5. La divisió entera i els seus tres arranjaments
  6. Precisió: el cèntim perdut de BotigaVerda
  7. Casos reals: IVA, marge, descomptes i ports
  8. RANDOM i generate_series
  9. Taula comparativa per motor
  10. Errors habituals i consells
  11. Exercicis
  12. Conclusió

  1. Arrodonir i truncar

Funció Què fa Exemple Resultat
ROUND(x) Arrodoneix a l'enter més proper ROUND(12.789) 13
ROUND(x, n) Arrodoneix a n decimals ROUND(12.789, 2) 12.79
TRUNC(x) Talla cap a zero, sense arrodonir TRUNC(12.789) 12
TRUNC(x, n) Talla a n decimals TRUNC(12.789, 2) 12.78
CEIL(x) / CEILING(x) Enter igual o superior CEIL(12.001) 13
FLOOR(x) Enter igual o inferior FLOOR(12.999) 12

Amb números negatius les quatre se separen, i és aquí on la gent s'equivoca:

SELECT TRUNC(-12.789) AS trunc, ROUND(-12.789) AS round,
       CEIL(-12.789)  AS ceil,  FLOOR(-12.789) AS floor;
trunc round ceil floor
-12 -13 -12 -13

TRUNC talla cap a zero (-12.789-12), així que coincideix amb CEIL en els negatius i amb FLOOR en els positius; FLOOR va sempre cap avall, cap a -∞ (-13); i ROUND va al més proper, que aquí també és -13.

Trampa de tipus. ROUND(x, n) i TRUNC(x, n) només existeixen per a NUMERIC. Si x és DOUBLE PRECISION, PostgreSQL falla amb ERROR: function round(double precision, integer) does not exist. La solució és ROUND(x::NUMERIC, 2), i que la versió de dos arguments no existeixi per a coma flotant no és cap descuit: és el motor dient-te que arrodonir un DOUBLE PRECISION a dos decimals no significa el que et penses. La secció 6 ho demostra.

  1. L'arrodoniment del .5: NUMERIC enfront de DOUBLE PRECISION

Quan el valor cau exactament al mig, cal decidir cap a on. I PostgreSQL ho decideix de manera diferent segons el tipus:

SELECT ROUND(0.5)          AS num_05, ROUND(2.5)          AS num_25,
       ROUND(0.5::FLOAT8)  AS flt_05, ROUND(2.5::FLOAT8)  AS flt_25;
num_05 num_25 flt_05 flt_25
1 3 0 2

Dues respostes diferents per a la mateixa pregunta. Els literals 0.5 i 2.5 són NUMERIC, i NUMERIC arrodoneix half-up (mig cap amunt, allunyant-se del zero). En convertir-los a coma flotant, PostgreSQL delega en la funció rint() del sistema, que arrodoneix half-even: al parell més proper.

Estratègia Nom habitual 0.5 1.5 2.5 3.5 -2.5
Half-up (NUMERIC) Arrodoniment comercial 1 2 3 4 -3
Half-even (DOUBLE PRECISION) Arrodoniment del banquer 0 2 2 4 -2

Cap de les dues és "la correcta". El half-even no esbiaixa les sumes —la meitat dels empats puja i la meitat baixa— i el half-up és el que ensenyen a l'escola i el que la majoria de normatives comptables exigeixen. El problema no és triar malament, és triar sense saber-ho: si la meitat del teu informe fa servir NUMERIC i l'altra DOUBLE PRECISION, els teus totals no quadraran entre si i ningú no sabrà per què.

La regla del curs: els diners són NUMERIC (01-04). Arrodoneix sempre NUMERIC, i així el .5 puja. Si reps un DOUBLE PRECISION de fora, converteix-lo amb ::NUMERIC abans d'arrodonir, no després.

I una precisió important: el famós ROUND(1.005, 2) es comporta bé amb NUMERIC —retorna 1.01— perquè 1.005 es desa exacte. En DOUBLE PRECISION aquest literal és realment 1.00499999999999989… i arrodoniria a 1.00. No és una fallada de ROUND: és que el número que li dones ja no és 1.005.

  1. Aritmètica: ABS, SIGN, MOD, POWER, SQRT, logaritmes

Funció Què fa Exemple Resultat
ABS(x) Valor absolut ABS(-12.789) 12.789
SIGN(x) -1, 0 o 1 segons el signe SIGN(-12.789) -1
MOD(a, b), a % b Residu de la divisió entera MOD(10, 3) 1
POWER(a, b) a elevat a b POWER(2, 10) 1024
SQRT(x) Arrel quadrada SQRT(16) 4
EXP(x) / LN(x) Exponencial / logaritme natural EXP(0) 1
LOG(x) Logaritme en base 10 LOG(100) 2
LOG(b, x) Logaritme en base b LOG(2, 8) 3

Dos avisos sobre aquesta taula. Els resultats que hi veus són els valors matemàtics: PostgreSQL calcula aquestes funcions sobre NUMERIC amb 16 dígits significatius, de manera que SELECT SQRT(16); mostra en realitat 4.0000000000000000. No és cap error, és la precisió de treball; embolcalla-ho amb ROUND en presentar. I LOG sense base és base 10 a PostgreSQL però és el logaritme natural a MySQL: el mateix SQL dona resultats diferents i cap dels dos motors no avisa.

MOD amb negatius mereix una nota: en SQL el residu hereta el signe del dividend, així que MOD(-10, 3) és -1, no 2 (coincideix amb C i Java, i no amb Python). El seu ús pràctic és partir un conjunt en lots:

SELECT id, MOD(id, 4) AS lot, data_comanda, estat
FROM comandes WHERE MOD(id, 4) = 0 ORDER BY id;
id lot data_comanda estat
4 0 2025-04-19 lliurat
8 0 2025-06-28 lliurat
12 0 2025-10-01 lliurat
16 0 2025-12-19 enviat
20 0 2026-02-21 pendent

És el patró dels backfills per lots que 05-06 recomanava: quatre processos concurrents, cadascun amb el seu residu, sense solapar-se.

  1. GREATEST i LEAST no són MAX i MIN

La confusió clàssica del mòdul, que es resol amb la distinció de 06-01:

GREATEST / LEAST MAX / MIN
Família Escalar Agregada
Compara Diverses columnes de la mateixa fila Una columna en moltes files
Files del resultat Les mateixes que hi havia Una per grup
SELECT id, preu, cost,
       GREATEST(preu, cost) AS major_dels_dos,
       LEAST(preu, cost)    AS menor_dels_dos
FROM productes WHERE id IN (1, 5, 15) ORDER BY id;
id preu cost major_dels_dos menor_dels_dos
1 12.50 7.80 12.50 7.80
5 1.95 0.90 1.95 0.90
15 22.00 12.50 22.00 12.50

Tres files. La mateixa consulta amb MAX i MIN en retornaria una, amb el preu més car i el més barat de tot el catàleg (22.00 i 1.95).

El seu ús més freqüent és acotar un valor: GREATEST(stock - 5, 0) mai no baixa de zero i LEAST(descompte, 0.30) mai no supera el 30 %. Un CASE faria el mateix amb quatre línies més (06-05).

Nota de dialecte: davant d'un nul, GREATEST(1, NULL) retorna 1 a PostgreSQL (ignora els nuls) i NULL a MySQL i Oracle. És una diferència real i perillosa a l'hora de portar codi.

  1. La divisió entera i els seus tres arranjaments

SELECT 10 / 3 AS divisio, 10 % 3 AS residu;
divisio residu
3 1

3, no 3.33. Si els dos operands són enters, SQL fa divisió entera i descarta la part decimal: no arrodoneix, trunca. No és cap caprici de PostgreSQL sinó l'estàndard —el tipus del resultat es deriva del dels operands, i INTEGER / INTEGER és INTEGER—. Les tres maneres d'evitar-ho:

SELECT 10.0 / 3                  AS amb_literal_decimal,
       10::NUMERIC / 3           AS amb_cast,
       ROUND(10.0 / 3, 4)        AS arrodonit;
amb_literal_decimal amb_cast arrodonit
3.3333333333333333 3.3333333333333333 3.3333

(1) Un operand amb decimals: 10.0 / 3 — el literal 10.0 és NUMERIC i arrossega l'altre. (2) Conversió explícita: 10::NUMERIC / 3 o CAST(10 AS NUMERIC) / 3, l'única que funciona quan els dos operands són columnes enteres, on no pots "escriure un punt". (3) Multiplicar per 1.0 abans de dividir: 1.0 * quantitat / total, truc vell, portable i legítim.

Els setze decimals de les dues primeres columnes són la precisió de treball de la divisió NUMERIC: com a mínim 16 dígits significatius. Calcula amb aquesta precisió i arrodoneix només en presentar.

El cas real que mossega

"Quantes unitats té de mitjana una línia de comanda?" La resposta són 113 / 47 unitats:

SELECT SUM(quantitat)            AS unitats,        COUNT(*)                 AS linies,
       SUM(quantitat) / COUNT(*) AS mitjana_malament, ROUND(AVG(quantitat), 4) AS mitjana_be
FROM linies_comanda;
unitats linies mitjana_malament mitjana_be
113 47 2 2.4043

SUM(quantitat) és BIGINT i COUNT(*) també, així que la divisió és entera i retorna 2 en lloc de 2,4043: un 17 % d'error, sense avís, en una consulta que sembla òbviament correcta. AVG no cau a la trampa perquè retorna NUMERIC quan l'entrada és entera; aquest és el motiu que existeixi.

  1. Precisió: el cèntim perdut de BotigaVerda

A 01-04 es va dir que els diners van en NUMERIC i mai en coma flotant. Toca demostrar-ho.

SELECT 0.1 + 0.2 AS en_numeric, 0.1::FLOAT8 + 0.2::FLOAT8 AS en_float8,
       (0.1::FLOAT8 + 0.2::FLOAT8) = 0.3::FLOAT8 AS son_iguals;
en_numeric en_float8 son_iguals
0.3 0.30000000000000004 false

REAL i DOUBLE PRECISION desen els números en base 2, i 0,1 no té representació exacta en base 2, igual que 1/3 no la té en base 10. L'error és minúscul… fins que s'acumula sobre dades reals. La facturació de producte de BotigaVerda —la xifra canònica del curs— calculada de les dues maneres:

SELECT SUM(quantitat * preu_unitari * (1 - descompte))                     AS suma_numeric,
       ROUND(SUM(quantitat * preu_unitari * (1 - descompte)), 2)           AS total_numeric,
       SUM(quantitat * preu_unitari::FLOAT8 * (1 - descompte::FLOAT8))     AS suma_float8,
       ROUND(SUM(quantitat * preu_unitari::FLOAT8
                          * (1 - descompte::FLOAT8))::NUMERIC, 2)          AS total_float8
FROM linies_comanda;
suma_numeric total_numeric suma_float8 total_float8
727.9450 727.95 727.9449999999998 727.94

727,95 € enfront de 727,94 €. Un cèntim, sobre 47 línies i 20 comandes: la suma exacta és 727,9450, que en NUMERIC arrodoneix cap amunt, mentre que l'acumulat en coma flotant es queda en 727,9449999999998 i arrodoneix cap avall. I hi ha una cosa pitjor que el cèntim: els últims dígits de suma_float8 depenen de l'ordre en què el motor sumi les files. Un pla paral·lel, un índex diferent o simplement més dades els poden canviar, de manera que la mateixa consulta sobre les mateixes dades pot donar dos resultats diferents. Amb NUMERIC això no passa mai.

NUMERIC(10,2) REAL / DOUBLE PRECISION
Representació Decimal exacta Binària aproximada
Suma de les 47 línies 727.9450 727.9449999999998
Resultat reproduïble? Sempre Depèn de l'ordre de suma
= fiable No: compara amb tolerància
Velocitat / ús Més lenta · diners i quantitats exactes Més ràpida · mesures físiques, estadística

La regla del curs, ara justificada: diners en NUMERIC; calcular amb precisió completa i arrodonir només en presentar. Arrodonir a cada pas intermedi introdueix error d'acumulació; calcular en coma flotant n'introdueix un de pitjor, perquè és impredictible.

  1. Casos reals: IVA, marge, descomptes i ports

7.1. Preu amb IVA i marge

SELECT id, nom, preu, cost,
       ROUND(preu * 1.21, 2)                 AS preu_amb_iva,
       ROUND(preu - cost, 2)                 AS marge,
       ROUND((preu - cost) / preu * 100, 1)  AS marge_pct
FROM productes WHERE id IN (1, 5, 15, 18) ORDER BY id;
id nom preu cost preu_amb_iva marge marge_pct
1 Oli d'oliva verge extra 500 ml 12.50 7.80 15.13 4.70 37.6
5 Tomàquet triturat ecològic 400 g 1.95 0.90 2.36 1.05 53.8
15 Te verd matcha cerimonial 30 g 22.00 12.50 26.62 9.50 43.2
18 Raspall de dents de bambú 3.50 1.20 4.24 2.30 65.7

Fixa't en (preu - cost) / preu * 100: els operands són NUMERIC, així que no hi ha divisió entera. Si preu i cost fossin INTEGER (cèntims, per exemple), aquesta expressió donaria 0 per als vint productes: és l'error de l'apartat 5 disfressat de fórmula de negoci.

7.2. Descomptes aplicats

SELECT id, comanda_id, quantitat, preu_unitari, descompte,
       ROUND(quantitat * preu_unitari, 2)                    AS brut,
       ROUND(quantitat * preu_unitari * descompte, 2)        AS estalvi,
       ROUND(quantitat * preu_unitari * (1 - descompte), 2)  AS import
FROM linies_comanda WHERE descompte > 0 ORDER BY id;
id comanda_id quantitat preu_unitari descompte brut estalvi import
6 3 6 1.95 0.10 11.70 1.17 10.53
18 8 3 12.50 0.05 37.50 1.88 35.63
24 10 2 18.90 0.10 37.80 3.78 34.02
27 11 8 1.95 0.15 15.60 2.34 13.26
39 16 2 11.20 0.05 22.40 1.12 21.28
45 19 6 4.95 0.10 29.70 2.97 26.73

Sis línies amb descompte de les 47, amb un estalvi total de 13,26 €. Observa la línia 18: l'estalvi exacte és 1,875 €, que ROUND(…, 2) puja a 1,88 perquè NUMERIC arrodoneix half-up; en coma flotant hauria donat 1,87.

7.3. Arrodoniment comercial a .95

Màrqueting vol pujar un 10 % i deixar els preus acabats en .95:

SELECT id, nom,
       preu                     AS preu_actual,
       ROUND(preu * 1.10, 2)    AS pujada_bruta,
       FLOOR(preu * 1.10) + 0.95 AS preu_comercial
FROM productes WHERE id IN (2, 6, 15, 18) ORDER BY id;
id nom preu_actual pujada_bruta preu_comercial
2 Arròs integral ecològic 1 kg 3.90 4.29 4.95
6 Crema facial d'àloe vera 50 ml 18.90 20.79 20.95
15 Te verd matcha cerimonial 30 g 22.00 24.20 24.95
18 Raspall de dents de bambú 3.50 3.85 3.95

FLOOR(x) + 0.95 és el patró canònic: part entera més els cèntims que vulguis. Compte amb l'efecte: l'arròs puja de 4,29 a 4,95 (un 15 % extra) i el matcha de 24,20 a 24,95 (un 3 %). L'arrodoniment comercial no és neutre; mesura'l abans d'aplicar-lo.

7.4. Repartiment de les despeses d'enviament per línia

La comanda 1 té tres línies (42,10 € de producte) i 4,95 € de ports. Quant d'enviament toca a cada línia, en proporció al seu import?

SELECT lc.id AS linia,
       ROUND(lc.quantitat * lc.preu_unitari * (1 - lc.descompte), 2)               AS import,
       ROUND(lc.quantitat * lc.preu_unitari * (1 - lc.descompte) / 42.10 * 100, 2) AS pes_pct,
       ROUND(co.despeses_enviament * lc.quantitat * lc.preu_unitari
             * (1 - lc.descompte) / 42.10, 2)                                      AS enviament_imputat
FROM linies_comanda AS lc
JOIN comandes       AS co ON co.id = lc.comanda_id
WHERE lc.comanda_id = 1 ORDER BY lc.id;
linia import pes_pct enviament_imputat
1 23.90 56.77 2.81
2 11.70 27.79 1.38
3 6.50 15.44 0.76

2,81 + 1,38 + 0,76 = 4,95. Quadra. Però no sempre quadra: repeteix el càlcul per a la comanda 3 (4,95 € de ports sobre 29,53 € de producte) i les tres parts arrodonides sumen 4,96 €; per a la comanda 8, 9,91 € en lloc de 9,90 €. Aquest cèntim sobrant és inevitable, perquè arrodonir tres números a dos decimals i sumar-los no té per què donar la suma arrodonida. La solució estàndard és imputar el residu a una línia, normalment la més gran: es calculen n − 1 parts arrodonides i l'última s'obté restant. Aquí hauries de comparar cada línia amb el màxim de la seva comanda, que és una subconsulta correlacionada i arriba a 07-02.

La lliçó que hi ha al darrere: el repartiment proporcional amb arrodoniment sempre produeix residus. Si un informe financer suma partides arrodonides, algú ha de decidir on va el cèntim. Que ho decideixi la teva consulta, no l'atzar.

  1. RANDOM i generate_series

Dues utilitats que reapareixeran més endavant.

Funció Què fa
RANDOM() Un DOUBLE PRECISION aleatori a [0, 1)
generate_series(a, b [, pas]) Genera les files a, a+1, …, b
SELECT n, n * n AS quadrat FROM generate_series(1, 4) AS g(n) ORDER BY n;
n quadrat
1 1
2 4
3 9
4 16

generate_series no llegeix cap taula: fabrica files del no-res. Això la converteix en l'eina per construir informes sense buits —una fila per mes encara que aquell mes no tingui vendes—, que és el que 03-06 va anunciar i el que veuràs a 06-03 amb dates. RANDOM() serveix per a mostreig (ORDER BY RANDOM() LIMIT 10 retorna deu files a l'atzar) i per a dades de prova; no en mostro el resultat perquè canvia a cada execució, i per això mateix no la facis servir mai en un DEFAULT de columna sense pensar-hi (05-06: un DEFAULT volàtil reescriu la taula sencera).

  1. Taula comparativa per motor

Tasca PostgreSQL 16 MySQL 8 SQLite SQL Server Oracle
INTEGER / INTEGER Entera (3) Decimal (3.3333) Entera (3) Entera (3) Decimal (no hi ha INTEGER)
Divisió entera explícita a / b amb enters a DIV b a / b a / b TRUNC(a/b)
Arrodoniment del .5 half-up en NUMERIC, half-even en DOUBLE half-up en DECIMAL, depèn del sistema en DOUBLE half-up (tot és REAL) half-up half-up
Truncar a n decimals TRUNC(x, n) TRUNCATE(x, n) — (fer servir CAST) ROUND(x, n, 1) TRUNC(x, n)
Sostre / terra CEIL, CEILING, FLOOR CEIL, CEILING, FLOOR CEIL, FLOOR (3.35+) CEILING, FLOOR CEIL, FLOOR
Mòdul MOD(a,b), a % b MOD(a,b), a % b a % b a % b MOD(a,b) (no hi ha %)
Logaritme sense base LOG(x) = base 10 LOG(x) = natural LOG(x) = base 10 LOG(x) = natural LOG(b,x) obligatori
GREATEST / LEAST Sí, ignoren nuls Sí, retornen NULL amb un nul MAX(a,b) / MIN(a,b) escalars Sí (2022+) Sí, NULL amb un nul
Aleatori a [0,1) RANDOM() RAND() RANDOM() retorna un enter de 64 bits RAND() DBMS_RANDOM.VALUE
Sèrie d'enters generate_series(a,b) CTE recursiva CTE recursiva GENERATE_SERIES (2022+) CONNECT BY LEVEL

Les tres files que més codi trenquen en migrar: la divisió entera (una consulta que funciona a MySQL retorna zeros a PostgreSQL), el logaritme sense base (mateix SQL, resultat diferent, sense error) i el RANDOM() de SQLite, que retorna un enter enorme amb signe i no un decimal entre 0 i 1.

Errors habituals i consells

  • Dividir dos enters i esperar decimals. 10 / 3 és 3. Converteix un operand a NUMERIC, o fes servir AVG quan estiguis calculant una mitjana.
  • Fer servir ROUND(x, n) sobre un DOUBLE PRECISION. Aquesta funció no existeix, i l'ERROR avisa d'una cosa més profunda: no hauries de tenir diners en coma flotant.
  • Suposar que ROUND(2.5) dona sempre el mateix. 3 en NUMERIC, 2 en DOUBLE PRECISION.
  • Desar diners en REAL o DOUBLE PRECISION. L'error s'acumula, els totals deixen de quadrar i el resultat ni tan sols és reproduïble. I no arrodoneixis a cada pas intermedi: calcula amb precisió completa i arrodoneix només en presentar.
  • Confondre GREATEST/LEAST amb MAX/MIN. Els primers comparen columnes d'una fila; els segons, files d'una columna. I amb nuls es comporten diferent segons el motor.
  • Esperar que TRUNC i FLOOR coincideixin. Només amb positius: TRUNC(-1.5) és -1 i FLOOR(-1.5) és -2. I MOD(-10, 3) és -1, no 2.
  • Sumar imports arrodonits i esperar que quadrin amb el total. No quadren: cal decidir on va el residu.
  • Consell: posa el ::NUMERIC com més aviat millor i el ROUND com més tard millor, i comprova sempre que la suma de les parts és el total. És l'equivalent numèric del "el filtre i la seva negació sumen el total" de 04-03.
  • Consell: fes servir MOD(id, n) per partir un procés en lots disjunts. Simple, determinista i sense taula de control.

Exercicis

Exercici 1

Direcció vol una anàlisi de rendibilitat del catàleg. Per a cada producte actiu, retorna preu, cost, marge absolut, marge percentual amb un decimal i valor de l'inventari (stock * cost) a dos decimals. Ordena per marge percentual descendent i mostra els cinc primers. Quin tipus de producte domina el rànquing i per què?

Exercici 2

Sobre ressenyes.puntuacio: (1) calcula la mitjana global amb 4 decimals i amb 0 decimals; (2) calcula-la un altre cop escrivint SUM(puntuacio) / COUNT(*) — què obtens i per què?; (3) calcula la mitjana per producte arrodonida a 2 decimals, de pitjor a millor. Quants productes hi apareixen i per què no en són 20?

Exercici 3

Un company afirma que aquestes dues consultes sobre linies_comanda són equivalents:

-- A
SELECT ROUND(SUM(quantitat * preu_unitari * (1 - descompte)), 2) AS total FROM linies_comanda;
-- B
SELECT SUM(ROUND(quantitat * preu_unitari * (1 - descompte), 2)) AS total FROM linies_comanda;

(1) Prediu si donaran el mateix número i executa-les. (2) Explica en què es diferencien conceptualment. (3) Quina coincideix amb el que factura de debò la botiga, si cada factura imprimeix l'import de cada línia arrodonit a dos decimals?

Solucions

Solució 1

SELECT id, nom, preu, cost,
       ROUND(preu - cost, 2)                 AS marge,
       ROUND((preu - cost) / preu * 100, 1)  AS marge_pct,
       ROUND(stock * cost, 2)                AS valor_inventari
FROM productes WHERE actiu = TRUE
ORDER BY marge_pct DESC, id LIMIT 5;
id nom preu cost marge marge_pct valor_inventari
18 Raspall de dents de bambú 3.50 1.20 2.30 65.7 288.00
9 Bàlsam labial de calèndula 15 ml 4.60 1.80 2.80 60.9 234.00
11 Fregall vegetal de lufa (pack 3) 5.50 2.20 3.30 60.0 242.00
19 Desodorant natural en barra 50 g 7.80 3.30 4.50 57.7 247.50
7 Xampú sòlid de romaní 80 g 8.40 3.60 4.80 57.1 342.00

(El marge percentual mitjà del catàleg és del 52,03 %.) Hi dominen els productes barats d'higiene i llar: sobre un preu petit, un cost petit deixa un percentatge alt encara que el marge absolut siguin dos euros. Marge percentual i marge absolut ordenen de manera diferent: el matcha, amb 9,50 € de marge, és el que més diners deixa per unitat i no apareix aquí. I el producte 20 no en pot sortir mai: està descatalogat i el WHERE actiu = TRUE l'exclou.

Solució 2

SELECT ROUND(AVG(puntuacio), 4) AS mitjana_4dec, ROUND(AVG(puntuacio), 0) AS mitjana_0dec,
       SUM(puntuacio) AS suma, COUNT(*) AS ressenyes,
       SUM(puntuacio) / COUNT(*) AS mitjana_entera
FROM ressenyes;
mitjana_4dec mitjana_0dec suma ressenyes mitjana_entera
4.0833 4 49 12 4

1 i 2. AVG(puntuacio) retorna 4.0833333333333333: puntuacio és SMALLINT, però AVG sobre enters retorna NUMERIC. En canvi SUM(puntuacio) / COUNT(*) divideix 49 entre 12 com a enters i retorna 4. La coincidència amb l'arrodoniment a 0 decimals és casual: si la mitjana fos 4,9, la divisió entera continuaria donant 4 i l'arrodoniment donaria 5. 3.

SELECT producte_id, COUNT(*) AS ressenyes, ROUND(AVG(puntuacio), 2) AS mitjana
FROM ressenyes GROUP BY producte_id ORDER BY mitjana, producte_id;
producte_id ressenyes mitjana
16 1 2.00
5 1 3.00
12 1 3.00
10 1 4.00
18 1 4.00
2 2 4.50
6 2 4.50
1 2 5.00
15 1 5.00

9 productes, no 20: ressenyes només té 12 files sobre 9 productes diferents, i un GROUP BY sobre aquesta taula no pot inventar els 11 que ningú no ha valorat. Perquè hi apareguessin caldria partir de productes amb un LEFT JOIN (03-03), i la seva mitjana seria NULL —que no és 0, i que es presenta amb COALESCE (06-04)—.

Solució 3

1. Totes dues retornen 727.95, però per sort: els 47 imports de línia de BotigaVerda tenen com a molt tres decimals i els residus d'arrodoniment es compensen. Amb altres dades no coincidirien. 2. No són la mateixa pregunta. A suma primer i arrodoneix al final: és l'import matemàticament exacte, arrodonit una sola vegada, amb un error màxim de mig cèntim sempre. B arrodoneix cada línia i després suma: introdueix fins a mig cèntim d'error per línia, així que amb 47 línies l'error acumulat pot arribar a uns 24 cèntims i amb un milió de línies, a 5.000 €.

3. I aquí l'enunciat inverteix la resposta: si la factura que rep el client imprimeix cada línia arrodonida a dos decimals, el que la botiga cobra de debò és la suma d'aquestes línies arrodonides, és a dir B. En aquest cas B no és cap error, és la definició de l'import facturat, i A seria la xifra que no quadra amb els papers. La moralitat no és "arrodoneix al final sempre" sinó "arrodoneix on ho fa el negoci": la regla del curs val per a informes analítics, mentre que en facturació l'arrodoniment per línia és un requisit legal a molts països. El que mai no és acceptable és no saber quina de les dues estàs calculant.

Conclusió

Ja calcules amb criteri:

  • Arrodoneixes amb ROUND, talles amb TRUNC i aproximes amb CEIL/FLOOR, sabent que amb negatius les quatre se separen. Coneixes la trampa del .5: NUMERIC arrodoneix half-up i DOUBLE PRECISION half-even, per la qual cosa ROUND(2.5) pot ser 3 o 2 segons el tipus. I ROUND(x, n) no existeix per a coma flotant, cosa que és un advertiment, no una limitació.
  • Manegues ABS, SIGN, MOD (el residu del qual hereta el signe del dividend), POWER, SQRT i els logaritmes, amb l'avís que LOG sense base no significa el mateix a PostgreSQL que a MySQL. I distingeixes GREATEST/LEAST —escalars— de MAX/MIN —agregades—.
  • Evites la divisió entera amb un literal decimal, un ::NUMERIC o un 1.0 *, i la saps reconèixer: SUM(quantitat) / COUNT(*) donava 2 on la mitjana és 2,4043. I has vist el cèntim perdut amb dades reals: 727,95 € en NUMERIC enfront de 727,94 € en coma flotant, amb l'agreujant que el resultat en coma flotant ni tan sols és reproduïble.
  • Apliques tot això a BotigaVerda: IVA, marges, descomptes, arrodoniment comercial a .95 i repartiment proporcional de ports, amb l'advertiment que el repartiment arrodonit no sempre quadra i algú ha de decidir on va el residu.

A la lliçó següent, funcions de data i hora, arriba el tipus de dada que més preguntes de negoci respon i més errors amaga. Veuràs per què BotigaVerda ho desa tot com a DATE i què canviaria amb TIMESTAMP i zones horàries, la diferència entre NOW() i CLOCK_TIMESTAMP(), l'aritmètica amb INTERVAL, i per fi EXTRACT i DATE_TRUNC, que són el deute que 04-05 va deixar pendent: agrupar la facturació per mes i per trimestre sense escriure vint condicions a mà. I tornarà generate_series, aquesta vegada perquè un informe mensual mostri els mesos en què no es va vendre res.

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