Anda di halaman 1dari 31

PL/pgSQL por Diverso e Lucro

Roberto Mello
3a. Conferncia Brasileira de PostgreSQL - PGCon-BR Campinas - 2009

Objetivos
Apresentar as partes principais do PL/pgSQL Apresentar casos de uso comum do PL/pgSQL Apresentar alguns casos do que NO fazer com PL/ pgSQL Apresentar os diferentes tipos de gatilhos Como criar gatilhos com PL/pgSQL Exerccios para prtica Resolver dvidas (ou pelo menos tentar)
2

Como aproveitar melhor


Tenha a documentao em mos http://www.postgresql.org/docs/8.4/static/ Tenha o PostgreSQL instalado no seu laptop Se no tiver, pode escrever no seu editor de texto favorito, ou mesmo em papel Estou usando exemplos simples, vrios dos quais seriam melhor feitos direto em SQL, ao invs de PL

Modelo de Dados
CREATE TABLE vendedores( vendedor_id SERIAL NOT NULL PRIMARY KEY, nome varchar(256)); INSERT INTO vendedores VALUES (DEFAULT, Fabio), (DEFAULT, Joao), (DEFAULT, Leonardo); CREATE TABLE vendas( item_id integer, vendedor_id integer REFERENCES vendedores.vendedor_id, quantidade integer, preco numeric); INSERT INTO vendas VALUES (1, 1, 5, 142), (2, 3, 54, 45), (3, 2, 21, 467), (4, 2, 32, 343), (5, 1, 21, 235);;

PL/pgSQL
uma linguagem de procedimentos (mas no a nica aceita no PostgreSQL) Adiciona estrutura e controle ao SQL parecida com outras linguagens da famlia C Roda dentro do servidor, evitando a viagem e converses entre cliente e servidor Um dos seus principais usos em gatilhos Acesso todas as funcionalidades do SQL
5

Estrutura
CREATE OR REPLACE FUNCTION uma_funcao(preco numeric) RETURNS integer AS $$ DECLARE -- declaraes de variveis quantidade integer := 30; BEGIN -- expresses, consultas, clculos, etc RETURN quantidade * preco; END; $$ LANGUAGE plpgsql;

Parmetros
Podem ser de entrada (IN) ou sada (OUT)
CREATE OR REPLACE FUNCTION calc_item(p_item_id int, OUT quantidade int, OUT total numeric) AS $$ BEGIN SELECT vendas.quantidade, vendas.quantidade * vendas.preco INTO quantidade, total FROM vendas WHERE item_id = p_item_id; END; $$ LANGUAGE plpgsql;

ltimos valores atribudos s variveis de sada so os que valem Uso de RETURN seria redundante

Blocos
CREATE OR REPLACE FUNCTION uma_funcao() RETURNS integer AS $$ << fora >> DECLARE quantidade integer := 30; BEGIN RAISE NOTICE 'Quantidade aqui %', quantidade; -- 30 quantidade := 50; --- Criamos um sub-bloco -DECLARE quantidade integer := 80; BEGIN RAISE NOTICE 'Quantidade aqui %', quantidade; -- 80 RAISE NOTICE 'Quantidade de fora aqui %', fora.quantidade; END; RAISE NOTICE 'Quantidade aqui %', quantidade; RETURN quantidade; END; $$ LANGUAGE plpgsql; -- 50

-- 50

Sobre transaes
Uma funo PL/pgSQL e todos os seus blocos roda INTEIRAMENTE dentro de uma transao BEGIN e END dentro de uma funo so para delinear os blocos, e no para controlar transaes Dentro da funo no h como alterar a transao da funo (e.g. COMMIT/ROLLBACK) Pode-se criar sub-transaes dentro da funo usando controle de excees (a ser visto mais adiante)
9

Operadores
variavel := expressao expressao ser avaliada como SQL e deve retornar um valor escalar *, +, -, / Qualquer outro operador permitido pelo mecanismo de SQL

10

Exerccio 1

Crie uma funo que aceite 2 nmeros inteiros e retorne a soma dos dois nmeros

