Jak mogę zwrócić wiele wierszy rekordów w PL / pgSQL

14

Próbuję zwrócić wiele rekordów przy użyciu typu danych RECORD, czy istnieje sposób, aby dołączyć do RECORD i dodawać / dodawać nową wartość z każdą iteracją do tego rekordu.

to znaczy, chcę dołączyć do rectak, że recstaje się zestawem wierszy po zakończeniu pętli, które mogę po prostu POWRÓT na końcu mojej funkcji. Obecnie robię to -

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

mój pełny kod jest tutaj:

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;

Wyjście prądowe po SELECT validation();

validation
(crea_ddf,8095,f)

Pożądane wyjście

validation
(crea_ddf,8095,f)
(some_source_system,some_count,f)
(some_other_source_system,some_count,f)
(.....)
hky404
źródło
@EvanCarroll Cześć Evan, to jest moje pytanie, które też tam zamieściłem ... na wypadek, gdyby ktoś tego nie zauważył.
hky404
Nie jestem pewien, co próbujesz zrobić, czy mógłbyś to wyjaśnić nieco więcej?
Evan Carroll
1
@ hky404: proszę nie przesyłać pocztą; powoduje to jedynie powielanie wysiłków.
Martijn Pieters,

Odpowiedzi:

14

Funkcja musi zwrócić SETOF RECORDzamiast RECORDi mieć jeden RETURN NEXTna wiersz zamiast jednego RETURN, jak w:

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;

Gość:

=> wybierz * z testu () jako x (a int, b int);
 a | b
--- + ---
 1 | 2)
 3 | 4
(2 rzędy)

Zauważ, że SQL jest silnie i statycznie typowany, więc RECORDpseudo-typ jest trudny do pracy.
Często mniej kłopotliwe jest stosowanie od samego początku typu złożonego z pełną definicją nazw i typów dla każdej kolumny, ze TABLE(...)składnią dla typu anonimowego lub z CREATE TYPEtrwałym nazwanym typem.

Daniel Vérité
źródło
8

Użyj setof recordi return next recjeśli chcesz zwrócić wiele rekordów z funkcji, przykład:

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 $$;

Taką funkcję należy wywołać w klauzuli FROM z listą definicji kolumn:

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)

Lepszą opcją jest użycie returns table(...)i 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 $$;

Stosowanie:

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
źródło
1

To jest czerwona flaga ..

  1. Masz tabelę validation.
  2. Przenosisz wiersze do tabeli tymczasowej staging.
  3. Wszelkie wiersze z temp_table.col3IS FALSE zwracane są do użytkownika
  4. Wraz z innymi wierszami na określonej liście tabel, w których ta kolumna jest fałszywa.
  5. Następnie upuszczasz tabelę temp (przy zatwierdzeniu)

Po prostu zrób to ...

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;

Możesz nawet to umieścić, VIEWjeśli chcesz

Na marginesie

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

Co DISTINCTtu robi? Po prostu zrób LIMIT jeden. W rzeczywistości argumentowałbym, że jest to jeszcze czystsze.

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

Więc nie potrzebujesz dziwnego = false ) = FALSE

Evan Carroll
źródło