PostgreSQL Prático/Funções Definidas pelo Usuário e Triggers/PlpgSQL

6.2 - Funções em Pl/pgSQL

editar

As funções em linguagens procedurais no PostgreSQL, como a Pl/pgSQL são correspondentes ao que se chama comumente de Stored Procedures. Por default o PostgreSQL só traz suporte às funções na linguagem SQL. Para dar suporte à funções em outras linguagens temos que efetuar procedimentos como a seguir.

Para que o banco postgres tenha suporte à linguagem de procedimento Pl/PgSQL executamos na linha de comando como super usuário do PostgreSQL:

createlang plpgsql –U nomeuser nomebanco no prompt ou create language plpgsql por SQL.

PGSQL 8.4

createlang -U nomeuser -W -e plpgsql nomebanco

A Pl/pgSQL é a linguagem de procedimentos armazenados mais utilizada no PostgreSQL, devido ser a mais madura e com mais recursos.

//Código-fonte
CREATE FUNCTION func_escopo() RETURNS integer AS $$
DECLARE
   quantidade integer := 30;
BEGIN
   RAISE NOTICE 'Aqui a quantidade é %', quantidade;  -- A quantidade aqui é 30
   quantidade := 50;
   --
   -- Criar um sub-bloco
   --
   DECLARE
       quantidade integer := 80;
   BEGIN
       RAISE NOTICE 'Aqui a quantidade é %', quantidade;  -- A quantidade aqui é 80
   END;
   RAISE NOTICE 'Aqui a quantidade é %', quantidade;  -- A quantidade aqui é 50
   RETURN quantidade;
END;

$$ LANGUAGE plpgsql;

=> SELECT func_escopo();


CREATE FUNCTION instr(varchar, integer) RETURNS integer AS $$
DECLARE
   v_string ALIAS FOR $1;
   index    ALIAS FOR $2;
BEGIN
   -- algum processamento neste ponto
END;

$$ LANGUAGE plpgsql;

Exemplo concatenar campos.

CREATE FUNCTION concatenar_campos_selecionados(in_t nome_da_tabela) RETURNS text AS $$
BEGIN
   RETURN in_t.f1 || in_t.f3 || in_t.f5 || in_t.f7;
END;

$$ LANGUAGE plpgsql;

Exemplo para somar 3 Valores

CREATE FUNCTION somar_tres_valores(v1 anyelement, v2 anyelement, v3 anyelement)
RETURNS anyelement AS $$
DECLARE
   resultado ALIAS FOR $0;
BEGIN
   resultado := v1 + v2 + v3;
   RETURN resultado;
END;

$$ LANGUAGE plpgsql;

SELECT somar_tres_valores(10,20,30);

Utilização de tipo composto:

CREATE FUNCTION mesclar_campos(t_linha nome_da_tabela) RETURNS text AS $$
DECLARE
   t2_linha nome_tabela2%ROWTYPE;
BEGIN
   SELECT * INTO t2_linha FROM nome_tabela2 WHERE ... ;
   RETURN t_linha.f1 || t2_linha.f3 || t_linha.f5 || t2_linha.f7;
END;

$$ LANGUAGE plpgsql;

SELECT mesclar_campos(t.*) FROM nome_da_tabela t WHERE ... ;

Temos uma tabela (datas) com dois campos (data e hora) e queremos usar uma função para manipular os dados desta tabela:

CREATE or REPLACE FUNCTION data_ctl(opcao char, fdata date, fhora time) RETURNS char(10) AS $$
DECLARE
    opcao ALIAS FOR $1;
    vdata ALIAS FOR $2;
    vhora ALIAS FOR $3;
    retorno char(10);
BEGIN
    IF opcao = 'I' THEN 
	insert into datas (data, hora) values (vdata, vhora);
        retorno := 'INSERT';
    END IF;
    IF opcao = 'U' THEN 
	update datas set data = vdata, hora = vhora where data='1995-11-01';
        retorno := 'UPDATE';
    END IF;
    IF opcao = 'D' THEN 
	delete from datas where data = vdata;
        retorno := 'DELETE';
    ELSE
        retorno := 'NENHUMA';
    END IF;	
    RETURN retorno;
END;
$$
LANGUAGE plpgsql;

