Voltar

As estruturas de controle estão divididas em dois grupos distintos: estruturas condicionais, composto pelas declarações IF e Case; e estruturas de repetição, como é o caso dos Loop, While e o FOR.

Veremos nesse artigo como é fácil trabalhar com ambas no PostgreSQL. Lembrando que os exemplos aqui mostrados foram executados na versão 9.4 (versão mais estável até o momento de escrita desse artigo).

Estruturas condicionais

IF’s

Ao trabalharmos com as declarações IF e CASE, temos com a possibilidade de executar comandos alternativos, baseados em determinadas condições. Começando pela declaração do IF, podemos utilizá-la de três possíveis formas: IF..THEN, IF..THEN..ELSE e o IF..THEN..ELSIF.

O IF..THEN é estrutura mais simples, onde apenas uma condição que será executada caso a condição seja satisfeita. Caso contrário, ela não será executada. Podemos ver a estrutura da condição na Listagem 1.

Listagem 1. Sintaxe do IF...THEN.

IF boolean-expression THEN
      statements
  END IF;

A declaração IF-THEN-ELSE nos permite a especificação de um conjunto alternativo de declarações que devem ser executadas caso a condição não seja retornada como verdadeira. A sua sintaxe pode ser vista na Listagem 2.

Listagem 2. Sintaxe do IF...THEN...ELSE.

IF parentid IS NULL OR parentid = ''
  THEN
      RETURN fullname;
  ELSE
      RETURN hp_true_filename(parentid) || '/' || fullname;
  END IF;

A sintaxe apresentada na Listagem 3 refere-se ao terceiro tipo de declaração IF, o qual nos fornece uma sucessão de IF’s para teste. Nesta forma de declaração temos as condições IF sendo testadas de forma sucessiva até que o primeiro resultado positivo seja encontrado.

Listagem 3. Sintaxe para IF..THEN...ELSIF.

IF boolean-expression THEN
      statements
  [ ELSIF boolean-expression THEN
      statements
  [ ELSIF boolean-expression THEN
      statements
      ...]]
  [ ELSE
      statements ]
  END IF;

Vamos ver na prática alguns exemplos de utilização de cada um dos casos apresentados até o momento, conforme as Listagens 4, 5 e 6.

Listagem 4. Exemplo de utilização de condição simples.

IF codCliente <> 0 THEN
      UPDATE Cliente SET telefone = _telefone WHERE codCliente = _codCliente;
  END IF;

Neste exemplo da Listagem 4 temos apenas uma verificação referente ao código do cliente, que em caso de ser diferente de 0, realizaremos a atualização do telefone dele.

Listagem 5. Exemplo de utilização da segunda condição IF.

IF nomeCliente <> '' THEN
      INSERT INTO Clientes(nome) VALUES (nomeCliente);
      RETURN 'Nome inserido com sucesso!';
  ELSE
      RETURN 'O nome não foi inserido na base de dados!';
  END IF;

Na Listagem 5 temos a validação referente ao nome ser passado ou não: caso ele seja passado, a informação será salva na base de dados e uma mensagem de sucesso será apresentada; caso contrário, será apresentada uma mensagem alternativa referente a impossibilidade de salvar a informação.

Listagem 6. Exemplo de utilização da terceira condição IF.

IF number = 0 THEN
      result := 'zero';
  ELSIF number > 0 THEN
      result := 'positive';
  ELSIF number < 0 THEN
      result := 'negative';
  ELSE
      -- hmm, the only other possibility is that number is null
      result := 'NULL';
  END IF;

Case

Os Cases podem ser apresentados de duas formas:

  1. Case simples, onde a execução da condicional é baseada na igualdade de operandos, onde a expressão de busca é então avaliada de forma sucessiva, comparando cada uma das expressões presentes nas cláusulas WHEN. Caso a condição encontre um resultado, as declarações correspondentes são executadas, e, em seguida, o controle passa para a próxima instrução após o END CASE;
  2. Searched Case, onde a execução da condicional é baseada nas expressões booleanas. A expressão Booleana de cada cláusula WHEN é avaliada até que seja encontrada uma condição verdadeira. Em seguida, as declarações correspondentes são executadas, e o controle passa para a próxima instrução após o END CASE.

Vejamos nas Listagens 7 e 8 suas respectivas sintaxes e nas Listagens 9 e 10 exemplos dessas duas formas.

Listagem 7. Sintaxe para uma estrutura Case simples.

CASE search-expression
      WHEN expression [, expression [ ... ]] THEN
        statements
    [ WHEN expression [, expression [ ... ]] THEN
        statements
      ... ]
    [ ELSE
        statements ]
  END CASE;

Listagem 8. Sintaxe da searched Case.

