¿Cómo puedo devolver varias filas de registros en PL / pgSQL

14

Estoy tratando de devolver múltiples registros usando el tipo de datos RECORD, ¿hay alguna manera de poder agregar a RECORD y agregar / agregar un nuevo valor con cada iteración a este RECORD?

es decir, quiero agregar para recque se recconvierta en un conjunto de filas cuando finalice el ciclo, que simplemente puedo VOLVER al final de mi función. Actualmente, estoy haciendo esto:

SELECT temp_table.col1, temp_table.col2, temp_table.col3
      INTO rec
      FROM temp_table
      WHERE temp_table.col3 = false;

mi código completo está aquí:

CREATE OR REPLACE FUNCTION validation()
  RETURNS RECORD AS $$
DECLARE
        rec RECORD;
        temp_row RECORD;
BEGIN

  CREATE TEMPORARY TABLE temp_table (col1 TEXT, col2 INTEGER, col3 BOOLEAN) ON COMMIT DROP;

  FOR temp_row IN SELECT * FROM staging.validation
  LOOP

    RAISE NOTICE 'sql: %', temp_row.sql;

    EXECUTE format('INSERT INTO temp_table %s', temp_row.sql);

    IF (SELECT DISTINCT temp_table.col3 FROM temp_table WHERE temp_table.col3 = false)=false THEN
      RAISE NOTICE 'there is a false value';

      SELECT temp_table.col1, temp_table.col2, temp_table.col3
      INTO rec
      FROM temp_table
      WHERE temp_table.col3 = false;
    END IF;


  END LOOP;
  RETURN rec;
END; $$
LANGUAGE plpgsql;

Salida de corriente después SELECT validation();

validation
(crea_ddf,8095,f)

Salida deseada

validation
(crea_ddf,8095,f)
(some_source_system,some_count,f)
(some_other_source_system,some_count,f)
(.....)
hky404
fuente
@EvanCarroll Hola Evan, esa es mi pregunta, que publiqué allí también ... en caso de que alguien se la pierda por aquí.
hky404
No estoy seguro de lo que estás tratando de hacer, ¿podrías explicarlo un poco más?
Evan Carroll
1
@ hky404: no publique mensajes cruzados; eso solo causa duplicación de esfuerzos.
Martijn Pieters

Respuestas:

14

La función necesita devolver un en SETOF RECORDlugar de RECORDy tener uno RETURN NEXTpor fila en lugar de uno solo RETURN, como en:

CREATE FUNCTION test() RETURNS SETOF RECORD AS $$
DECLARE
 rec record;
BEGIN
  select 1,2 into rec;
  return next rec;

  select 3,4 into rec;
  return next rec;
END $$ language plpgsql;

Llamador:

=> seleccione * de test () como x (a int, b int);
 a | si
--- + ---
 1 | 2
 3 | 4 4
(2 filas)

Tenga en cuenta que SQL está fuertemente y estáticamente tipado, el RECORDpseudo-tipo es difícil de trabajar.
A menudo es menos engorroso usar desde el principio un tipo compuesto con una definición completa de nombres y tipos para cada columna, ya sea con la TABLE(...)sintaxis para un tipo anónimo o con CREATE TYPEun tipo con nombre persistente.

Daniel Vérité
fuente
8

Use setof recordy return next recsi desea devolver múltiples registros de una función, por ejemplo:

create or replace function test_function()
    returns setof record 
    language plpgsql as $$
declare
    rec record;
begin
    for rec in
        select i, format('str%s', i), i/2*2 = i
        from generate_series(1, 3) i
    loop
        return next rec;
    end loop;
end $$;

Dicha función debe llamarse en la cláusula FROM con una lista de definición de columna:

select test_function(); -- NO

ERROR:  set-valued function called in context that cannot accept a set  

select * from test_function();  -- NO

ERROR:  a column definition list is required for functions returning "record"

select * from test_function() as (id int, str text, is_even boolean);

 id | str  | is_even 
----+------+---------
  1 | str1 | f
  2 | str2 | t
  3 | str3 | f
(3 rows)

Una mejor opción es usar returns table(...)y return query:

drop function if exists test_function();
create or replace function test_function()
    returns table (id int, str text, is_even boolean)
    language plpgsql as $$
begin
    return query
        select i, format('str%s', i), i/2*2 = i
        from generate_series(1, 3) i;
    -- you can use return query multiple times
    -- or assign values to columns
    -- and return the row:
    id = 100;
    str = 'extra';
    is_even = true;
    return next; -- without a parameter
end $$;

Uso:

select test_function(); -- possible but rather impractical

 test_function 
---------------
 (1,str1,f)
 (2,str2,t)
 (3,str3,f)
 (100,extra,t)
(4 rows)

select * from test_function();

 id  |  str  | is_even 
-----+-------+---------
   1 | str1  | f
   2 | str2  | t
   3 | str3  | f
 100 | extra | t
(4 rows)
klin
fuente
1

Esta es una bandera roja..

  1. Tiene una tabla validation.
  2. Mueves las filas a una tabla temporal staging.
  3. Cualquier fila con un temp_table.col3IS FALSO la devuelve al usuario
  4. Junto con cualquier otra fila en una lista específica de tablas donde esa columna es falsa.
  5. Luego sueltas la tabla temporal (en commit)

Solo haz esto ...

WITH t AS ( SELECT true AS runthis FROM staging.validation WHERE col3 IS FALSE )
SELECT *
FROM staging.validation
WHERE t.runthis && col3 = 3
UNION ALL 
  SELECT *
  FROM some_source_system
  WHERE t.runthis && some_source_system.col3 = 3
UNION ALL 
  SELECT *
  FROM some_other_source_system
  WHERE t.runthis && some_other_source_system.col3 = 3;

Incluso puedes poner eso en un VIEWsi quieres

Como nota al margen

SELECT DISTINCT temp_table.col3
FROM temp_table
WHERE temp_table.col3 = false

¿Qué hace DISTINCTaquí? Solo haz LIMIT uno. De hecho, diría que esto es aún más limpio.

SELECT true
FROM temp_table
WHERE temp_table.col3 = false
LIMIT 1;

Entonces no necesitas lo raro = false ) = FALSE

Evan Carroll
fuente