--select data_ctl('I','1996-11-01', '08:15');
select data_ctl('U','1997-11-01','06:36');
select data_ctl('U','1997-11-01','06:36');

Mais Detalhes no capítulo 35 do manual oficial.

Funções que Retornam Conjuntos de Registros (SETS)

CREATE OR REPLACE FUNCTION codigo_empregado (codigo INTEGER)
   RETURNS SETOF INTEGER AS $$
   DECLARE
	registro RECORD;
	retval INTEGER;
   BEGIN
	FOR registro IN SELECT * FROM empregados WHERE salario >= $1 LOOP
		RETURN NEXT registro.departamento_cod;
	END LOOP;
	RETURN;
   END;
$$ language 'plpgsql';

select * from codigo_empregado (0);
select count (*), g from codigo_empregado (5000) g group by g;


Funções que retornam Registro Para criar funções em plpgsql que retornem um registro, antes precisamos criar uma variável composta do tipo ROWTYPE, descrevendo o registro (tupla) de saída da função.

CREATE TABLE empregados(
  nome_emp text,
  salario int4,
  codigo int4 NOT NULL,
  departamento_cod int4,
  CONSTRAINT empregados_pkey PRIMARY KEY (codigo),
  CONSTRAINT empregados_departamento_cod_fkey FOREIGN KEY (departamento_cod)
      REFERENCES departamentos (codigo) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
) 

CREATE TABLE departamentos (codigo INT primary key,  nome varchar);
CREATE TYPE dept_media AS (minsal INT, maxsal INT, medsal INT);

create or replace function media_dept() returns dept_media as
$$
declare
	r dept_media%rowtype;
	dept record;
	bucket int8;
	counter int;
   begin
	bucket := 0;
	counter := 0;
	r.maxsal :=0;
	r.minsal :=0;
	for dept in select sum(salario) as salario, d.codigo as departamento
		from empregados e, departamentos d where e.departamento_cod = d.codigo
		group by departamento loop
	   counter := counter + 1;
	   bucket := bucket + dept.salario;

	   if r.maxsal <= dept.salario or r.maxsal = 0 then
		r.maxsal := dept.salario;
	   end if;

	   if r.minsal <= dept.salario or r.minsal = 0 then
		r.minsal := dept.salario;
	   end if;

	end loop;
	r.medsal := bucket/counter;
	return r;
   end
   $$ language 'plpgsql';


Funções que Retornam Conjunto de Registros (SETOF, Result Set) Também requerem a criação de uma variável (tipo definidopelo user)

CREATE TYPE media_sal AS
	(deptcod int, minsal int, maxsal int, medsal int);
CREATE OR REPLACE FUNCTION medsal() RETURNS SETOF media_sal AS
$$
DECLARE
	s media_sal%ROWTYPE;
	salrec RECORD;
	bucket int;
	counter int;
BEGIN
	bucket :=0;
	counter :=0;
	s.maxsal :=0;
	s.minsal :=0;
	s.deptcod :=0;
	FOR salrec IN SELECT salario AS salario, d.codigo AS departamento
		FROM empregados e, departamentos d WHERE e.departamento_cod = d.codigo ORDER BY d.codigo LOOP
		IF s.deptcod = 0 THEN
			s.deptcod := salrec.departamento;
			s.minsal := salrec.salario;
			s.maxsal := salrec.salario;
			counter := counter + 1;
			bucket := bucket + salrec.salario;
		ELSE
			IF s.deptcod = salrec.departamento THEN
				IF s.maxsal <= salrec.salario THEN
					s.maxsal := salrec.salario;
				END IF;
				IF s.minsal >= salrec.salario THEN
					s.minsal := salrec.salario;
				END IF;
                                bucket := bucket + salrec.salario;
				counter := counter +1;
			ELSE
				s.medsal := bucket/counter;
				RETURN NEXT s;
				s.deptcod := salrec.departamento;
				s.minsal := salrec.salario;
				s.maxsal := salrec.salario;
				counter := 1;
				bucket := salrec.salario;
			END IF;
		END IF;
	END LOOP;
   	s.medsal := bucket/counter;
	RETURN NEXT s;
	RETURN;
END 
$$
LANGUAGE 'plpgsql';

select * from medsal()

Relacionando:

select d.nome, a.minsal, a.maxsal, a.medsal 
from medsal() a, departamentos d 
where d.codigo = a.deptcod