14/12/2010 PG/PLSQL Miguel Ángel Manso ETSI en Topografía, Geodesia y Cartografía - UPM Índice • Estructura PL/PGSQL • Declaraciones, Alias para parámetros en funciones • Datos de tipo tabla, Type & RowType • Sentencias, estructuras de control • Reporte de errores, cursores • Triggers 1 14/12/2010 Estructura PL/PGSQL CREATE OR REPLACE PROCEDURE [esquema].nombre-procedimiento(nombre-parámetro {IN, OUT, IN OUT} tipo de dato, ..) {IS, AS} Declaración de variables; Declaración de constantes; Declaración de cursores; BEGIN Cuerpo del subprograma PL/SQL; EXCEPTION Bloque de excepciones PL/SQL; END; CREATE OR REPLACE FUNCTION [esquema].nombre-funcion(nombre-parámetro {IN, OUT, IN OUT} tipo-dedato, ..) RETURN tipo-de-dato {IS, AS} Declaración de variables; Declaración de constantes; Declaración de cursores; BEGIN Cuerpo del subprograma PL/SQL; EXCEPTION Bloque de excepciones PL/SQL; END; Declaraciones • Sintaxis general: name [ CONSTANT ] type [ NOT NULL ] [ { DEFAULT | := } expression ]; • Ejemplos: user_id integer; quantity numeric(5); url varchar; myrow tablename%ROWTYPE; myfield tablename.columnname%TYPE; arow RECORD; Registro Tipo col. Registro quantity INTEGER DEFAULT 32; url varchar := ”http://mysite.com”; user_id CONSTANT INTEGER := 10; 2 14/12/2010 Alias para parámetros de funciones name ALIAS FOR $n; Ejemplo: CREATE FUNCTION sales_tax(REAL) RETURNS REAL AS ’ DECLARE subtotal ALIAS FOR $1; BEGIN return subtotal * 0.06; END; ’ LANGUAGE ’plpgsql’; CREATE FUNCTION instr(VARCHAR, INTEGER) RETURNS INTEGER AS ’ DECLARE v_string ALIAS FOR $1; index ALIAS FOR $2; BEGIN Datos de tipo Tabla name tablename%ROWTYPE; -- Variable de tipo complejo (registro) -- Se usa name.fieldName CREATE FUNCTION use_two_tables(tablename) RETURNS TEXT AS ’ DECLARE in_t ALIAS FOR $1; use_t table2name%ROWTYPE; BEGIN SELECT * INTO use_t FROM table2name WHERE ... ; RETURN in_t.f1 || use_t.f3 || in_t.f5 || use_t.f7; END; ’ LANGUAGE ’plpgsql’; 3 14/12/2010 Type & RowType Conocer el tipo de dato de una variable Type user_id users.user_id%TYPE; Para crear una variable de tipo registro asociado a las columnas de una tabla users_rec users%ROWTYPE; Sentencias Asignación: <Variable> := <valor>; user_id := 20; tax := subtotal * 0.06; SELECT INTO target select_expressions FROM ...; SELECT INTO Result * FROM EMP WHERE empname = myname; IF NOT FOUND THEN RAISE EXCEPTION ”El empleado % no existe “, myname; END IF; Ejecución sin resultados: PERFORM query; PERFORM create_mv(”cs_session_page_requests_mv”, my_query); 4 14/12/2010 Sentencias No hacer nada: Null; Ejecución dinámica: EXECUTE texto-comando; EXECUTE ”UPDATE tabla SET ” || quote_ident(nombreCampo) || ” = ” || quote_literal(NuevoValor) || ” WHERE ...”; Obtener resultado de la última ejecución: GET DIAGNOSTICS variable = item [ , ... ] ; Ejemplos: GET DIAGNOSTICS Variable_int = ROW_COUNT; -- Cantidad de filas GET DIAGNOSTICS estado = FOUND; -- True/False último resultado Estructuras de control • Retorno de resultados: – Return expresión; • Condicionales: IF ... THEN IF ... THEN ... ELSE IF ... THEN ... ELSE IF IF ... THEN ... ELSIF ... THEN ... ELSE IF ... THEN ... ELSEIF ... THEN ... ELSE 5 14/12/2010 Bucles Bucles simples: [<<label>>] LOOP sentencias EXIT [ label ] [ WHEN expresion ]; END LOOP; Ejemplos: LOOP -- algunos cálculos IF count > 0 THEN EXIT; -- exit loop END IF; END LOOP; LOOP -- otros cálculos EXIT WHEN count > 0; END LOOP; Bucle While While [<<label>>] WHILE expresión LOOP sentencias END LOOP; Ejemplos WHILE amount_owed > 0 AND gift_certificate_balance > 0 LOOP -- algunos cálculos END LOOP; WHILE NOT expresión_booleana LOOP -- algunos cálculos END LOOP; 6 14/12/2010 Bucle For For [<<label>>] FOR nombre IN [ REVERSE ] expresión .. expresión LOOP sentencias END LOOP; Ejemplos FOR i IN 1..10 LOOP -- algunas expresiones RAISE NOTICE ”i is %”,i; END LOOP; FOR i IN REVERSE 10..1 LOOP -- algunas expresiones END LOOP; Iterar sobre resultados Iterar sobre resultados [<<etiqueta>>] FOR fila IN consulta LOOP sentencias END LOOP; Ejemplo CREATE FUNCTION cs_refresh_mviews () RETURNS INTEGER AS ’ DECLARE mviews RECORD; BEGIN PERFORM cs_log(”Actualizando las vistas...”); FOR mviews IN SELECT * FROM vista_cs ORDER BY sort_key LOOP -- Ahora "mviews" contiene un registro de vista_cs PERFORM cs_log(”Actualizando las vistas…” || quote_ident(mviews.mv_name) ….. END LOOP; RETURN 1; END; ’ LANGUAGE ’plpgsql’; 7 14/12/2010 Reportar errores RAISE level ’format’ [, variable [...]]; Ejemplos: RAISE NOTICE ”Invocando cs_create_job(%)”,v_job_id; RAISE EXCEPTION ”No existe el ID --> %”,user_id; Cursores Declaración: nombre CURSOR [ ( argumentos ) ] FOR|IS consulta ; Ejemplos: curs1 refcursor; --Es un tipo de dato genérico para cursores curs2 CURSOR FOR SELECT * FROM tenk1; curs3 CURSOR (key integer) IS SELECT * FROM tenk1 WHERE unique1 = key; Abrir cursor para usar: OPEN unbound_cursor FOR SELECT ...; OPEN unbound_cursor FOR EXECUTE query_string; Ejemplos: OPEN curs1 FOR SELECT * FROM foo WHERE key = mykey; OPEN curs1 FOR EXECUTE 'SELECT * FROM ' || quote_ident($1); 8 14/12/2010 Cursores Usar cursores: FETCH cursor INTO target; Cerrar cursores: CLOSE cursor; Retornar cursores: CREATE TABLE prueba (columna text); INSERT INTO prueba VALUES (’123’); CREATE FUNCTION reffunc(refcursor) RETURNS refcursor AS ’ BEGIN OPEN $1 FOR SELECT columna FROM prueba; RETURN $1; END; ’ LANGUAGE ’plpgsql’; BEGIN; SELECT reffunc(’funccursor’); FETCH ALL IN funccursor; COMMIT; Triggers CREATE TRIGGER --- argumentos vía TG_ARGV Posibles argumentos: NEW OLD TG_NAME TG_WHEN TG_LEVEL TG_OP TG_RELID TG_RELNAME TG_NARGS TG_ARGV[] variable con los nuevos datos Variable con el dato antiguo Nombre del trigger Cuándo se dispara AFTER, BEFORE Nivel al que afecta: fila, sentencia Operación que dispara (INSERT, UPDATE; DELETE) ID del objecto de la tabla que dispara el trigger nombre de la tabla que dispara el trigger nº de argumentos 9 14/12/2010 Mas sobre trigger CREATE TRIGGER nombre { BEFORE | AFTER } { INSERT | UPDATE | DELETE [ OR ... ] } ON tabla [ FOR [ EACH ] { ROW | STATEMENT } ] EXECUTE PROCEDURE nombre_de_función ( argumentos ) Ejemplo trigger 1 CREATE TABLE numeros( numero bigint NOT NULL, cuadrado bigint, cubo bigint, raiz2 real, raiz3 real, PRIMARY KEY (numero) ); 2 CREATE OR REPLACE FUNCTION rellenar_datos() RETURNS TRIGGER AS $rellenar_datos$ DECLARE BEGIN NEW.cuadrado := power(NEW.numero,2); NEW.cubo := power(NEW.numero,3); NEW.raiz2 := sqrt(NEW.numero); NEW.raiz3 := cbrt(NEW.numero); RETURN NULL; END; $rellenar_datos$ LANGUAGE plpgsql; 3 CREATE TRIGGER rellena_datos BEFORE INSERT OR UPDATE ON numeros FOR EACH ROW EXECUTE PROCEDURE rellenar_datos(); 10 14/12/2010 Ejemplo trigger CREATE TABLE emp ( empname text, salary integer, last_date timestamp, last_user text ); CREATE FUNCTION emp_stamp () RETURNS TRIGGER AS ’ BEGIN IF NEW.empname ISNULL THEN -- Comprueba que se proporcionan: empname y salary RAISE EXCEPTION ”empname no puede ser NULL”; END IF; IF NEW.salary ISNULL THEN RAISE EXCEPTION ”El empleado % no puede tener salario NULL”, NEW.empname; END IF; IF NEW.salary < 0 THEN -- El sueldo no puede ser negativo! RAISE EXCEPTION ”El empleado % no puede tener salario negativo”, NEW.empname; END IF; NEW.last_date := ”now”; -- Se almacena quién y cuándo se cambiaron los datos NEW.last_user := current_user; RETURN NEW; END; ’ LANGUAGE ’plpgsql’; CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp FOR EACH ROW EXECUTE PROCEDURE emp_stamp(); Referencias • http://plsql-tutorial.com/index.htm • http://www.postgresql-es.org/node/297 • Triggers: http://www.postgresql-es.org/node/301 11