CASE
      WHEN boolean-expression THEN
        statements
    [ WHEN boolean-expression THEN
        statements
      ... ]
    [ ELSE
        statements ]
  END CASE;

Listagem 9. Exemplo de utilização da estrutura simples.

CASE x
      WHEN 2, 4, 8 THEN
          msg := 'O valor pode ser dois, quarto ou oito.';
      ELSE
          msg := 'Outro valor diferente dos requisitados';
  END CASE;

Listagem 10. Exemplo da estrutura searched Case.

CASE
      WHEN x BETWEEN 0 AND 100 THEN
          msg := 'O valor apresentado está entre zero e cem.';
      WHEN x BETWEEN 300 AND 350 THEN
          msg := 'O valor está entre  300 e 350.';
  END CASE;

Estrutura de repetição

LOOP

Um LOOP é um laço incondicional que será repetido até que uma instrução EXIT ou RETURN seja encontrada. De acordo com a Listagem 11, temos o <<label>> (é opcional) que pode ser utilizado para as declarações do EXIT e do CONTINUE dentro de loops aninhados para especificarmos a qual laço essas declarações são referenciados. A sintaxe básica de utilização de um LOOP simples pode ser vista na Listagem 11.

Listagem 11. Sintaxe do LOOP básico.

[ <<label>> ]
  LOOP
      statements
  END LOOP [ label ];

Caso não tenhamos informado nenhum label, o laço mais interno será terminado e em seguida, a declaração END LOOP será executada. Isso não ocorrerá caso tenhamos um label declarado, pois se ele existe, então deverá ser encontrado em algum nível externo do laço ou mesmo em blocos aninhados para, em seguida ser encerrado.

Além da estrutura mais simples do LOOP, temos a ainda as declarações WHILE, FOR e FOREACH, onde podemos criar funções com a repetição de uma série de comandos.

Por exemplo, caso tivéssemos um WHEN especificado na Listagem 11, teremos que a saída do LOOP só será realizada quando uma expressão booleana for verdadeira; caso contrário, o controle será passado para a instrução EXIT.

Lembre-se que o EXIT pode ser utilizado em qualquer tipo de Loops.

Quando trabalhamos com blocos do tipo BEGIN...END, a cláusula EXIT é passada para a próxima instrução encontrada fora do bloco. Neste caso, para o seu devido funcionamento, torna-se necessário a declaração da label. Consideremos o seguinte exemplo apresentado na Listagem 12.

Listagem 12. Utilização do Loop básico utilizando o EXIT.

LOOP
      IF contador > 0 THEN
          EXIT; -- saindo do Loop
      END IF;
  END LOOP;
  LOOP
      EXIT WHEN contador > 0;
  END LOOP;
  <<blocoTeste>>
  BEGIN
      IF contador > 30 THEN
          EXIT blocoTeste; -- Neste ponto, temos a saída do bloco BEGIN...END
      END IF;
      -- quando o valor passar do solicitado, ele será ignorado
  END;

Caso nenhum label tenha sido especificado, a próxima iteração do loop que estiver mais interno será iniciada, ou seja, todas as instruções restantes presentes no corpo do loop serão ignoradas e o controle retornará para a expressão de controle do loop para determinar se uma outra iteração será necessária. Caso o label seja especificado, a execução irá continuar. E caso tenhamos especificado a cláusula WHEN, a próxima iteração do loop será iniciada apenas se a expressão booleana for verdade. Caso contrário, o controle passa para a instrução após o CONTINUE ser encontrado.

No que se refere a cláusula CONTINUE, esta pode ser utilizada com todos os tipos de Loops.

Um exemplo pode ser visto na Listagem 13.

Listagem 13. Exemplo de loops com WHILE e CONTINUE.

LOOP
      EXIT WHEN contador > 100;
      CONTINUE WHEN contador < 50;
  END LOOP;

WHILE

A instrução WHILE repete uma sequência de instruções desde que a expressão booleana seja avaliada como verdade. A expressão é verificada imediatamente antes de cada entrada para o corpo do laço. Na Listagem 14 vemos como ele funciona.

Listagem 14. Utilizando a instrução WHILE.

CREATE FUNCTION add_two_loop (integer, integer) RETURNS integer AS '
    DECLARE
      numero_minimo ALIAS FOR $1;
      numero_maximo ALIAS FOR $2;
      resultado INTEGER = 0;
    BEGIN
      WHILE resultado != numero_maximo LOOP
        resultado := resultado + 1;
      END LOOP;    
      RETURN resultado;
    END;
  LANGUAGE 'plpgsql';

A função apresentada demonstra a utilização de um loop WHILE criado para adicionar de um em um até que o número encontre o valor especificado. Ambos os números especificados, tanto o inicial quanto o final são fornecidos pelo usuário como argumentos da função. O operador de desigualdade indica que o loop while será executado enquanto a variável resultado não for igual à variável numero_maximo.

