7.1.8. Comandos de Repetição

PL/pgSQL possui diversos tipos de estruturas para laços de repetição: LOOP, WHILE, FOR e FOREACH. Além de comandos para desvio do fluxo de instruções: EXIT e CONTINUE.

7.1.8.1. FOR com variável de controle do tipo inteiro

Uma das variantes do laço tipo FOR possibilita iterar em uma variável do tipo inteiro. Como exemplo deste tipo de laço, vamos criar uma função para computar um certo número de pontos posicionados de forma aleatória. Este exemplo irá mostrar também como retornar um conjunto de registros. No caso, os registros terão o seguinte esquema: (INTEGER, GEOMETRY).

CREATE OR REPLACE FUNCTION random_pt_generator(npts NUMERIC)
RETURNS SETOF RECORD
AS $$
DECLARE
    tupla RECORD;
    longitude NUMERIC;
    latitude NUMERIC;
    pt GEOMETRY;
BEGIN
    RAISE NOTICE 'Computando % pontos aleatórios...', npts;

    FOR i IN 1..npts LOOP
        longitude := 360.0 * random() - 180.0;
        latitude := 180.0 * random() - 90.0;

        pt := ST_SetSRID(ST_MakePoint(longitude, latitude), 4326);

        tupla := (i, pt);  -- ou: SELECT i, pt INTO tupla;

        RETURN NEXT tupla;

        IF (i % 1000) = 0 THEN
            RAISE NOTICE 'random_pt_generator: iteração %', i;
        END IF;
    END LOOP;

    RETURN;
END;
$$
LANGUAGE plpgsql;

A função random_pt_generator ilustra também um tipo de função que não pode ser utilizada somente na cláusula SELECT pois ela retorna um conjunto de tuplas. Neste caso, deve-se utilizar a função na cláusula FROM, mas com um cuidado especial, que é definir o tipo do conjunto retornado. Abaixo, mostramos como criar uma consulta que computa 5 pontos:

SELECT gid, ST_AsText(geom)
  FROM random_pt_generator(5) AS tabela(gid INTEGER, geom GEOMETRY);

Saída:

NOTICE:  Computando 5 pontos aleatórios...

 gid |                 st_astext
-----+--------------------------------------------
   1 | POINT(25.8069811575115 4.40172334201634)
   2 | POINT(-31.4398757368326 18.4167264495045)
   3 | POINT(-42.4448257684708 -74.7917356621474)
   4 | POINT(12.7397736534476 -52.6765340007842)
   5 | POINT(87.8538537584245 -22.9236561711878)
 (5 rows)

7.1.8.2. FOR sobre o resultado de uma consulta

Outro tipo de laço FOR pode ser empregado para iterar nas tuplas resultantes de uma consulta. Para ilustrar este tipo de laço, vamos construir uma função chamada build_pt_table que irá criar uma tabela geométrica com pontos gerados de forma aleatória usando como base nossa função random_pt_generator. Ao final esta função irá criar uma chave primária e um índice espacial sobre a coluna geométrica da tabela criada.

CREATE OR REPLACE FUNCTION build_pt_table(table_name TEXT,
                                          npts NUMERIC)
RETURNS VOID
AS
$$
DECLARE
    tupla RECORD;
    i     INTEGER DEFAULT 1;
BEGIN
    RAISE NOTICE 'Criando tabela %...', table_name;

    EXECUTE 'CREATE TABLE ' || table_name ||
            '(gid INTEGER, geom GEOMETRY(POINT,4326))';

    FOR tupla IN SELECT *
                   FROM random_pt_generator(npts) AS tabela(gid INTEGER, geom GEOMETRY)
              LOOP
        EXECUTE 'INSERT INTO ' || table_name ||
                '(gid, geom) VALUES($1, $2)' USING tupla.gid, tupla.geom;

        IF (i % 1000) = 0 THEN
            RAISE NOTICE 'Inseridos % tuplas!', i;
        END IF;

        i = i + 1;
    END LOOP;

    RAISE NOTICE 'Criando chave primária...';

    EXECUTE format('ALTER TABLE %I ADD PRIMARY KEY(gid)', table_name);

    RAISE NOTICE 'Criando índice espacial...';

    EXECUTE 'CREATE INDEX spidx_' || table_name ||
    '_geom ON ' || table_name || ' USING GIST(geom)';

    RETURN;
END;
$$
LANGUAGE plpgsql;

Usando a função build_pt_table, vamos criar uma tabela chamada pt10k contendo 10.000 pontos:

SELECT build_pt_table('pt10k', 10000);

Saída:

NOTICE:  Criando tabela pt10k...
NOTICE:  Computando 10000 pontos aleatórios...
...
NOTICE:  Inseridos 1000 tuplas!
NOTICE:  Inseridos 2000 tuplas!
NOTICE:  Inseridos 3000 tuplas!
NOTICE:  Inseridos 4000 tuplas!
NOTICE:  Inseridos 5000 tuplas!
NOTICE:  Inseridos 6000 tuplas!
NOTICE:  Inseridos 7000 tuplas!
NOTICE:  Inseridos 8000 tuplas!
NOTICE:  Inseridos 9000 tuplas!
NOTICE:  Inseridos 10000 tuplas!
NOTICE:  Criando chave primária...
NOTICE:  Criando índice espacial...
build_pt_table
----------------

(1 row)

Outra forma útil deste tipo de FOR utiliza o comando EXECUTE:

[<<rótulo>>]
FOR variável-tupla IN EXECUTE query-string [USING expressão [, ... ]] LOOP
    comandos;
END LOOP [rótulo];

7.1.8.3. WHILE

[<<rótulo>>]
WHILE expressão-booleana LOOP
    comandos;
END LOOP [rótulo];

7.1.8.4. LOOP: repetições incondicionais

[<<rótulo>>]
LOOP
    comandos;
END LOOP [rótulo];

7.1.8.5. EXIT: interrompendo um laço ou bloco de comandos

EXIT [rótulo] [WHEN expressão-booleana];

7.1.8.6. CONTINUE: desviando o fluxo de uma laço

CONTINUE [rótulo] [WHEN expressão-booleana];

7.1.8.7. FOREACH: iterando sobre arrays

[<<rótulo>>]
FOREACH variável [SLICE número] IN ARRAY expressão-array LOOP
    comandos;
END LOOP [rótulo];