11

Executando Consultas
SQL que no retorna registros pode ser executado diretamente.
CREATE FUNCTION remarca( p_item_id int, p_pct int) RETURNS NULL AS $$ DECLARE novo_preco numeric DEFAULT 0; BEGIN UPDATE vendas SET preco = preco + 1 WHERE item_id = p_item_id; -- Usando PERFORM ao invs de SELECT descartar todos -- os resultados que sejam retornados PERFORM remarca_todos(); -- Varivel especial FOUND IF FOUND THEN raise notice resultados descartados; END IF; END; $$ LANGUAGE plpgsql;
12

Executando Consultas
Resultados de consultas precisam ser postos em variveis
CREATE FUNCTION remarca( p_item_id int, p_pct int) RETURNS numeric AS $$ DECLARE novo_preco numeric DEFAULT 0; BEGIN UPDATE vendas SET preco = preco + (preco * 1/p_pct) WHERE item_id = p_item_id RETURNING preco INTO novo_preco; RETURN novo_preco; END; $$ LANGUAGE plpgsql;
13

Executando Consultas
Do mesmo jeito para SELECT, INSERT, DELETE
SELECT expresses_select INTO [STRICT] alvo FROM ...; INSERT ... RETURNING expresses INTO [STRICT] alvo; UPDATE ... RETURNING expresses INTO [STRICT] target; DELETE ... RETURNING expresses INTO [STRICT] alvo;

14

Exerccio 2

Crie uma funo que: Receba um identicador de um vendedor Retorne a soma das vendas daquele vendedor

15

Registros
Variveis do tipo RECORD armazenam registros inteiros DECLARE uma_venda RECORD; BEGIN SELECT * INTO uma_venda FROM vendas WHERE item_id = p_item_id; RAISE NOTICE Preco: %,uma_venda.preco;
16

Estruturas de Controle
IF var <> 0 THEN faa algo ELSIF var >= 2 THEN faa outra coisa ELSE trate casos extras END IF; WHILE preco > 410 LOOP RAISE NOTICE Bom Lucro; END LOOP; CASE vendedor WHEN 1, 3 THEN faca algo aqui ELSE outros calculos END CASE; CASE WHEN vendedor = 1 THEN comissao := 100; WHEN vendedor = 2 THEN comissao := 60; ELSE comissao := 30; END CASE;
17

FOR i IN 1..10 LOOP --i ir de 0 a 10 --no lao END LOOP;

Passando pelos Resultados de Consultas


DECLARE v_vendedores RECORD; BEGIN FOR v_vendedores IN SELECT * FROM vendedores ORDER BY vendedor_id LOOP RAISE NOTICE %, v_vendedores.nome; END LOOP;

18

Exerccio 3

Crie uma funo que receba um vendedor_id e imprima Parabens! para cada venda (tabela vendas) que o total (preco * quantidade) da venda tenha passado de 800

19

Retornando Mltiplos Registros


CREATE OR REPLACE FUNCTION calc_item(p_item_id int) RETURNS TABLE (quantidade int, total numeric) AS $$ BEGIN RETURN QUERY SELECT vendas.quantidade, vendas.quantidade * vendas.preco FROM vendas WHERE item_id = p_item_id; END; $$ LANGUAGE plpgsql;

RETURNS TABLE o mesmo que declarar vrios parmetros OUT e especicar RETURNS SETOF umtipo
20

Retornando Mltiplos Registros


CREATE OR REPLACE FUNCTION getAllFoo() RETURNS SETOF foo AS $BODY$ DECLARE r foo%rowtype; BEGIN FOR r IN SELECT * FROM foo WHERE fooid > 0 LOOP -- pode-se fazer processamento aqui RETURN NEXT r; -- returna o registro corrente do SELECT END LOOP; RETURN; END $BODY$ LANGUAGE 'plpgsql';

21

Retornando Mltiplos Registros


CUIDADO!! RETURN NEXT e RETURN QUERY armazenam TODO o resultado na memria antes de retornar, o que causa uso exagerado de memria e disco se o resultado for de muitos registros

