PL/pgSQL (Procedural Language/PostgreSQL) és el llenguatge procedural natiu
de PostgreSQL. Permet combinar sentències SQL amb les estructures pròpies
de la programació, com les variables, els condicionals o els bucles.
Gràcies a això, es poden implementar lògica de negoci, validacions, processos repetitius
i gestió d'errors directament dins de la base de dades, sense dependre d'una aplicació externa.
SQL treballa amb dades; PL/pgSQL treballa amb processos.
SQL és un llenguatge declaratiu: cada sentència indica quin resultat es vol, però no com obtindre'l.
PL/pgSQL, en canvi, és procedural: permet encadenar sentències, prendre decisions i repetir accions.
La taula següent resumeix les diferències principals:
El codi PL/pgSQL s'organitza en blocs. En crear una funció o un procediment, cal indicar
el llenguatge explícitament amb LANGUAGE plpgsql, i el codi del bloc s'escriu entre
delimitadors, habitualment $$.
Cada bloc té fins a quatre seccions:
DECLARE (opcional): declaració de variables.
BEGIN (obligatòria): codi executable.
EXCEPTION (opcional): gestió d'errors.
END; (obligatòria): final del bloc.
Un bloc anònim, és a dir, sense nom, s'executa directament amb DO:
Si una variable ha de guardar el valor d'una columna, és millor no escriure'n el tipus a mà.
En lloc d'això, es pot ancorar el tipus a la base de dades, de manera que, si la columna canvia,
la variable s'adapta automàticament:
%TYPE: pren el tipus d'una columna.
%ROWTYPE: pren l'estructura d'una fila completa d'una taula.
Els exemples d'aquests apunts utilitzen l'esquema següent:
ud4/ScriptDBEmployeesPostgreSQL.sql
---------------------------------------------------- LIMPIEZA PREVIA (por si existen)--------------------------------------------------DROPTABLEIFEXISTSemployeesCASCADE;DROPTABLEIFEXISTSpositionsCASCADE;DROPTABLEIFEXISTSwork_centersCASCADE;---------------------------------------------------- TABLA: WORK_CENTERS--------------------------------------------------CREATETABLEwork_centers(center_idINTEGERPRIMARYKEY,center_nameVARCHAR(100)NOTNULL,cityVARCHAR(50),phoneVARCHAR(20));---------------------------------------------------- TABLA: POSITIONS--------------------------------------------------CREATETABLEpositions(position_idINTEGERPRIMARYKEY,position_nameVARCHAR(50)NOTNULL,base_salaryNUMERIC(8,2));---------------------------------------------------- TABLA: EMPLOYEES--------------------------------------------------CREATETABLEemployees(employee_idINTEGERPRIMARYKEY,first_nameVARCHAR(50),last_nameVARCHAR(50),hire_dateDATE,salaryNUMERIC(8,2),center_idINTEGERREFERENCESwork_centers(center_id),position_idINTEGERREFERENCESpositions(position_id),emailVARCHAR(50));---------------------------------------------------- DATOS DE EJEMPLO: WORK_CENTERS--------------------------------------------------INSERTINTOwork_centersVALUES(1,'Centro Madrid Norte','Madrid','910000001');INSERTINTOwork_centersVALUES(2,'Centro Barcelona Este','Barcelona','930000002');INSERTINTOwork_centersVALUES(3,'Centro Sevilla Sur','Sevilla','950000003');---------------------------------------------------- DATOS DE EJEMPLO: POSITIONS--------------------------------------------------INSERTINTOpositionsVALUES(1,'Desarrollador Junior',18000);INSERTINTOpositionsVALUES(2,'Desarrollador Senior',32000);INSERTINTOpositionsVALUES(3,'Administrador de Sistemas',30000);INSERTINTOpositionsVALUES(4,'Jefe de Proyecto',38000);---------------------------------------------------- DATOS DE EJEMPLO: EMPLOYEES--------------------------------------------------INSERTINTOemployeesVALUES(100,'Juan','Pérez',DATE'2020-02-15',18500,1,1,'juan@work.es');INSERTINTOemployeesVALUES(101,'Ana','García',DATE'2019-06-10',33000,1,2,'ana@work.es');INSERTINTOemployeesVALUES(102,'Luis','Martín',DATE'2021-01-20',30000,2,3,'luis@work.es');INSERTINTOemployeesVALUES(103,'María','López',DATE'2018-09-05',39000,2,4,'maria@work.es');INSERTINTOemployeesVALUES(104,'Carlos','Sánchez',DATE'2022-03-12',18000,3,1,'carlos@work.es');INSERTINTOemployeesVALUES(105,'Elena','Ruiz',DATE'2020-11-30',32000,3,2,'elena@work.es');COMMIT;
SELECT INTO no llança cap error si troba 0 files o més d'una.
Per exigir exactament una fila i llançar un error en cas contrari, cal afegir la clàusula STRICT:
Les excepcions permeten controlar els errors que es produeixen en temps d'execució.
Quan es produeix un error dins del bloc, l'execució salta a la secció EXCEPTION, on es pot
tractar segons el tipus d'error:
Obtín el salari de l'empleat amb identificador 100 de la taula employees i tracta els casos
en què no existeix o n'hi ha més d'un.
Solució
DO$$DECLAREv_salariNUMERIC;BEGINSELECTsalaryINTOSTRICTv_salariFROMemployeesWHEREemployee_id=100;RAISENOTICE'Salari de l''empleat: %',v_salari;EXCEPTIONWHENNO_DATA_FOUNDTHENRAISENOTICE'Empleat no trobat';WHENTOO_MANY_ROWSTHENRAISENOTICE'Hi ha més d''un empleat amb eixe ID';END;$$;
Els bucles repeteixen un bloc de codi. El bucle FOR és el més utilitzat, ja que recorre
un rang de valors o el resultat d'una consulta sense haver de controlar cap comptador:
FORiIN1..10LOOP...ENDLOOP;
El bucle WHILE, en canvi, es repeteix mentre es complisca una condició:
WHILEcondicioLOOP...ENDLOOP;
Exercici: SELECT INTO, IF/ELSE i bucle
Crea un bloc que:
Obtinga el salari mitjà de la taula employees.
Recórrega tots els empleats.
Per a cada empleat, mostre el nom i el salari, i indique si està «Per damunt de la mitjana»
o «Per davall de la mitjana».
Solució
DO$$DECLAREv_avg_salaryNUMERIC;BEGINSELECTAVG(salary)INTOv_avg_salaryFROMemployees;FORrIN(SELECTfirst_name,salaryFROMemployees)LOOPIFr.salary>=v_avg_salaryTHENRAISENOTICE'% - % (Per damunt de la mitjana)',r.first_name,r.salary;ELSERAISENOTICE'% - % (Per davall de la mitjana)',r.first_name,r.salary;ENDIF;ENDLOOP;END;$$;
Els blocs anònims s'executen una vegada i desapareixen. Per reutilitzar el codi, cal guardar-lo
en la base de dades com a funció o com a procediment. PostgreSQL distingeix entre tots dos
(els procediments existeixen des de la versió 11).
Una funció és un bloc amb nom que sempre retorna un valor, indicat amb RETURNS. Com que
retorna un valor, es pot utilitzar dins de sentències SQL. En canvi, no pot executar
COMMIT ni ROLLBACK.
Un procediment és un bloc amb nom que no retorna cap valor, encara que pot tindre
paràmetres OUT o INOUT. S'invoca amb CALL, mai des d'una consulta SQL. A diferència
de les funcions, pot executar COMMIT i ROLLBACK, sempre que s'invoque com a sentència
de nivell superior (no dins d'un altre bloc transaccional).
Un cursor explícit es declara amb CURSOR i es gestiona manualment amb OPEN, FETCH i CLOSE.
Dona més control que el cursor implícit, per exemple, per aturar el recorregut a mitjan camí.
La variable especial FOUND indica si l'última FETCH ha obtingut una fila:
Una sessió es finalitza amb la funció pg_terminate_backend, indicant el PID del procés
de la connexió. El procediment següent l'encapsula i en gestiona els errors:
PERFORM s'utilitza per cridar una funció sense recollir el seu valor de retorn.
Cas pràctic: identificar i finalitzar sessions bloquejants¶
Quan una sessió manté un bloqueig durant massa temps, les altres sessions que necessiten
les mateixes dades es queden esperant. Per resoldre-ho, primer cal identificar quina sessió
bloqueja i, després, finalitzar-la:
-- 1. Consultar les sessions bloquejades i qui les bloquejaSELECTblocked.pidASbloquejada_pid,blocked.usenameASusuari,blocking.pidASbloquejant_pid,blocking.usenameASusuari_bloquejantFROMpg_locksblocked_locksJOINpg_stat_activityblockedONblocked.pid=blocked_locks.pidJOINpg_locksblocking_locksONblocking_locks.locktype=blocked_locks.locktypeANDblocking_locks.pid!=blocked_locks.pidANDblocking_locks.grantedJOINpg_stat_activityblockingONblocking.pid=blocking_locks.pidWHERENOTblocked_locks.granted;-- Suposem que la sessió bloquejant té el pid 456-- 2. Invocar el procediment per finalitzar-laCALLkill_session(456);
Exercici: Reflexió final
Per què no és recomanable posar tota la lògica de negoci en la base de dades?
Solució
Dificulta el manteniment, redueix la flexibilitat i acobla massa l'aplicació a la base de dades.
📌 Aquest document pot quedar desactualitzat després d’imprimir-lo. Pots consultar la versió més recent
a la pàgina web.
🌿 Abans d’imprimir aquest document, considera si és realment necessari. Redueix el consum de paper i ajuda a protegir el nostre entorn.