FOR

Ao trabalharmos com a instrução FOR podemos criar laços de iteração que são executados sobre uma série de inteiros. Observe na Listagem 15 que temos a variável “name”, definida automaticamente definida como um valor inteiro e só existe dentro do laço. As duas expressões que dão o limite inferior e superior do intervalo são avaliadas uma vez ao entrar no loop. Se a cláusula BY não for especificada, a iteração retornará o valor 1, caso contrário, é o valor especificado na cláusula BY.

Se o termo REVERSE for especificado, então o valor é adicionado na ordem inversa no decorrer da iteração.

Listagem 15. Sintaxe básica da instrução FOR.

[ <<label>> ]
  FOR name IN [ REVERSE ] expression .. expression [ BY expression ] LOOP
      statements
  END LOOP [ label ];

Para melhor entendimento sobre a utilização da instrução FOR, temos um exemplo na Listagem 16 com dois loops FOR, sendo um deles com o termo REVERSE e o outro é sem este termo ser especificado.

Listagem 16. Utilizando a instrução FOR com e sem REVERSE

FOR i IN 1..45 LOOP
      -- dessa forma, teremos que os valores serão trazidos em ordem crescente com o Loop FOR
  END LOOP;
  FOR i IN REVERSE 45..1 LOOP
      -- dessa forma, teremos que os valores serão trazidos em ordem decrescente com o Loop FOR
  END LOOP;

Caso o limite inferior seja maior do que o limite superior, o corpo do laço não será executado. Caso um label seja especificado, por estar ligado ao laço FOR, então a variável de contagem inteiro poderá ser referenciada com um nome qualificador, utilizando esse label.

Podemos também trabalhar com o Loop FOR para percorrer os resultados de uma consulta e manipular esses dados, como mostra a Listagem 17.

Listagem 17. Sintaxe do loop FOR com Queries.

[ <<label>> ]
  FOR target IN query LOOP
      statements
  END LOOP [ label ];

O termo “target” é uma varável de registro, variável de linha ou mesmo uma lista de variáveis escalares separadas por vírgula, que pode ser atribuído de forma sucessiva para cada linha resultante da consulta, como mostra o exemplo da Listagem 18.

Listagem 18. Utilizando queries com o Loop FOR.

CREATE FUNCTION extrairTitulo2 () RETURNS text AS '
    DECLARE
      codigo INTEGER;
      titulo TEXT;
      texto TEXT;
      row_data livro%ROWTYPE;
   
    BEGIN
      FOR i IN 0..15 LOOP
        SELECT INTO assunto , titulo FROM titulos WHERE id = i;
        texto = texto || ''\n'' || titulo || '':\n'';
        FOR row_data IN SELECT * FROM livro
          WHERE codigo = i LOOP
          texto := texto || row_data.titulo || ''\n'';
        END LOOP;
      END LOOP;
      RETURN texto;
    END;
  $ LANGUAGE plpgsql;

A função extrairTitulo() é usada para realizar a extração de uma lista contendo todos os títulos de livros que existem no banco de dados, organizados por assunto. Quando um livro não tem o título encontrado, este passa a receber uma linha em branco. A lista é retornada como uma variável de texto. Um laço FOR é utilizado dentro da função extrairTitulo()para percorrer as disciplinas oferecidas por número.

FOREACH

O FOREACH é bastante parecido com o loop FOR: o que difere é que o FOREACH realiza a iteração através dos elementos de uma matriz. Podemos ver a sintaxe dessa estrutura na Listagem 19.

Listagem 19. Sintaxe do Loop Foreach.

[ <<label>> ]
  FOREACH target [ SLICE number ] IN ARRAY expression LOOP
      statements
  END LOOP [ label ];

O termo SLICE pode não ser especificado, já que ele serve para decompor uma informação em partes menores para que possamos melhor entender o dado que está sendo analisado.

Em todo caso, os loops realizam a iteração através de elementos individuais da matriz criada pelo resultado da expressão.

A variável target é atribuída a cada valor de elemento em sequência, e o corpo do laço é então executado para cada um dos elementos, como pode ser visto no exemplo da Listagem 20, representada por valores inteiros.

Listagem 20. Exemplo de utilização do loop FOREACH.

CREATE FUNCTION sum(int[]) RETURNS int8 AS $
  DECLARE
    s int8 := 0;
    x int;
  BEGIN
    FOREACH x IN ARRAY $1
    LOOP
      s := s + x;
    END LOOP;
    RETURN s;
  END;
  $ LANGUAGE plpgsql;

Aprendendo a lidar com erros

