09-05 tancava prometent "l'arsenal que fa mantenible tot l'anterior", i la primera eina d'aquest arsenal és la més senzilla de totes: donar nom a una consulta. Una vista és exactament això —una consulta desada a la base de dades amb un nom— i no sembla gran cosa fins que comptes quantes vegades has copiat en aquest curs el mateix JOIN de quatre taules i la mateixa expressió quantitat * preu_unitari * (1 - descompte).
En aquesta lliçó veuràs què és i què no és una vista (no desa dades: desa la definició), com crear-la, reemplaçar-la i esborrar-la amb les seves regles exactes, els cinc motius reals pels quals es fan servir —amb un cas de BotigaVerda per a cadascun, inclòs l'esborrat lògic que 05-04 va deixar pendent—, quan PostgreSQL deixa escriure a través d'una vista i què és WITH CHECK OPTION, per què una vista no és una memòria cau i què passa en imbricar-les, i les vistes materialitzades que 08-04 va remetre aquí: les que sí que desen dades, amb el seu REFRESH i el seu preu.
Contingut
- Què és una vista i què no és
CREATE VIEW,CREATE OR REPLACE VIEW,DROP VIEW- Els cinc motius, amb un cas cadascun
- Vistes actualitzables i
WITH CHECK OPTION - Rendiment: la vista s'expandeix, no es cacheja
- Vistes materialitzades
- Metadades: on viuen les vistes
- Errors habituals i consells
- Exercicis
- Conclusió
- Què és una vista i què no és
Una vista és una consulta
SELECTemmagatzemada a l'esquema amb un nom. Quan la consultes, el motor substitueix el nom per la seva definició i executa la consulta resultant contra les taules reals.
| Afirmació | És certa? | Per què |
|---|---|---|
| Una vista desa files | No | Desa el text del SELECT. Ocupa uns bytes, no megues |
| Una vista retorna dades al dia | Sí | S'executa en el moment de consultar-la, contra les taules actuals |
| Una vista accelera una consulta lenta | No | Executa la mateixa feina. El que accelera és una vista materialitzada (apartat 6) |
| Es consulta com una taula | Sí | SELECT, WHERE, JOIN, GROUP BY... tot el del curs funciona sobre ella |
flowchart LR
A["SELECT * FROM v_detall_vendes<br/>WHERE pais = 'França'"] --> B["el planificador<br/><b>expandeix</b> la vista"]
B --> C["SELECT ... FROM linies_comanda lc<br/>JOIN comandes co ... JOIN clients c ...<br/>WHERE c.pais = 'França'"]
C --> D["taules reals"]
Aquest diagrama és tota la lliçó en una imatge: la vista desapareix abans d'executar-se. És sucre sintàctic amb nom i permisos propis, no una capa d'emmagatzematge.
CREATE VIEW, CREATE OR REPLACE VIEW, DROP VIEW
CREATE VIEW, CREATE OR REPLACE VIEW, DROP VIEWCREATE VIEW v_productes_actius AS
SELECT p.id, p.nom, p.categoria_id, p.preu, p.stock
FROM productes AS p
WHERE p.actiu = TRUE;(Totes les vistes d'aquesta lliçó —v_productes_actius, v_detall_vendes, v_vendes_per_categoria, v_clients_public, mv_vendes_mensuals— són objectes d'exemple d'aquesta lliçó: no formen part de l'esquema canònic de BotigaVerda de 01-06.)
Les tres operacions i les seves regles:
| Sentència | Què fa | La regla que sorprèn |
|---|---|---|
CREATE VIEW v AS SELECT ... |
La crea | Falla si ja existeix |
CREATE OR REPLACE VIEW v AS SELECT ... |
La crea o la redefineix | Només pot afegir columnes al final. No en pot treure, ni reanomenar-les, reordenar-les o canviar-los el tipus |
DROP VIEW v |
L'esborra | Falla si una altra vista en depèn, tret que hi posis CASCADE |
Aquesta restricció d'OR REPLACE és la que fa perdre més temps:
-- ⚠️ INCORRECTA: intenta reanomenar una columna existent
CREATE OR REPLACE VIEW v_productes_actius AS
SELECT p.id, p.nom AS producte, p.categoria_id, p.preu, p.stock
FROM productes AS p WHERE p.actiu = TRUE;ERROR: cannot change name of view column "nom" to "producte" HINT: Use ALTER VIEW ... RENAME COLUMN ... to change name of view column instead.
En canvi, afegir al final sí que es pot: SELECT p.id, p.nom, p.categoria_id, p.preu, p.stock, p.proveidor_id funciona sense protestar.
La raó és la mateixa que feia delicat un ALTER TABLE a 05-06: altres consultes i altres vistes depenen de la posició i del tipus de cada columna. Per canviar la forma d'una vista cal fer DROP VIEW + CREATE VIEW, i això obliga a recrear també tot el que en depenia. ALTER VIEW existeix, però només per al que és perifèric: reanomenar la vista o una columna, canviar de propietari o d'esquema.
- Els cinc motius, amb un cas cadascun
3.1. Simplificar: la consulta canònica de detall
Fa des del mòdul 3 que escrius aquestes quatre línies de JOIN. S'escriuen una vegada:
CREATE VIEW v_detall_vendes AS
SELECT lc.id AS linia_id, co.id AS comanda_id, co.data_comanda, co.estat,
c.id AS client_id, c.nom || ' ' || c.cognoms AS client, c.pais,
p.id AS producte_id, p.nom AS producte, p.categoria_id,
lc.quantitat, lc.preu_unitari, lc.descompte,
ROUND(lc.quantitat * lc.preu_unitari * (1 - lc.descompte), 2) AS import
FROM linies_comanda AS lc
JOIN comandes AS co ON lc.comanda_id = co.id
JOIN clients AS c ON co.client_id = c.id
JOIN productes AS p ON lc.producte_id = p.id;I a partir d'aquí, una consulta de negoci es llegeix com una frase:
SELECT linia_id, comanda_id, data_comanda, client, producte, quantitat, import
FROM v_detall_vendes
ORDER BY linia_id
LIMIT 5;| linia_id | comanda_id | data_comanda | client | producte | quantitat | import |
|---|---|---|---|---|---|---|
| 1 | 1 | 2025-03-04 | Lucía Martínez Soler | Oli d'oliva verge extra 500 ml | 2 | 23.90 |
| 2 | 1 | 2025-03-04 | Lucía Martínez Soler | Arròs integral ecològic 1 kg | 3 | 11.70 |
| 3 | 1 | 2025-03-04 | Lucía Martínez Soler | Infusió de camamilla ecològica 20 u | 2 | 6.50 |
| 4 | 2 | 2025-03-12 | Carlos Ferrer Ibáñez | Crema facial d'àloe vera 50 ml | 1 | 17.50 |
| 5 | 2 | 2025-03-12 | Carlos Ferrer Ibáñez | Bàlsam labial de calèndula 15 ml | 2 | 9.20 |
(5 primeres de 47 files.) I SELECT COUNT(*), ROUND(SUM(import), 2) FROM v_detall_vendes retorna 47 i 727.95: les xifres canòniques del curs, ara a un SELECT de distància.
3.2. Estandarditzar una mètrica
Aquest motiu és més important que l'anterior i es veu pitjor. A la vista hi ha una decisió de negoci escrita una sola vegada: l'import d'una línia és quantitat * preu_unitari * (1 - descompte), arrodonit a dos decimals, sense despeses d'enviament. Si aquesta fórmula viu copiada en catorze informes, tard o d'hora tres d'ells oblidaran el (1 - descompte) i la direcció rebrà tres xifres diferents de facturació a la mateixa reunió.
Una vista és el lloc on es defineix una mètrica. La versió agregada, recolzada en l'anterior:
CREATE VIEW v_vendes_per_categoria AS
SELECT cat.id AS categoria_id, cat.nom AS categoria,
COUNT(DISTINCT dv.comanda_id) AS comandes,
SUM(dv.quantitat) AS unitats,
ROUND(SUM(dv.import), 2) AS facturacio
FROM v_detall_vendes AS dv
JOIN categories AS cat ON cat.id = dv.categoria_id
GROUP BY cat.id, cat.nom;
SELECT * FROM v_vendes_per_categoria ORDER BY facturacio DESC;| categoria_id | categoria | comandes | unitats | facturacio |
|---|---|---|---|---|
| 1 | Alimentació | 10 | 49 | 256.27 |
| 4 | Begudes | 8 | 29 | 195.28 |
| 2 | Cosmètica natural | 6 | 16 | 156.32 |
| 3 | Llar sostenible | 4 | 10 | 88.58 |
| 5 | Higiene personal | 3 | 9 | 31.50 |
Les cinc categories amb vendes i les seves xifres canòniques. Complements no hi apareix perquè el JOIN sobre les vendes la descarta (04-05); per veure-la amb 0 caldria partir de categories amb un LEFT JOIN. Fixa't a més que aquesta vista està construïda sobre una altra vista: és legal i molt còmode, amb el matís de l'apartat 5.
3.3. Encapsular l'esborrat lògic
Aquí es tanca la promesa de 05-04. L'esborrat lògic amb actiu = FALSE tenia un inconvenient enorme: cal recordar-se d'escriure WHERE actiu a tot arreu, i el dia que algú se n'oblidi, un producte descatalogat apareixerà a la botiga.
La vista v_productes_actius de l'apartat 2 retorna 19 de les 20 files de productes: les Càpsules d'espirulina (producte 20, actiu = FALSE) no hi són, i no hi ha manera que s'hi colin. La regla operativa és senzilla: l'aplicació consulta la vista; només el manteniment del catàleg toca la taula. El filtre deixa de ser una cosa que cal recordar i passa a ser una cosa que hi és.
3.4. Desacoblar l'aplicació de l'esquema físic
A 05-06 vas veure el patró expand/contract: per reanomenar una columna sense aturar el servei s'afegeix la nova, s'escriuen totes dues durant un temps i es retira la vella. Suposa ara que productes.nom passa a dir-se nom_comercial. Les consultes de l'aplicació que apuntaven a productes.nom es trenquen totes; les que apuntaven a la vista no, perquè n'hi ha prou amb redefinir-la amb SELECT p.nom_comercial AS nom, ... i el canvi queda absorbit allà dins.
La vista actua com a contracte estable: per fora continua havent-hi una columna nom, i per dins l'esquema pot evolucionar. És la mateixa idea que una interfície en programació, i és el motiu pel qual molts equips exposen als informes i a les eines de BI només vistes, mai taules.
3.5. Exposar només una part
Una vista pot ometre columnes i files. clients té email, que és una dada personal, i referit_per_id, que és informació comercial interna:
CREATE VIEW v_clients_public AS
SELECT c.id, c.nom, c.ciutat, c.pais, c.data_registre
FROM clients AS c;Qui la consulti no veurà mai cap adreça de correu electrònic, perquè no és a la vista. El mateix amb les files: WHERE pais = 'Espanya' a la definició crea una vista que només mostra el mercat nacional.
Això és una eina de seguretat, i aquí només l'esmentem. Perquè serveixi d'alguna cosa cal treure el permís sobre la taula i donar-lo sobre la vista —
GRANT SELECT ON v_clients_public TO ...—, i això, juntament amb els rols i el control d'accés a nivell de fila, és matèria d'11-03.
- Vistes actualitzables i
WITH CHECK OPTION
WITH CHECK OPTIONSorpresa raonable: sobre una vista de vegades s'hi pot escriure. PostgreSQL la considera automàticament actualitzable —INSERT, UPDATE i DELETE funcionen sense més— quan compleix totes aquestes condicions:
| Requisit | v_productes_actius |
v_detall_vendes |
v_vendes_per_categoria |
|---|---|---|---|
Exactament una taula o vista al FROM |
✅ | ❌ (quatre) | ❌ |
Sense GROUP BY, HAVING, DISTINCT, LIMIT, OFFSET |
✅ | ✅ | ❌ (GROUP BY) |
Sense UNION, INTERSECT, EXCEPT |
✅ | ✅ | ✅ |
Sense funcions de finestra ni agregats al SELECT |
✅ | ✅ | ❌ |
| Les columnes escrites són referències simples a columnes, no expressions | ✅ | ❌ (import és calculada) |
❌ |
| És actualitzable? | Sí | No | No |
UPDATE v_productes_actius SET preu = 13.00 WHERE id = 1; respon UPDATE 1, i el canvi ha anat a la taula productes. I ara el problema interessant. Afegim actiu a la vista —recorda: afegir al final sí que es pot— per poder escriure-la:
CREATE OR REPLACE VIEW v_productes_actius AS
SELECT p.id, p.nom, p.categoria_id, p.preu, p.stock, p.proveidor_id, p.actiu
FROM productes AS p
WHERE p.actiu = TRUE;
UPDATE v_productes_actius SET actiu = FALSE WHERE id = 1; -- UPDATE 1I l'oli d'oliva acaba de desaparèixer de la vista. Has escrit, a través d'una finestra, una fila que la finestra ja no mostra: l'UPDATE diu que ha tocat una fila, però tornar-la a consultar és impossible des d'aquí. A això se'n diu marxar per la porta del darrere, i es tanca així:
CREATE OR REPLACE VIEW v_productes_actius AS
SELECT p.id, p.nom, p.categoria_id, p.preu, p.stock, p.proveidor_id, p.actiu
FROM productes AS p
WHERE p.actiu = TRUE
WITH CHECK OPTION;
UPDATE v_productes_actius SET actiu = FALSE WHERE id = 1;Amb WITH CHECK OPTION, tota fila inserida o modificada ha de continuar complint el WHERE de la vista:
ERROR: new row violates check option for view "v_productes_actius" DETAIL: Failing row contains (1, Oli d'oliva verge extra 500 ml, 1, 1, 12.50, 7.80, 120, f, 2025-01-15).
Té dues variants: WITH LOCAL CHECK OPTION comprova només la condició d'aquesta vista, i WITH CASCADED CHECK OPTION la d'aquesta i la de totes les vistes sobre les quals es recolza —és el que s'aplica si no dius res—.
I per a les vistes que no són actualitzables automàticament —v_detall_vendes, per exemple— PostgreSQL ofereix dues sortides: un disparador INSTEAD OF, que intercepta l'escriptura i decideix a mà quines taules toca, o el sistema de regles (CREATE RULE), més antic i desaconsellat. La forma moderna és el disparador, i és exactament el que veuràs a 10-05.
- Rendiment: la vista s'expandeix, no es cacheja
Aquest és el malentès més car de la lliçó: una vista no desa res i no estalvia ni un microsegon de feina. El planificador la substitueix per la seva definició i optimitza el conjunt.
La bona notícia és que aquesta substitució és intel·ligent: els filtres de fora s'empenyen cap a dins.
Hash Join
Hash Cond: (lc.producte_id = p.id)
-> Hash Join
Hash Cond: (co.client_id = c.id)
-> Hash Join (Hash Cond: lc.comanda_id = co.id)
-> Seq Scan on linies_comanda lc
-> Hash -> Seq Scan on comandes co
-> Hash
-> Seq Scan on clients c
Filter: ((pais)::text = 'França'::text)
-> Hash -> Seq Scan on productes pFixa't en el Filter: pais = 'França': ha baixat fins a l'escaneig de clients. La vista no ha materialitzat 47 files per filtrar-les després; el filtre forma part del pla. I en aquest pla la paraula v_detall_vendes no apareix enlloc, que és justament el que cal entendre.
El risc apareix amb la imbricació. v_vendes_per_categoria es recolza en v_detall_vendes, que uneix quatre taules: dos nivells encara es llegeixen bé. Però en bases de dades amb anys a sobre és habitual trobar una vista sobre una vista sobre una vista, cadascuna amb els seus JOIN i els seus LEFT JOIN "per si de cas"; en expandir-les totes, el planificador es troba amb una consulta de vint taules que no sap reordenar —a partir de join_collapse_limit, 8 per omissió, deixa de provar combinacions— i tria un pla mediocre. Els símptomes són inconfusibles: una consulta que demana tres columnes triga quatre segons i el seu EXPLAIN ANALYZE és ple de taules que no havies demanat.
Tres regles pràctiques: dos nivells d'imbricació com a màxim (si en necessites més, el nivell intermedi probablement vol ser una CTE dins de la consulta final, 10-02, o una vista materialitzada); davant d'una vista lenta, EXPLAIN sobre la consulta que la fa servir, no sobre la vista sola (08-05); i res d'ORDER BY a la definició, que no es garanteix que sobrevisqui a l'expansió.
- Vistes materialitzades
Aquí es tanca la promesa de 08-04. Una vista materialitzada sí que desa les files a disc: és el resultat d'una consulta congelat en el temps.
CREATE MATERIALIZED VIEW mv_vendes_mensuals AS
SELECT to_char(co.data_comanda, 'YYYY-MM') AS mes,
COUNT(DISTINCT co.id) AS comandes,
ROUND(SUM(lc.quantitat * lc.preu_unitari * (1 - lc.descompte)), 2) AS facturacio
FROM comandes AS co
JOIN linies_comanda AS lc ON lc.comanda_id = co.id
GROUP BY 1;La resposta és SELECT 12, no CREATE VIEW: ha executat la consulta i ha escrit les seves 12 files.
| mes | comandes | facturacio |
|---|---|---|
| 2025-03 | 2 | 68.80 |
| 2025-04 | 2 | 61.28 |
| 2025-05 | 2 | 58.85 |
| 2025-06 | 2 | 95.48 |
(4 primeres de 12 files; la sèrie completa, de 2025-03 a 2026-02, és la que faràs servir a 10-03.) Ara es llegeix de disc sense tocar comandes ni linies_comanda. I amb això arriba el defecte: si demà entra la comanda 21, aquesta taula continuarà dient el mateix. Les dades es queden com estaven fins que algú les refresqui.
REFRESH MATERIALIZED VIEW mv_vendes_mensuals; -- bloqueja les lectures
REFRESH MATERIALIZED VIEW CONCURRENTLY mv_vendes_mensuals; -- no les bloqueja| Forma | Bloqueig | Requisit | Cost |
|---|---|---|---|
REFRESH |
ACCESS EXCLUSIVE: ningú no la pot ni llegir mentre es reconstrueix |
Cap | El més ràpid |
REFRESH ... CONCURRENTLY |
EXCLUSIVE: les lectures continuen funcionant (09-05) |
Un índex UNIQUE sobre la vista materialitzada |
Més lent: calcula el resultat nou i aplica les diferències |
Aquest requisit no és un caprici: per aplicar diferències cal poder identificar cada fila, i això exigeix una clau — aquí, CREATE UNIQUE INDEX ux_mv_vendes_mensuals ON mv_vendes_mensuals (mes);. Altres detalls que es descobreixen tard: es pot crear buida amb WITH NO DATA (i llavors consultar-la dona error fins al primer REFRESH); admet els seus propis índexs, com una taula; i no es refresca sola —cal cridar-la des d'un cron, des del planificador de l'aplicació o des de pg_cron.
La taula de decisió
| Vista | Vista materialitzada | Taula de resum | |
|---|---|---|---|
| Desa dades | No | Sí | Sí |
| Frescor | Sempre al dia | Del darrer REFRESH |
La que tu mantinguis |
| Cost de lectura | El de la consulta original | Molt baix | Molt baix |
| Cost d'escriptura | Cap | El REFRESH complet |
Actualització incremental (disparador, procés) |
| Es pot indexar | No (indexa les seves taules) | Sí | Sí |
| Complexitat | Mínima | Baixa: una sentència programada | Alta: cal mantenir-la i es pot desincronitzar |
| Quan | Gairebé sempre. Comença aquí | Informe car que tolera dades de fa una hora | Mètrica que ha de ser instantània i exacta |
Quan compensa una materialitzada, en concret: l'informe triga segons, es consulta moltes vegades al dia, i a ningú no li importa que les dades siguin de fa una hora. El quadre de comandament de direcció de BotigaVerda és l'exemple perfecte; l'estoc disponible del carretó, exactament el contrari.
- Metadades: on viuen les vistes
A psql, \dv llista les vistes, \dm les materialitzades i \d+ v_detall_vendes mostra les columnes i la definició. Des de SQL, SELECT viewname, viewowner FROM pg_views WHERE schemaname = 'public' i, sobretot, SELECT pg_get_viewdef('v_productes_actius'::regclass, TRUE): aquest segon argument a TRUE retorna la definició formatada, i és la manera correcta de versionar vistes —es bolca, es desa al repositori al costat del codi i es revisa com qualsevol altre font—. També existeixen les vistes de l'estàndard (information_schema.views) i, per al graf de dependències —"què es trenca si esborro aquesta vista?"—, pg_depend.
Nota de dialecte:
Motor Vistes OR REPLACEMaterialitzades PostgreSQL Sí, actualitzables si són simples Sí, només afegint columnes al final Sí, amb REFRESHmanualMySQL 8 Sí, actualitzables, amb WITH CHECK OPTIONSí No existeixen: s'emulen amb una taula i un esdeveniment programat SQLite Sí, només lectura No ( DROP+CREATE)No SQL Server Sí CREATE OR ALTER VIEWSí, s'anomenen indexed views i es mantenen soles (amb moltes restriccions) Oracle Sí CREATE OR REPLACE VIEW, sense la restricció de columnesSí, amb refresc incremental ( FAST REFRESH) i automàtic
Errors habituals i consells
- Creure que una vista accelera alguna cosa. Executa exactament la mateixa feina. El que desa dades és la vista materialitzada; una vista normal només desa text.
- Intentar reanomenar o treure una columna amb
CREATE OR REPLACE VIEW.cannot change name of view column. Només es poden afegir columnes al final; per a la resta,DROP+CREATEi recrear el que en depengués. - Imbricar vistes sobre vistes sobre vistes. En expandir-les, el planificador veu una consulta de vint taules i deixa de reordenar (
join_collapse_limit). Màxim dos nivells. - Posar
ORDER BYa la definició. No està garantit que sobrevisqui i fa nosa: l'ordre el demana qui consulta. - Escriure per una vista amb
WHEREsenseWITH CHECK OPTION. Pots inserir o actualitzar files que la vista ja no mostra, i desapareixen davant dels teus ulls. I esperar que unINSERTfuncioni sobre una vista ambJOINoGROUP BY: no és automàticament actualitzable (cannot insert into view), cal un disparadorINSTEAD OF(10-05). - Oblidar el
REFRESH. Una vista materialitzada sense procés de refresc és un informe congelat que la direcció llegeix com si fos d'avui. I sense índexUNIQUEno hi haCONCURRENTLY: cada refresc bloquejarà les lectures. - Fer servir una vista com a mecanisme de seguretat sense treure el permís sobre la taula. No protegeix res: qui pugui llegir
clientscontinuarà llegint els correus (11-03). - Consell: anomena les vistes amb un prefix (
v_,mv_), i versiona'n les definicions ambpg_get_viewdef(..., TRUE). Una vista creada a mà en producció que ningú no té al repositori és deute tècnic invisible. - Consell: una vista per mètrica de negoci. L'objectiu real no és escriure menys, és que la facturació es calculi igual a tot arreu.
Exercicis
Exercici 1
Màrqueting vol treballar amb una vista v_clients_valor que doni, per a cadascun dels 15 clients: id, nom complet, país, nombre de comandes, unitats comprades i facturació (0 si no ha comprat mai).
- Escriu-la. Vigila el tipus de
JOINi elCOALESCE. - Consulta-la ordenada per facturació descendent i comprova que els clients 13, 14 i 15 surten amb zeros i que la suma de la columna dona 727,95 €.
- És actualitzable automàticament? Justifica-ho amb la taula de l'apartat 4.
Exercici 2
Sobre v_productes_actius definida amb WITH CHECK OPTION, prediu el resultat de cada sentència i després comprova-ho:
-- a)
UPDATE v_productes_actius SET stock = stock + 50 WHERE id = 5;
-- b)
UPDATE productes SET actiu = FALSE WHERE id = 5;
SELECT COUNT(*) FROM v_productes_actius;
-- c)
INSERT INTO v_productes_actius (nom, categoria_id, preu, stock) VALUES ('Te chai 100 g', 4, 6.90, 30);
-- d)
DROP VIEW v_detall_vendes;Exercici 3
El quadre de comandament de direcció executa cada vegada que s'obre una consulta que triga 4 segons: facturació per mes i categoria des de l'inici de l'activitat. S'obre unes 200 vegades al dia i s'accepta un desfasament d'una hora.
- Vista, vista materialitzada o taula de resum? Justifica-ho amb la taula comparativa.
- Escriu l'objecte triat i el que calgui per poder refrescar-lo sense bloquejar qui l'estigui consultant.
- Què canviaria si el requisit fos "les dades han de ser del segon actual"?
Solucions
Solució 1
1. El LEFT JOIN no és negociable: amb INNER desapareixerien els tres clients sense comandes.
CREATE VIEW v_clients_valor AS
SELECT c.id, c.nom || ' ' || c.cognoms AS client, c.pais,
COUNT(DISTINCT co.id) AS comandes,
COALESCE(SUM(lc.quantitat), 0) AS unitats,
COALESCE(ROUND(SUM(lc.quantitat * lc.preu_unitari * (1 - lc.descompte)), 2), 0) AS facturacio
FROM clients AS c
LEFT JOIN comandes AS co ON co.client_id = c.id
LEFT JOIN linies_comanda AS lc ON lc.comanda_id = co.id
GROUP BY c.id, c.nom, c.cognoms, c.pais;
-- 2.
SELECT * FROM v_clients_valor ORDER BY facturacio DESC, id LIMIT 4;| id | client | pais | comandes | unitats | facturacio |
|---|---|---|---|---|---|
| 7 | Sofia Moreira Costa | Portugal | 2 | 11 | 111.88 |
| 1 | Lucía Martínez Soler | Espanya | 3 | 15 | 107.60 |
| 9 | Camille Dubois | França | 2 | 9 | 70.87 |
| 10 | Julien Moreau | França | 1 | 8 | 66.90 |
(4 primeres de 15 files.) Els clients 13, 14 i 15 tanquen amb 0 i 0.00, i SELECT SUM(facturacio) FROM v_clients_valor dona 727.95. El COUNT(DISTINCT co.id) és obligatori: amb COUNT(co.id) la Lucía tindria 9 "comandes", una per línia (04-04).
3. No és actualitzable automàticament, i falla en tres requisits alhora: té tres taules al FROM, té GROUP BY i té agregats al SELECT. Un UPDATE sobre ella donaria cannot update view "v_clients_valor", amb la pista que cal un disparador INSTEAD OF. I té sentit: què hauria de fer la base de dades si li demanes "posa la facturació de la Sofia a 200 €"?
Solució 2
a) Funciona: UPDATE 1. La vista és actualitzable —una sola taula, sense agregats—, la fila 5 està activa abans i després, i el canvi va a productes.
b) UPDATE 1, i després COUNT retorna 18. L'UPDATE va contra la taula, així que WITH CHECK OPTION no hi intervé: només vigila les escriptures fetes a través de la vista. El tomàquet desapareix de v_productes_actius sense cap més avís, que és precisament el comportament desitjat de l'esborrat lògic.
c) Funciona: INSERT 0 1. L'INSERT no esmenta actiu, així que la fila nova pren el DEFAULT TRUE de la taula, compleix el WHERE de la vista i WITH CHECK OPTION la deixa passar. Hauria fallat escrivint actiu a FALSE explícitament, o si el WHERE filtrés per una columna el valor per omissió de la qual no el satisfés. Recarrega l'script després: acabes d'afegir un producte 21 al catàleg.
d) Falla, perquè v_vendes_per_categoria en depèn:
ERROR: cannot drop view v_detall_vendes because other objects depend on it DETAIL: view v_vendes_per_categoria depends on view v_detall_vendes HINT: Use DROP ... CASCADE to drop the dependent objects too.
DROP VIEW v_detall_vendes CASCADE funcionaria, i esborraria també v_vendes_per_categoria sense preguntar. És el perill de la imbricació de l'apartat 5, ara en forma de dependència.
Solució 3
1. Vista materialitzada. Els tres criteris coincideixen amb la columna del mig de la taula: la lectura és cara (4 s), es repeteix molt (200 vegades al dia = 800 segons de CPU diaris) i es tolera desfasament. Una vista normal no estalviaria res; una taula de resum mantinguda amb disparadors donaria dades instantànies, però a canvi de complexitat i d'un cost per cada INSERT a linies_comanda que aquí ningú no ha demanat.
2.
CREATE MATERIALIZED VIEW mv_vendes_mes_categoria AS
SELECT to_char(dv.data_comanda, 'YYYY-MM') AS mes,
cat.id AS categoria_id, cat.nom AS categoria,
ROUND(SUM(dv.import), 2) AS facturacio
FROM v_detall_vendes AS dv
JOIN categories AS cat ON cat.id = dv.categoria_id
GROUP BY 1, 2, 3;
-- Imprescindible per poder refrescar sense bloquejar
CREATE UNIQUE INDEX ux_mv_vmc ON mv_vendes_mes_categoria (mes, categoria_id);I al cron, cada hora: REFRESH MATERIALIZED VIEW CONCURRENTLY mv_vendes_mes_categoria;. Sense aquest índex únic, CONCURRENTLY falla i el refresc pren ACCESS EXCLUSIVE, deixant el quadre de comandament inaccessible durant els 4 segons del recàlcul.
3. Si les dades han de ser del segon actual, la materialitzada queda descartada: una vista normal ben indexada (mòdul 8) i, si tot i així no baixa dels 4 segons, una taula de resum mantinguda amb disparadors (10-05), amb tot el seu cost de complexitat i el seu risc de desincronització. La pregunta honesta, abans de res, és si "del segon actual" és un requisit de debò o un costum: en un informe mensual gairebé mai no ho és.
Conclusió
La primera eina del mòdul 10 és també la més barata:
- Una vista és una consulta amb nom. No emmagatzema dades, s'expandeix dins de la consulta que la fa servir —fins al punt que el seu nom no apareix a l'
EXPLAIN— i per tant no accelera res. CREATE OR REPLACE VIEWnomés permet afegir columnes al final: ni reanomenar, ni treure, ni reordenar, ni canviar tipus. La resta ésDROP+CREATE, arrossegant el que en depengués.- Els cinc motius: simplificar (els quatre
JOINdev_detall_vendes, 47 línies i 727,95 €), estandarditzar una mètrica perquè la facturació es calculi igual a tot arreu, encapsular l'esborrat lògic (v_productes_actius, 19 de 20 productes — la promesa de 05-04), desacoblar l'aplicació de l'esquema físic com a contracte estable davant de l'expand/contract de 05-06, i exposar només una part de les dades (amb els permisos a 11-03). - Una vista és actualitzable automàticament si ve d'una sola taula, sense agregats, sense
DISTINCT, senseGROUP BYi amb columnes simples;WITH CHECK OPTIONimpedeix escriure files que la mateixa vista no mostraria. Per a la resta, disparadorINSTEAD OF(10-05). - Imbricar vistes és còmode i perillós: passats dos nivells, el planificador deixa de reordenar els
JOINi el pla es degrada. Davant del dubte,EXPLAINde la consulta completa (08-05). - Les vistes materialitzades sí que desen files: 12 mesos precalculats que es llegeixen a l'instant i envelleixen fins al
REFRESH.CONCURRENTLYevita bloquejar les lectures, però exigeix un índexUNIQUE. Compensen en informes cars que toleren dades de fa una hora.
Una vista resol el problema de reutilitzar una consulta entre sessions i entre persones. Però moltes vegades el que vols no és reutilitzar-la, sinó entendre-la: descompondre una consulta de quaranta línies en passos amb nom, aquí i ara, sense crear cap objecte permanent. A la lliçó següent, expressions de taula comunes (CTE), posaràs WITH davant del SELECT i veuràs com les taules derivades imbricades de tres nivells de 07-04 es converteixen en una llista de passos llegibles; encadenaràs diverses CTE on cadascuna es recolza en l'anterior; entendràs per què el que diuen els tutorials antics sobre materialització va deixar de ser cert a PostgreSQL 12; i, sobretot, escriuràs la teva primera consulta recursiva per recórrer per fi la jerarquia completa d'empleats i la cadena de referits que 03-06 va deixar a mitges.
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