22

Cursores
Cursores so resultados de consultas que se pode ir e voltar na lista dos resultados, ao invs de s adiante So ecientes no que diz respeito ao uso de memria S podem ser usados dentro de uma transao Operaes:
FETCH [ direction { FROM | IN } ] cursor INTO target; MOVE [ direction { FROM | IN } ] cursor; UPDATE table SET ... WHERE CURRENT OF cursor; DELETE FROM table WHERE CURRENT OF cursor;
23

Cursores
CREATE FUNCTION myfunc(refcursor, refcursor) RETURNS SETOF refcursor AS $$ BEGIN OPEN $1 FOR SELECT * FROM table_1; RETURN NEXT $1; OPEN $2 FOR SELECT * FROM table_2; RETURN NEXT $2; END; $$ LANGUAGE plpgsql; -- precisa estar numa transao para usar cursores. BEGIN; SELECT * FROM myfunc('a', 'b'); FETCH ALL FROM a; FETCH ALL FROM b; COMMIT;

24

Gatilhos
Funes que so chamadas automaticamente antes (before) ou depois (after) de Insert, Update, Delete, Truncate Funo tem que ser declarada especcamente como de gatilho, e um CREATE TRIGGER tem que ser rodado por tabela sobre a qual o gatilho incidir

25

Gatilhos - variveis
NEW - armazena o novo registro em insert/update OLD - armazena o registro velho em UPDATE/DELETE TG_NAME - nome do gatilho disparado TG_WHEN - string contendo BEFORE ou AFTER TG_LEVEL - string com ROW ou STATEMENT dependendo do gatilho TG_OP - string com INSERT, UPDATE, DELETE, or TRUNCATE indicando a operao TG_RELID - oid da tabela que chamou o gatilho TG_TABLE_NAME - nome da tabela que chaou o gatilho TG_TABLE_SCHEMA - nome do esquema da tabela que chamou o gatilho TG_NARGS - nmero de argumentos dados funo do gatilho no CREATE TRIGGER TG_ARGV[] - matriz com os argumentos dados no CREATE TRIGGER
26

Gatilhos
CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$ BEGIN IF NEW.empname IS NULL THEN RAISE EXCEPTION 'empname no pode ser nulo'; END IF; IF NEW.salary IS NULL THEN RAISE EXCEPTION '% no pode ter salrio nulo', NEW.empname; END IF; IF NEW.salary < 0 THEN RAISE EXCEPTION '% no pode ter salrio negativo', NEW.empname; END IF; -- Lembre quem mudou a folha, e quando NEW.last_date := current_timestamp; NEW.last_user := current_user; RETURN NEW; END; $emp_stamp$ LANGUAGE plpgsql; CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp FOR EACH ROW EXECUTE PROCEDURE emp_stamp();

27

Exerccio 4
Crie uma funo de gatilho que insira um novo registro na tabela comissoes cada vez que um vendedor zer uma nova venda (tabela vendas) cujo preo seja maior que 400 A comisso dever ser de 15% do valor da venda Tabela comissoes tem colunas: comissao_id, vendedor_id, item_id, comissao

28

Armadilhas a serem evitadas


Programadores inexperientes em SQL tentam fazer tudo em PL/pgSQL, ao invs de procurar a maneira idiomtica (e geralmente muito mais rpida) de se fazer em SQL Consultas dentro de funes tambm precisam ser testadas e otimizadas, seno viram buraco negro Mudanas de dados em gatilhos no devem ser silenciosas: erros tem que ser noticados aplicao para que ela seja corrigida
29

Ex: Gatilho errneo


BEGIN /* Dont allow date_created or created_by fields to be updated */ IF (UPDATE = TG_OP) THEN bool_has_column := func_table_has_column(TG_RELNAME::varchar, created_by); IF bool_has_column THEN NEW.created_by = OLD.created_by; END IF; NEW.date_created = OLD.date_created; END IF; RETURN NEW; END;

Dados so mudados slienciosamente, sem log


30

Perguntas? robertomello@2reng.com.br

31

Anda mungkin juga menyukai