Além de entendermos os loops, precisamos entender como resolver os casos em que ocorrem erros, interrompendo a execução da operação. Podemos realizar a intercepção dos erros e recuperá-los através da utilização de blocos BEGIN...END com as cláusulas de exceção, como mostra a sintaxe presente na Listagem 21.

Listagem 21. Sintaxe de utilização de blocos de exceção.

[ <<label>> ]
  [ DECLARE
      declarations ]
  BEGIN
      statements
  EXCEPTION
      WHEN condition [ OR condition ... ] THEN
          handler_statements
      [ WHEN condition [ OR condition ... ] THEN
            handler_statements
        ... ]
  END;

Caso não ocorra erros, teremos a execução de todas as declarações e, em seguida, o controle passa para a próxima instrução após o END. Caso ocorra algum erro, o controle passará para a lista de exceções. Nesse momento, a lista passará a buscar pela primeira condição correspondente ao erro ocorrido e caso seja encontrada, as instruções do tratamento correspondente serão executadas, e após isso o controle passará para a próxima instrução após END.

Quando um erro é pego por uma cláusula de exceção, as variáveis locais da função PL/pgSQL permanecem como estavam quando ocorreu o erro, mas todas as alterações referentes ao estado do banco de dados que são persistidas dentro do bloco são desfeitas, como mostra o exemplo de divisão por 0, apresentado na Listagem 22.

Listagem 22. Exemplo de utilização de exceções.

INSERT INTO cliente(nome, profissao) VALUES('Edson Dionisio', 'Desenvolvedor');
  BEGIN
      UPDATE cliente SET nome = 'Edson' WHERE profissao = 'Desenvolvedor';
      x := x + 1;
      y := x / 0;
  EXCEPTION
      WHEN division_by_zero THEN
          RAISE NOTICE 'uma exceção de divisão por zero foi levantada…';
          RETURN x;
  END;

Como podemos ver, ao chegarmos a variável y a nossa instrução irá falhar devido a um erro de divisão por zero, com isso o erro será pego pela nossa cláusula de exceção. O valor retornado pela instrução RETURN será o valor incrementado de x, mas os efeitos do comando UPDATE serão revertidos.

Na Listagem 23 temos o exemplo do tratamento de exceção para a execução das cláusulas UPDATE ou INSERT.

Listagem 23. Exceções com UPDATE / INSERIR

CREATE TABLE testesExcecao (numero INT PRIMARY KEY, texto TEXT);
  CREATE FUNCTION testesExcecaoComposto(chave INT, dados TEXT) RETURNS VOID AS
  $
  BEGIN
      LOOP
          -- inicialmente tentamos atualizar a chave primária
          UPDATE testesExcecao SET texto = data WHERE numero = chave;
          IF found THEN
              RETURN;
          END IF;
          -- Em seguida, tentamos inserir um novo registro, de forma que se alguém tentar inserir o mesmo código, teremos um erro de chave única.
          BEGIN
              INSERT INTO testesExcecao(numero, texto) VALUES (chave, dados);
              RETURN;
          EXCEPTION WHEN unique_violation THEN
          END;
      END LOOP;
  END;
  $
  $ LANGUAGE plpgsql;
   
  SELECT testesExcecaoComposto(1, 'Edson');
  SELECT testesExcecaoComposto(1, 'Caroline');

O erro de codificação unique_violation é causado pela Inserção, além do erro com relação a índices exclusivos presentes na tabela, uma vez que a operação irá ser repetida, independentemente de qual índice cause o erro. Para que a manipulação das exceções encontre o erro específico, podemos usar variáveis especiais ou o comando GET DIAGNOSTICS STACKED.

Uma das variáveis especiais é a SQLSTATE, a qual, contém o código de erro que corresponde a exceção que foi levantada. Uma outra variável é a SQLERRM, que contém a mensagem de erro associada a exceção. Podemos obter informações sobre a exceção atual utilizando o comando GET DIAGNOSTICS STACKED, da seguinte forma:

GET STACKED DIAGNOSTICS variable { = | := } item [ , ... ];

Dessa forma, temos cada item é uma palavra chave que identifica o valor de estado a ser atribuído à variável especificada.

Na Listagem 24 vemos outros tipos de tratamento de exceções: CONSTRAINT_NAME, TABLE_NAME, SCHEMA_NAME, dentre outros.

Listagem 24. Utilizando comandos especiais de exceção.

DECLARE
    texto1 text;
    texto2 text;
  BEGIN
    -- outros códigos aqui...
  EXCEPTION WHEN OTHERS THEN
    GET STACKED DIAGNOSTICS texto1 = MESSAGE_TEXT,
                            texto2 = CONSTRAINT_NAME,
  END;

Esperamos que tenham gostado!