Documentação do PostgreSQL 7.4.1 | ||||
---|---|---|---|---|
Anterior | Início | Capítulo 37. PL/pgSQL - Linguagem procedural SQL | Fim | Próxima |
As estruturas de controle provavelmente são a parte mais útil (e mais importante) da linguagem PL/pgSQL. Com as estruturas de controle do PL/pgSQL os dados do PostgreSQL podem ser manipulados de uma forma muita flexível e poderosa.
Estão disponíveis dois comandos que permitem retornar dados de uma função: RETURN e RETURN NEXT.
RETURN expressão;
O comando RETURN com uma expressão termina a função e retorna o valor da expressão para quem chama. Esta forma é utilizada pelas funções do PL/pgSQL que não retornam conjunto.
Qualquer expressão pode ser utilizada para retornar um tipo escalar. O resultado da expressão é automaticamente convertido no tipo de retorno da função conforme descrito nas atribuições. Para retornar um valor composto (linha), deve ser escrita uma variável registro ou linha como a expressão.
O valor retornado pela função não pode ser deixado indefinido. Se o controle atingir o final do bloco de nível mais alto da função sem atingir uma instrução RETURN, ocorrerá um erro em tempo de execução.
Se a função for declarada como retornando void, ainda assim deve ser especificada uma instrução RETURN; mas neste caso a expressão após o comando RETURN é opcional, sendo ignorada caso esteja presente.
RETURN NEXT expressão;
Quando uma função PL/pgSQL é declarada como retornando SETOF algum_tipo, o procedimento a ser seguido é um pouco diferente. Neste caso, os itens individuais a serem retornados são especificados em comandos RETURN NEXT, e um comando RETURN final, sem nenhum argumento, é utilizado para indicar que a função chegou ao fim de sua execução. O comando RETURN NEXT pode ser utilizado tanto com tipos de dado escalares quanto compostos; no último caso toda uma "tabela" de resultados é retornada.
As funções que utilizam RETURN NEXT devem ser chamadas da seguinte maneira:
SELECT * FROM alguma_função();
Ou seja, a função deve ser utilizada como uma fonte de tabela na cláusula FROM.
Na verdade, o comando RETURN NEXT não faz o controle sair da função: simplesmente salva o valor da expressão. Em seguida, a execução continua na próxima instrução da função PL/pgSQL. O conjunto de resultados é construído se executando comandos RETURN NEXT sucessivos. O RETURN final, que não deve possuir argumentos, faz o controle sair da função.
Nota: A implementação atual de RETURN NEXT para o PL/pgSQL armazena todo o conjunto de resultados antes de retornar da função, conforme foi mostrado acima. Isto significa que, se a função PL/pgSQL produzir um conjunto de resultados muito grande, o desempenho será ruim: os dados serão escritos em disco para evitar exaurir a memória, mas a função não retornará antes que todo o conjunto de resultados tenha sido gerado. Uma versão futura do PL/pgSQL deverá permitir aos usuários definirem funções que retornam conjuntos que não tenham esta limitação. Atualmente, o ponto onde os dados começam a ser escritos em disco é controlado pela variável de configuração sort_mem. Os administradores que possuem memória suficiente para armazenar conjuntos de resultados maiores, devem considerar o aumento deste parâmetro.
As instruções IF permitem executar os comandos com base em certas condições. A linguagem PL/pgSQL possui quatro formas de IF:
IF expressão_booleana THEN instruções END IF;
As instruções IF-THEN são a forma mais simples de IF. As instruções entre o THEN e o END IF são executadas se a condição for verdade. Senão, são saltadas.
Exemplo:
IF v_id_usuario <> 0 THEN UPDATE usuarios SET email = v_email WHERE id_usuario = v_id_usuario; END IF;
IF expressão_booleana THEN instruções ELSE instruções END IF;
As instruções IF-THEN-ELSE ampliam o IF-THEN permitindo especificar um conjunto alternativo de instruções a serem executadas se a condição for avaliada como falsa.
Exemplos:
IF id_pais IS NULL OR id_pais = '''' THEN RETURN nome_completo; ELSE RETURN hp_true_filename(id_pais) || ''/'' || nome_completo; END IF;
IF v_contador > 0 THEN INSERT INTO contador_de_usuários (contador) VALUES (v_contador); RETURN ''t''; ELSE RETURN ''f''; END IF;
As instruções IF podem ser aninhadas, como no seguinte exemplo:
IF linha_demo.sexo = ''m'' THEN sexo_extenso := ''masculino''; ELSE IF linha_demo.sexo = ''f'' THEN sexo_extenso := ''feminino''; END IF; END IF;
Na verdade, quando esta forma é utilizada uma instrução IF está sendo aninhada dentro da parte ELSE da instrução IF externa. Portanto, há necessidade de uma instrução END IF para cada IF aninhado, mais um para o IF-ELSE pai. Embora funcione, cresce de forma tediosa quando existem muitas alternativas a serem verificadas. Por isso existe a próxima forma.
IF expressão_booleana THEN instruções [ ELSIF expressão_booleana THEN instruções [ ELSIF expressão_booleana THEN instruções ...]] [ ELSE instruções ] END IF;
A instrução IF-THEN-ELSIF-ELSE fornece um método mais conveniente para verificar muitas alternativas em uma instrução. Formalmente equivale aos comandos IF-THEN-ELSE-IF-THEN aninhados, mas somente necessita de um END IF.
Abaixo segue um exemplo:
IF numero = 0 THEN resultado := ''zero''; ELSIF numero > 0 THEN resultado := ''positivo''; ELSIF numero < 0 THEN resultado := ''negativo''; ELSE -- hmm, a única outra possibilidade é que o número seja nulo resultado := ''nulo''; END IF;
Com as instruções LOOP, EXIT, WHILE e FOR pode-se fazer uma função PL/pgSQL repetir uma série de comandos.
[<<rótulo>>]
LOOP
instruções
END LOOP;
A instrução LOOP define um laço incondicional, repetido indefinidamente até ser terminado por uma instrução EXIT ou RETURN. Nos laços aninhados pode ser utilizado um rótulo opcional na instrução EXIT para especificar o nível de aninhamento que deve ser terminado.
EXIT [ rótulo ] [ WHEN expressão ];
Se não for especificado nenhum rótulo, o laço mais interno é terminado, e a instrução após o END LOOP é executada a seguir. Se o rótulo for especificado, este deve ser o rótulo do nível corrente ou de algum nível externo do laço ou bloco aninhado. Neste caso o laço ou bloco é terminado, e o controle continua na instrução após o END do laço ou do bloco.
Quando WHEN está presente, a saída do laço ocorre somente se a condição especificada for verdadeira, senão o controle passa para a instrução após o EXIT.
Exemplos:
LOOP -- algum processamento IF contador > 0 THEN EXIT; -- sair do laço END IF; END LOOP; LOOP -- algum processamento EXIT WHEN contador > 0; -- mesmo resultado do exemplo acima END LOOP; BEGIN -- algum processamento IF estoque > 100000 THEN EXIT; -- inválido; EXIT não pode ser utilizado fora do LOOP END IF; END;
[<<rótulo>>]
WHILE expressão LOOP
instruções
END LOOP;
A instrução WHILE repete uma seqüência de instruções enquanto a expressão de condição for avaliada como verdade. A condição é verificada logo antes de cada entrada no corpo do laço.
Por exemplo:
WHILE quantia_devida > 0 AND saldo_do_certificado_de_bonus > 0 LOOP -- algum processamento END LOOP; WHILE NOT expressão_booleana LOOP -- algum processamento END LOOP;
[<<rótulo>>] FOR nome IN [ REVERSE ] expressão .. expressão LOOP instruções END LOOP;
Esta forma do FOR cria um laço que interage num intervalo de valores inteiros. A variável nome é definida automaticamente como sendo do tipo integer, e somente existe dentro do laço. As duas expressões que fornecem o limite inferior e superior do intervalo são avaliadas somente uma vez, ao entrar no laço. Normalmente o passo da interação é 1, mas quando REVERSE é especificado se torna -1.
Alguns exemplos de laços FOR inteiros:
FOR i IN 1..10 LOOP -- algum processamento RAISE NOTICE ''i é %'', i; END LOOP; FOR i IN REVERSE 10..1 LOOP -- algum processamento END LOOP;
Se o limite inferior for maior do que o limite superior (ou menor, no caso do REVERSE), o corpo do laço não é executado nenhuma vez. Nenhum erro é gerado.
Utilizando um tipo diferente de laço FOR, é possível interagir através do resultado de uma consulta e manipular os dados. A sintaxe é:
[<<rótulo>>]
FOR registro_ou_linha IN comando LOOP
instruções
END LOOP;
Cada linha de resultado do comando (que deve ser um SELECT) é atribuída, sucessivamente, à variável registro ou linha, e o corpo do laço é executado uma vez para cada linha. Abaixo segue um exemplo:
CREATE FUNCTION cs_refresh_mviews() RETURNS integer AS ' DECLARE mviews RECORD; BEGIN PERFORM cs_log(''Atualização das visões materializadas...''); FOR mviews IN SELECT * FROM cs_materialized_views ORDER BY sort_key LOOP -- Agora "mviews" possui um registro de cs_materialized_views PERFORM cs_log(''Atualizando a visão materializada '' || quote_ident(mviews.mv_name) || ''...''); EXECUTE ''TRUNCATE TABLE '' || quote_ident(mviews.mv_name); EXECUTE ''INSERT INTO '' || quote_ident(mviews.mv_name) || '' '' || mviews.mv_query; END LOOP; PERFORM cs_log(''Fim da atualização das visões materializadas.''); RETURN 1; END; ' LANGUAGE plpgsql;
Se o laço for terminado por uma instrução EXIT, o último valor de linha atribuído ainda é acessível após o laço.
A instrução FOR-IN-EXECUTE é outra forma de interagir sobre linhas:
[<<rótulo>>]
FOR registro_ou_linha IN EXECUTE texto_da_expressão LOOP
instruções
END LOOP;
Esta forma é semelhante à anterior, exceto que o código fonte da instrução SELECT é especificado como uma expressão cadeia de caracteres, que é avaliada e replanejada a cada entrada no laço FOR. Isto permite ao programador escolher entre a velocidade da consulta pré-planejada e a flexibilidade da consulta dinâmica, da mesma maneira que na instrução EXECUTE pura.
Nota: Atualmente o analisador da linguagem PL/pgSQL faz distinção entre os dois tipos de laços FOR (inteiro e resultado de consulta), verificando se a variável de destino mencionada logo após o FOR foi declarada como uma variável tipo-registro ou tipo-linha. Se não foi, então é assumido como sendo um laço FOR inteiro. Isto pode ocasionar mensagens de erro bastante contra-intuitivas quando o problema é, digamos, que o nome da variável foi escrito errado após o FOR. Tipicamente a mensagem será algo como faltando ".." no final da expressão SQL.
Nota: Seção escrita pelo tradutor, não fazendo parte do manual original.
Exemplo 37-1. Utilização de SETOF para retornar valores calculados
Neste exemplo é definida uma função que lê a tabela compras
e retorna uma linha contendo o valor total do produto, calculado multiplicando a quantidade pelo preço unitário, e o valor acumulado, para cada linha da tabela compras
.
[1]
Abaixo está mostrado o script utilizado e o resultado obtido:
CREATE TABLE compras ( id SERIAL PRIMARY KEY, produto TEXT, qtd INTEGER, unitario NUMERIC(6,2) ); CREATE TYPE tipo_compras_calculado AS ( id INTEGER, produto TEXT, qtd INTEGER, unitario NUMERIC(6,2), valor NUMERIC(6,2), acumulado NUMERIC(6,2) ); CREATE OR REPLACE FUNCTION fun_compras_calculado() RETURNS SETOF tipo_compras_calculado AS ' DECLARE linha_compras RECORD; linha_calculada tipo_compras_calculado; BEGIN linha_calculada.acumulado := 0; FOR linha_compras IN SELECT id, qtd, produto, unitario FROM compras ORDER BY id LOOP linha_calculada.id := linha_compras.id; linha_calculada.produto := linha_compras.produto; linha_calculada.qtd := linha_compras.qtd; linha_calculada.unitario := linha_compras.unitario; linha_calculada.valor := linha_compras.qtd * linha_compras.unitario; linha_calculada.acumulado := linha_calculada.acumulado + linha_calculada.valor; RETURN NEXT linha_calculada; END LOOP; RETURN; END; ' LANGUAGE 'plpgsql'; INSERT INTO compras (produto, qtd, unitario) VALUES ('Leite em pó',2,4.79); INSERT INTO compras (produto, qtd, unitario) VALUES ('Cerveja em lata',12,1.05); INSERT INTO compras (produto, qtd, unitario) VALUES ('Salsicha hot-dog',1,2.99); SELECT * FROM fun_compras_calculado(); id | produto | qtd | unitario | valor | acumulado ----+------------------+-----+----------+-------+----------- 1 | Leite em pó | 2 | 4.79 | 9.58 | 9.58 2 | Cerveja em lata | 12 | 1.05 | 12.60 | 22.18 3 | Salsicha hot-dog | 1 | 2.99 | 2.99 | 25.17 (3 linhas)
Exemplo 37-2. Utilização de SETOF para retornar um calendário
Neste exemplo é definida uma função que retorna uma linha para cada data entre duas datas recebidas como parâmetro, contendo o número do dia da semana (0 para Domingo), o nome do dia da semana e a data. [2]
Abaixo está mostrado o script utilizado e o resultado obtido:
CREATE TYPE tipo_calendario AS ( num_dia INTEGER, nome_dia VARCHAR(7), data DATE ); CREATE OR REPLACE FUNCTION fun_calendario (DATE, DATE) RETURNS SETOF tipo_calendario AS ' DECLARE v_inicio ALIAS FOR $1; v_fim ALIAS FOR $2; v_atual DATE; v_calendario tipo_calendario; BEGIN v_atual = v_inicio; WHILE (v_atual<=v_fim) LOOP v_calendario.num_dia = extract(DOW FROM v_atual); IF (v_calendario.num_dia = 0) THEN v_calendario.nome_dia = ''Domingo''; ELSIF (v_calendario.num_dia = 1) THEN v_calendario.nome_dia = ''Segunda''; ELSIF (v_calendario.num_dia = 2) THEN v_calendario.nome_dia = ''Terça''; ELSIF (v_calendario.num_dia = 3) THEN v_calendario.nome_dia = ''Quarta''; ELSIF (v_calendario.num_dia = 4) THEN v_calendario.nome_dia = ''Quinta''; ELSIF (v_calendario.num_dia = 5) THEN v_calendario.nome_dia = ''Sexta''; ELSE v_calendario.nome_dia = ''Sábado''; END IF; v_calendario.data = v_atual; RETURN NEXT v_calendario; v_atual:=v_atual+1; END LOOP; RETURN; END;' LANGUAGE 'plpgsql'; SELECT * FROM fun_calendario('2005-02-25', '2005-03-05'); num_dia | nome_dia | data ---------+----------+------------ 5 | Sexta | 2005-02-25 6 | Sábado | 2005-02-26 0 | Domingo | 2005-02-27 1 | Segunda | 2005-02-28 2 | Terça | 2005-03-01 3 | Quarta | 2005-03-02 4 | Quinta | 2005-03-03 5 | Sexta | 2005-03-04 6 | Sábado | 2005-03-05 (9 linhas)
[1] |
Exemplo escrito pelo tradutor, baseado em um exemplo enviado por Mark Dilger |
[2] |
Exemplo escrito pelo tradutor, baseado em um exemplo enviado por Franco Bruno Borghesi |