PL/PGSQL Extensión "procedural" de SQL Pero primero triggers DROP TABLE producto cascade; CREATE TABLE producto( id int PRIMARY KEY, vendido timestamp); DROP FUNCTION modificacion() cascade; CREATE FUNCTION modificacion() RETURNS TRIGGER AS ' BEGIN NEW.vendido := ''now''; RETURN NEW; END; 'LANGUAGE 'plpgsql'; DROP TRIGGER t_modificacion on producto; CREATE TRIGGER t_modificacion BEFORE INSERT ON producto FOR EACH ROW EXECUTE PROCEDURE modificacion(); INSERT into producto VALUES (1); SELECT * FROM producto; 6.2 Trigger Layout INSERT BEFORE CREATE TRIGGER <trigger_name> UPDATE AFTER DELETE OLD NEW OR FOR EACH ROW <table_name> ON FOR EACH STATEMENT EXECUTE PROCEDURE <function_name> 6.3 ( <arguments> ); Extensiones SQL-99 ■ Entre las propuestas definidas en el estándar SQL-99 están la extensión del SQL dotándolo de comandos para controlar el flujo: ★ If, where, loop, etc ■ El standard SQL-99 esta bastante lejos de ser satisfecho por la mayoría de las bases de datos. ■ Oracle tiene una versión propia de estas extensiones "procedurales" llamada PL/SQL ■ PostgreSQL tiene su versión llamada PL/pgSQL 6.4 Por qué PL/pgSQL ■ Permite crear funciones que se ejecutan en el servidor (versus otras aproximaciones como JDBC que se ejecutan en el cliente con "overhead" de comunicaciones). ■ La propia base de datos se encarga de compilar y gestionar estas funciones con lo que suelen ser eficientes. ■ proporciona: ★ variables ★ bucles ★ evaluación condicional 6.5 Un paso previo a usar PL/pgSQL ■ Cuando se crea una base de datos nueva hace falta "autorizar" el uso de pl/pgSQL (a menos que template1 ya este autorizada) createdb mydatab createlang plpgsql mydatab ■ En los laboratorios debería estar "autorizado" por defecto ■ Ojo: no se comprueba la sintaxis de las funciones hasta que no son ejecutadas. (Es difícil depurar el código) 6.6 PL/pgSQL: Estructura de las funciones ■ PL/pgSQL presenta una estructura en "Bloques". ■ Cada bloque se define usando DECLARE --variables inicializada con NULL cada vez --que se entra en el bloque [...] BEGIN --comandos; [...] END; ■ No se pueden definir transacciones dentro de una función 6.7 PL/pgSQL estructura CREATE FUNCTION nombre_función (argumentos) RETURNS type AS ' DECLARE declaracion; --variables [...] BEGIN statement; --comandos [...] END; ' LANGUAGE 'plpgsql'; ■ Una función puede constar de varios bloques y estos pueden estar anidados 6.8 PL/pgSQL: Generalidades ■ Los tipos de datos pasados a la función se dan en paréntesis (sin nombre de variable antes de postgres 8) ■ El cuerpo de la función se pasa a la base de datos como una cadena de caracteres (nótese, que el cuerpo empieza y acaba con comillas simples) (escape doble) ■ Tras la cadena el lenguaje usado para crear la función se define usando la orden "LANGUAJE" (otros lenguajes posibles son PL/PERL, PL/TCL, C, etc) 6.9 Ejemplo trivial sin pasar parámetros ■ ¿Qué hace esta función? CREATE OR REPLACE FUNCTION una_funcion () RETURNS int4 AS ' DECLARE an_integer int4; --variables BEGIN an_integer := 10 * 10; --comandos RETURN an_integer; END; ' LANGUAGE 'plpgsql'; ____________________________ select una_funcion(); una_funcion -----------100 (1 row) 6.10 Tipos de Variables-I ■ Ejemplos de variables: id_usuario INTEGER; cantidad NUMERIC(5,2); url VARCHAR; -- Mas sobre los tipos siguientes más adelante micampo mitabla.campo%TYPE; mitupla mitabla%ROWTYPE; ■ The general syntax of a variable declaration is: name [ CONSTANT ] type [ NOT NULL ] [ { DEFAULT | := } expression ]; 6.11 Tipos de Variables II ■ Todos los tipos de variable definidos para SQL son válidos en PL/pgSQL ■ No es imprescindible conocer el tipo de variables de los atributos ★ Ejemplos usando %TYPE DECLARE … BEGIN … mivar payroll.salario%TYPE; RETURN mivar*2; ★ %ROWTYPE reserva sitio para toda la tupla ★ Reteniendo la estructura de los datos DECLARE … mivar payroll%ROWTYPE; BEGIN … RETURN mivar.salario*2; 6.12 Ejemplo trivial pasando variables CREATE OR REPLACE FUNCTION cal_longitud (text) RETURNS int4 AS ' DECLARE intext ALIAS FOR $1; --primer parametro resultado int4; BEGIN resultado := (SELECT LENGTH(intext)); RETURN resultado; END; ' LANGUAGE 'plpgsql'; ____________________________ SELECT cal_longitud('qwerty'); cal_longitud -------------6 (1 row) 6.13 Más sobre Variables ■ CREATE FUNCTION mifuncion(INTEGER, CHAR, …) ■ Se pueden pasar hasta 16 variables ★ $1, $2, …, $16 ■ ALIAS permite renombrar variables CREATE FUNCTION cal_longitud (text) RETURNS int4 AS ' DECLARE intext ALIAS FOR $1; --primer parametro resultado int4; . . . 6.14 Ejemplo usando Rowtype CREATE OR REPLACE FUNCTION trae_pelicula (integer) RETURNS text AS ' DECLARE pelicula_id ALIAS FOR $1; encontrada_pelicula pelicula%ROWTYPE; BEGIN SELECT INTO encontrada_pelicula * FROM pelicula WHERE id = pelicula_id; RETURN encontrada_pelicula.titulo || '' encontrada_pelicula.agno || '')''; ('' || END; ' LANGUAGE 'plpgsql'; ■ Nota: Si SELECT INTO devuelve más de una tupla se ignoran todas menos la primera (la solución a esto más tarde) 6.15 Ejercicio ■ En la base de datos de películas crear dos funciones que nos sirvan para llenar la tabla reparto, dando el nombre de la pelicula y el nombre del actor CREATE TABLE PELICULA( ID INTEGER, -- Identificador único TITULO CHAR(70), -- Titulo de la película AGNO DECIMAL(4), -- Año de estreno PUNTUACION FLOAT, -- Puntuación media VOTOS INTEGER, -- Numero de votos PRIMARY KEY (ID)); -CREATE TABLE ACTOR ( ID INTEGER, -- Identificador Único NOMBRE CHAR(35), -- Nombre del actor/actriz PRIMARY KEY (ID)); -CREATE TABLE REPARTO( PELICULA_ID INTEGER, -- referencia a la tabla PELICULA ACTOR_ID INTEGER, -- referencia a la tabla ACTOR_ID ORD INTEGER, -- Orden en el reparto -- La estrella es 1, ... -FOREIGN KEY (PELICULA_ID ) REFERENCES PELICULA(ID), FOREIGN KEY (ACTOR_ID) REFERENCES ACTOR(ID), PRIMARY KEY (PELICULA_ID, ACTOR_ID)); 6.16 Control de Flujo ■ Los programas no suelen ejecutarse de principio a fin sin exceptuar ninguna línea de código. PL/pgSQL contiene estructuras de control que permiten seleccionar las líneas de código que serán ejecutarse en tiempo real. ■ IF…THEN…ELSE…ELSE IF ★ ejecución condicional ■ LOOPS, WHILE LOOPS, FOR LOOPS ★ iteraciones ★ bucles 6.17 Ejemplo IF/ELSE ■ Programa que calcula la longitud de dos cadenas y devuelve la longitud mayor. CREATE OR REPLACE FUNCTION cadena_mas_larga(text, text) RETURNS int4 AS ' DECLARE in_uno ALIAS FOR $1; in_dos ALIAS FOR $2; lon_uno int4; lon_dos int4; result int4; BEGIN lon_uno := (SELECT LENGTH(in_uno)); lon_dos := (SELECT LENGTH(in_dos)); IF lon_uno > lon_dos THEN RETURN lon_uno; ELSE RETURN lon_dos; END IF; END; 'LANGUAGE 'plpgsql'; ■ NOTA 1: se pueden hacer condiciones mas complicadas usando OR y AND ■ NOTA 2: Como PL/pgSQL se agrupa en bloques no hacen falta paréntesis en torno a IF 6.18 Ejemplo bucle WHILE (FOR) ■ Función que cuenta cuantas veces aparece un carácter en una cadena CREATE OR REPLACE FUNCTION cuentac(text,text) RETURNS INT4 AS ' DECLARE intext ALIAS FOR $1; inchar ALIAS FOR $2; lon int4; resultado int4; i int4; tmp char; BEGIN lon := length(intext)+1; i:=1; resultado:=0; WHILE i <= lon LOOP tmp := substr(intext,i,1); IF tmp = inchar THEN resultado := resultado +1; END IF; i:=i+1; END LOOP; RETURN resultado; END ' LANGUAGE 'plpgsql'; -- SELECT cuentac('qwertytq','q'); 6.19 Excepciones ■ RAISE se usa para imprimir mensajes y, en el caso de excepcion, abortar la transacción ■ RAISE { NOTICE | EXCEPTION} ■ RAISE NOTICE ★ RAISE NOTICE ' No hagas eso!' '; ★ RAISE NOTICE ' 'El señor' ' || id || ' 'no está en casa' '; ★ RAISE NOTICE ' 'el señor % no está en casa' ' , id; 6.20 Excepciones: Ejemplo Calcular la suma de los enteros de n a m (usar la formula (p+1)*p/2 CREATE OR REPLACE FUNCTION suma(int4, int4) RETURNS int4 AS ' DECLARE inicio ALIAS FOR $1; fin ALIAS FOR $2; resultado int; BEGIN IF (inicio <1) THEN RAISE EXCEPTION ''inicio debe ser mayor que 1''; ELSE IF(inicio <= fin) THEN resultado := (fin+1)*fin/2 (inicio-1)*inicio/2; ELSE RAISE EXCEPTION ''El valor inicial % debe ser menor que el final %'', inicio, fin; END IF; END IF; RETURN resultado; END ' LANGUAGE 'plpgsql'; SELECT suma(1,3); $$ ■ 6.21 SELECT y Bucles CREATE OR REPLACE FUNCTION trae_pelicula (integer) RETURNS text AS ' DECLARE pelicula_id ALIAS FOR $1; encontrada_pelicula pelicula%ROWTYPE; BEGIN SELECT INTO encontrada_pelicula * FROM pelicula WHERE id = pelicula_id; RETURN encontrada_pelicula.titulo || '' encontrada_pelicula.agno || '')''; ('' || ■ Nota: Si SELECT INTO devuelve más de una tupla se ignoran todas menos la primera (la solución a esto más tarde) 6.22 SELECT y Bucles Cuantas tuplas empiezan con una letra determinada CREATE OR REPLACE FUNCTION cuenta_letra (text) RETURNS int4 AS ' DECLARE caracter ALIAS FOR $1; temporal record; tmp_caracter text; resultado int4; BEGIN resultado:=0; FOR temporal IN SELECT titulo FROM pelicula LOOP tmp_caracter :=substr(temporal.titulo,1,1); IF tmp_caracter = caracter THEN resultado := resultado +1; END IF; END LOOP; RETURN resultado; END; 'LANGUAGE 'plpgsql'; SELECT cuenta_letra('A'); ■ 6.23 Examen Parcial Nov PUJA Persona Puja Puja Objeto PERSONA OBJETO Subastado por • Rechazar pujas no admisibles • 10% menos que puja anterior • Un día más tarde • Actualizar campos redundantes • 6.24 vendido TABLAS CREATE TABLE persona( id SERIAL, --identificador unico PRIMARY KEY(id) ); CREATE TABLE objeto( id SERIAL, --identificador unico persona_id INT, fecha_salida TIMESTAMP DEFAULT now(), vendido int DEFAULT 0, -- 1 vendido, 0 no vendido precio_salida NUMERIC(10,2), PRIMARY KEY(id), FOREIGN KEY (persona_id) REFERENCES persona(id) ); CREATE TABLE puja( objeto_id INT, persona_id INT, fecha TIMESTAMP, cuantia NUMERIC(10,2), FOREIGN KEY (objeto_id) REFERENCES objeto(id), FOREIGN KEY (persona_id) REFERENCES persona(id), PRIMARY KEY(persona_id,objeto_id,fecha) ); 6.25 DATOS --poblar la tabla ---persona (id) -INSERT INTO persona VALUES (1);-INSERT INTO persona VALUES (2);-INSERT INTO persona VALUES (3);-INSERT INTO persona VALUES (4);-INSERT INTO persona VALUES (5);----objeto(id, persona_id,fecha_salida,vendido,precio_salida) -INSERT INTO objeto VALUES (1,1,now(),DEFAULT,23);-INSERT INTO objeto VALUES (2,1,now()+'-1.1 day',DEFAULT,23); INSERT INTO objeto VALUES (3,3,now()+'-3.14 day',DEFAULT,23); INSERT INTO objeto VALUES (4,3,now()+'-2.9 day',DEFAULT,23); INSERT INTO objeto VALUES (5,3,now()+'-5.9 day',DEFAULT,23); INSERT INTO objeto VALUES (6,4,now()+'-5.9 day',DEFAULT,23); INSERT INTO objeto VALUES (7,4,now()+'-0.9 day',DEFAULT,23); INSERT INTO objeto VALUES (8,5,now()+'-0.8 day',DEFAULT,23); INSERT INTO objeto VALUES (9,5,now()+'-1.34 day',DEFAULT,23); INSERT INTO objeto VALUES (10,5,now()+'-5.9 day',DEFAULT,23); ---puja (objeto_id,persona_id,fecha) -INSERT INTO puja VALUES (1,1,now()+'1 hour',10.34); INSERT INTO puja VALUES (1,1,now()+'2 hour',10.34); INSERT INTO puja VALUES (1,1,now()+'3 hour',10.34); INSERT INTO puja VALUES (1,1,now()+'4 hour',10.34); INSERT INTO puja VALUES (1,1,now()+'5 hour',10.34); INSERT INTO puja VALUES (1,1,now()+'10 hour',10.34); INSERT INTO puja VALUES (10,1,now()+'1 hour',10.34); INSERT INTO puja VALUES (9,1,now()+'1 hour',10.34); 6.26 Ejemplo función en C Ejemplo de Función en C ■ Código #include "postgres.h" #include <string.h> /* by value */ int add_one(int arg) { return arg + 1; } ■ Compilación gcc -fpic -c foo.c -I /usr/include/pgsql/server/ gcc -shared -o foo.so foo.o 6.28 Ejemplo de Función en C-II ■ Instalacion: ★ como istrador de la base (postges): CREATE FUNCTION add_one(integer) RETURNS integer AS '/tmp/foo.so', 'add_one' LANGUAGE C STRICT; _______________________________ ■ add_one.so es la librería dinámica con la función ■ Más información en: http://developer.postgresql.org/docs/postgres/xfunc-c. ■ Recordar que C no soporta todos los tipos usados en SQL (y viceversa) 6.29 Se acabó