GBC043 - Sistemas de Banco de Dados Funções e Gatilhos no PostgreSQL Ilmério Reis da Silva ilmerio@facom.ufu.br www.facom.ufu.br/~ilmerio/sbd UFU/FACOM/BCC
Página 2 Prgrama Teórico/Prático - SQL Linguagem SQL Comandos de criação e eliminação de tabelas Evolução de esquemas de banco de dados Comandos de inserção de tuplas em tabelas Comandos de alteração e supressão de tuplas Comandos de consulta (simples e complexos) Definição de visões Funções e gatilhos no servidor de BD Álgebra Relacional Cálculo Relacional
UFU/FACOM Página 3 SQL EM FUNÇÕES E GATILHOS Def. Funções e Gatilhos são procedimentos armazenados, interpretados e executados no servidor de BD, escritos em uma linguagem procedural que permite controle de fluxo e cálculos complexos, além de acesso ao BD via SQL. As funções são chamadas de forma explícita e os gatilhos por meio de eventos, implementando o conceito de BD Ativo
UFU/FACOM Página 4 OBJETIVOS DE FUNÇÕES E GATILHOS padronizar acesso por vários programas; orientar execuções para o servidor; controlar acesso via GRANT específico; adicionar estruturas de controle; implementar restrições de integridade; realizar cálculos complexos;
UFU/FACOM Página 5 FUNÇÕES E GATILHOS NO PostgreSQL 1. O SGBD armazena no catálogo comandos em linguagens procedurais com ou sem instruções SQL; 2. Há um interpretador da linguagem; 3. O interpretador também é uma função; 4. Linguagens: PL/PgSQL, PL/Tcl, PL/Perl, PL/Pynton... 5. Devem ser habilitadas pelo super-usuário, por exemplo: CREATE LANGUAGE plpgsql; 6. Outros interpretadores podem ser desenvolvidos pelo usuário;
Funções PlpgSql - Exemplo 0 CREATE OR REPLACE FUNCTION ehoras(employee.ssn%type) RETURNS DECIMAL(6,2) AS ' DECLARE myssn alias for $1; myhoras DECIMAL(6,2); BEGIN SELECT SUM(hours) FROM works_on INTO myhoras WHERE essn=myssn; RETURN myhoras; END; ' LANGUAGE 'plpgsql'; => SELECT * FROM ehoras('123456789'); UFU/FACOM Página 6
PlpgSql Bloco de sentença em Funções UM BLOCO DE SENTENÇA: [ << label >> ] [ DECLARE declarações de variáveis ] BEGIN sentenças END; OBS: - um bloco é uma senteça - tudo é convertido para caixa-baixa, exceto se "..." - variáveis de tipos semelhantes ao SQL com peculiaridades... UFU/FACOM Página 7
Plpgsql Definindo Variáveis nome [CONSTANT] tipo [NOT NULL] [ { DEFAULT := } expression ]; Exemplos: user_id INTEGER; quantity NUMERIC(5); url VARCHAR; myrow tablename%rowtype; myfield tablename.fieldname%type; arow RECORD; quantity INTEGER DEFAULT 32; url varchar := ''http://mysite.com''; user_id CONSTANT INTEGER := 10; OBS: neste caso, myfield é uma variável associada a um atribuo específico e myrow.field é uma variável associada ao atributo field UFU/FACOM Página 8
Plpgsql Parâmetros $1, $2, etc Aliases: subtotal ALIAS FOR $1; UFU/FACOM Página 9
Plpgsql A variável FOUND CREATE OR REPLACE FUNCTION does_employee_exist (employee.ssn%type) RETURNS bool AS ' DECLARE key ALIAS FOR $1; myemployee employee%rowtype; BEGIN SELECT INTO myemployee * FROM employee WHERE ssn=key; IF NOT FOUND THEN RETURN false; END IF; RETURN true; END; 'LANGUAGE 'plpgsql'; UFU/FACOM Página 10
Plpgsql ESTRUTURAS DE CONTROLE RETURN expression; - - encerra a função RETURN NEXT expression; - - retorna linha e não encerra a função Exemplo: some_func() RETURNS SETOF sometype... => SELECT * FROM some_func(); CONDIÇÕES IF... THEN... END IF; IF... THEN... ELSE... END IF; IF... THEN... ELSE IF... END IF END IF; IF... THEN... ELSIF... THEN... ELSE... END IF; LAÇOS: WHEN FOR UFU/FACOM Página 11
Plpgsql LAÇOS Bloco de sentenças é executado até: EXIT [ label ] [ WHEN expression ] ou RETURN [<< label >>] LOOP sentenças END LOOP; UFU/FACOM Página 12
Plpgsql WHILE [<< label >>] WHILE expressão LOOP sentenças END LOOP; UFU/FACOM Página 13
Plpgsql FOR [<< label >>] FOR nome_var IN [ REVERSE ] exp_from..exp_to LOOP sentenças END LOOP; EXXEMPLO VARRENDO O RESULTADO DE UMA CONSULTA [<< label >>] FOR {record row} IN select_query LOOP sentenças END LOOP; UFU/FACOM Página 14
Plpgsql Consultas dinâmicas EXECUTE query-string; Exemplo: EXECUTE ''UPDATE tab SET '' quote_ident(fieldname) " = " quote_literal(newvalue) ''WHERE ''; resultados de consultas podem ser usados em FOR-IN-EXECUTE GET DIAGNOSTICS variable = ROW_COUNT; UFU/FACOM Página 15
Plpgsql Varrendo resultado de consulta dinâmica [<< label >>] FOR {record row} IN EXECUTE text exp LOOP sentenças END LOOP; UFU/FACOM Página 16
Plpgsql IGNORANDO RESULTADOS PERFORM * FROM works on WHERE essn=my ssn; IF NOT FOUND THEN RAISE NOTICE ''...'' END IF; UFU/FACOM Página 17
UFU/FACOM Página 18 Plpgsql FUNÇÕES Exemplo 1 CREATE OR REPLACE FUNCTION tgenero(company.employee.ssn%type) RETURNS TEXT AS ' DECLARE myssn alias for $1; myrow company.employee%rowtype; mysexo TEXT DEFAULT '' ''; BEGIN SELECT * INTO myrow FROM company.employee WHERE ssn=myssn; IF myrow.sex = ''F'' THEN mysexo := ''Feminino''; ELSE IF myrow.sex = ''M'' THEN mysexo := ''Masculino''; END IF; END IF; RETURN myrow.fname '' '' myrow.minit ''. '' myrow.lname '' - '' mysexo; END; ' LANGUAGE 'plpgsql'; SELECT * FROM tgenero('123456789')
UFU/FACOM Página 19 Plpgsql FUNÇÕES Exemplo 2 - Problema Considere que um conjunto de comandos contendo junções e/ou funções de agregação têm uma frequência de execução muito alta. Então, por questões de desempenho, vc como projetista do BD optou por criar 'Visões Materializadas' para esses comandos. Como o PostgreSql não implementa este tipo de visão, vc usará uma tabela gerada por CREATE TABLE table_name AS SELECT... para cada comando. Para facilitar a atualização dos dados dessas tabelas, implemente uma função que atualize todas as tabelas. Para isso os nomes e comandos SELECT correspondentes serão armazenados em uma tabela chamada 'visoesmaterializadas' com o seguinte esquema: CREATE TABLE company.visoesmaterializadas ( mvname varchar(50), -- nome da tabela mvquery varchar(500), -- comando que obtem dados para inserção na tabela primary key (mvname) );
UFU/FACOM Página 20 Plpgsql FUNÇÕES Exemplo 2 Uma Tabela CREATE TABLE company.materializeddsummary AS SELECT dno as Dno, count(*) as NroEmp, sum(salary) as TotalS, avg(salary) as AverageS FROM company.employee GROUP BY dno; INSERT INTO company.visoesmaterializadas(mvname, mvquery) VALUES ('company.materializeddsummary', 'SELECT dno as Dno, count(*) as NroEmp, sum(salary) as TotalS, avg(salary) as AverageS FROM company.employee GROUP BY dno');
UFU/FACOM Página 21 Plpgsql FUNÇÕES Exemplo 2 Outra Tabela CREATE TABLE company.materializedpsummary1 AS SELECT pname AS PName, dname AS DName, COUNT(*) AS NroEmp, SUM(hours) AS SumHours FROM company.project, company.department, company.works_on WHERE pnumber=pno AND dnum=dnumber GROUP BY pname, dname; INSERT INTO company.visoesmaterializadas(mvname, mvquery) VALUES ('company.materializedpsummary1', 'SELECT pname AS PName, dname AS DName, COUNT(*) AS NroEmp, SUM(hours) AS SumHours FROM company.project, company.department, company.works_on WHERE pnumber=pno AND dnum=dnumber GROUP BY pname, dname');
UFU/FACOM Página 22 Plpgsql FUNÇÕES Exemplo 2 A Função CREATE OR REPLACE FUNCTION atualizavisoes() RETURNS TEXT AS ' DECLARE mviews RECORD; BEGIN FOR mviews IN SELECT * FROM company.visoesmaterializadas ORDER BY mvname LOOP RAISE NOTICE ''Limpando as tuplas de <%> '', mviews.mvname; EXECUTE ''DELETE FROM '' mviews.mvname; RAISE NOTICE ''Inserindo versao atualizada de <%> '', mviews.mvname; EXECUTE ''INSERT INTO '' mviews.mvname '' '' mviews.mvquery; END LOOP; RETURN ''As tabelas foram atualizadas!!''; END; ' LANGUAGE 'plpgsql'; SELECT * FROM atualizavisoes()
Plpgsql GATILHOS Def. GATILHOS ou TRIGGERS: implementam regras ativas IMPLEMENTAÇÃO Definindo o evento que acionará o gatilho: CREATE TRIGGER nome gatilho { BEFORE AFTER } { evento [OR...] } ON nome tabela FOR EACH { ROW STATEMENT } EXECUTE PROCEDURE nome função ( argumentos ) evento: INSERT, DELETE or UPDATE; FOR EACH: quantas vezes será acionado (só ROW funciona); argumentos: argumentos(literais) passados via TG_ARGV[] Função sem argumento com RETURNS TRIGGER; UFU/FACOM Página 23
UFU/FACOM Página 24 Plpgsql VARIÁVEIS ESPECIAIS EM GATILHOS VARIÁVEIS ESPECIAIS são criadas automaticamente NEW: tipo RECORD contendo nova linha em INSERT/UPDATE OLD: tipo RECORD contendo linha antiga em UPDATE/DELETE TG_NAME: o nome do gatilho TG_WHEN: BEFORE or AFTER TG_LEVEL: ROW or STATEMENT TG_OP: INSERT, UPDATE or DELETE TG_RELID: object ID da tabela TG_RELNAME: nome da tabela TG_NARGS: n umero de argumentos TG_ARGV[]: array contendo argumento
UFU/FACOM Página 25 Plpgsql RETORNOS EM GATILHOS RETORNO: deve ser NULL ou um RECORD/ROW NULL em BEFORE salta o restante das opera c oes RECORD/ROW em BEFORE prossegue com o valor retornado Em AFTER o valor retornado é ignorado
Plpgsql Gatilho EXEMPLO 0- Salário ok? CREATE FUNCTION emp_stamp () RETURNS TRIGGER AS ' BEGIN IF NEW.salary ISNULL THEN RAISE EXCEPTION '' % : Informe Salario'', NEW.fname, NEW.lname; END IF; IF NEW.salary <= 0 THEN RAISE EXCEPTION ''% % : Informe Salario > 0'', NEW.fname, NEW.lname; END IF; RETURN NEW; correto END; ' LANGUAGE 'plpgsql'; CREATE TRIGGER emp stamp BEFORE INSERT OR UPDATE ON employee FOR EACH ROW EXECUTE PROCEDURE emp_stamp(); UFU/FACOM Página 26
Plpgsql Gatilho EXEMPLO 1- Uma esposa CREATE OR REPLACE FUNCTION spouse () RETURNS TRIGGER AS ' BEGIN END; IF NEW.relationship = ''SPOUSE'' THEN END IF; PERFORM * FROM dependent WHERE essn=new.essn AND dependent_name <> NEW.dependent_name AND relationship=''spouse''; IF FOUND THEN RAISE EXCEPTION ''Employee % still have a spouse'', NEW.essn; END IF; RETURN NEW; ' LANGUAGE 'plpgsql'; CREATE TRIGGER spouse BEFORE INSERT OR UPDATE ON dependent FOR EACH ROW EXECUTE PROCEDURE spouse(); UFU/FACOM Página 27
Plpgsql Gatilho EXEMPLO 2- dsummary1 CREATE OR REPLACE FUNCTION fnewemp () RETURNS TRIGGER AS ' DECLARE mycomand TEXT; mydsummary materializeddsummary%rowtype; mynroemp materializeddsummary.nroemp%type; mytotals materializeddsummary.totals%type; myaverages materializeddsummary.averages%type; BEGIN IF NEW.dno ISNULL THEN RAISE EXCEPTION ''dno cannot be NULL value''; END IF; IF NEW.salary ISNULL THEN RAISE EXCEPTION ''% cannot have NULL salary'', NEW.fname; END IF; IF NEW.salary <= 0 THEN RAISE EXCEPTION ''% cannot have a negative salary'', NEW.fname; END IF; - - continua UFU/FACOM Página 28
Plpgsql Gatilho EXEMPLO 2- dsummary1 - - continuação END; SELECT * FROM materializeddsummary INTO mydsummary WHERE dno=new.dno; mynroemp = mydsummary.nroemp + 1; mytotals = mydsummary.totals + NEW.salary; myaverages = ((mydsummary.averages * mydsummary.nroemp) + NEW.salary) / mynroemp; mycomand := ''UPDATE '' ''materializeddsummary '' ''SET nroemp = '' quote_literal(mynroemp) '', totals = '' quote_literal(mytotals) '', averages = '' quote_literal(myaverages) '' '' ''WHERE dno = '' quote_literal(new.dno); EXECUTE mycomand; RETURN NEW; ' LANGUAGE 'plpgsql'; CREATE TRIGGER tnewemp BEFORE INSERT ON employee FOR EACH ROW EXECUTE PROCEDURE fnewemp(); UFU/FACOM Página 29
UFU/FACOM Página 30 Plpgsql EXERCÍCIOS DE IMPLEMENTAÇÃO => Lab Implementar os gatilhos que atualizam as tabelas anteriores para os comandos UPDATE e DELETE, mantendo as visões atualizadas Verificar a execução automática das funções quando os comandos INSERT, UPDATE e DELETE em employee forem executados Ver exercícios de funções e gatilhos para o BD SAFIS
UFU/FACOM Página 31 Bibliografia [EN] Capítulos 4,5,26 [SK] Capítulos [RG] Capítulos Manuais do PostgreSQL
UFU/FACOM Página 32 Plpgsql FIM Plpgsql