PostgreSQL
 sql >> Baza danych >  >> RDS >> PostgreSQL

Refaktoryzuj funkcję PL/pgSQL, aby zwrócić wyniki różnych zapytań SELECT

Dynamiczny SQL i RETURN wpisz


Chcesz wykonać dynamiczny SQL . Zasadniczo jest to proste w plpgsql za pomocą EXECUTE . Nie potrzebujesz kursor. W rzeczywistości przez większość czasu lepiej jest bez wyraźnych kursorów.

Problem, z którym się spotykasz:chcesz zwrócić rekordy jeszcze nieokreślonego typu . Funkcja musi zadeklarować swój typ zwracany w RETURNS klauzula (lub z OUT lub INOUT parametry). W twoim przypadku musiałbyś sięgnąć do anonimowych zapisów, ponieważ liczba , nazwy i rodzaje zwróconych kolumn różni się. Na przykład:

CREATE FUNCTION data_of(integer)
  RETURNS SETOF record AS ...

Nie jest to jednak szczególnie przydatne. Przy każdym wywołaniu musisz podać listę definicji kolumn. Na przykład:

SELECT * FROM data_of(17)
AS foo (colum_name1 integer
      , colum_name2 text
      , colum_name3 real);

Ale jak możesz to zrobić, jeśli nie znasz wcześniej kolumn?
Możesz użyć mniej ustrukturyzowanych typów danych dokumentów, takich jak json , jsonb , hstore lub xml . Zobacz:

  • Jak przechowywać tabelę danych w bazie danych?

Ale na potrzeby tego pytania załóżmy, że chcesz zwrócić jak najwięcej pojedynczych, poprawnie wpisanych i nazwanych kolumn.

Proste rozwiązanie ze stałym typem zwrotu

Kolumna datahora wydaje się być dane, przyjmę typ danych timestamp i że zawsze są dwie dodatkowe kolumny o różnej nazwie i typie danych.

Nazwiska zrezygnujemy z nazw ogólnych w zwracanym typie.
Typy my też porzucimy i rzucimy wszystko na text od co typ danych można rzutować na text .

CREATE OR REPLACE FUNCTION data_of(_id integer)
  RETURNS TABLE (datahora timestamp, col2 text, col3 text)
  LANGUAGE plpgsql AS
$func$
DECLARE
   _sensors text := 'col1::text, col2::text';  -- cast each col to text
   _type    text := 'foo';
BEGIN
   RETURN QUERY EXECUTE '
      SELECT datahora, ' || _sensors || '
      FROM   ' || quote_ident(_type) || '
      WHERE  id = $1
      ORDER  BY datahora'
   USING  _id;
END
$func$;

Zmienne _sensors i _type zamiast tego mogą być parametrami wejściowymi.

Zwróć uwagę na RETURNS TABLE klauzula.

Zwróć uwagę na użycie RETURN QUERY EXECUTE . To jeden z bardziej eleganckich sposobów zwracania wierszy z dynamicznego zapytania.

Używam nazwy dla parametru funkcji, aby utworzyć USING klauzula RETURN QUERY EXECUTE mniej zagmatwane. $1 w ciągu SQL nie odnosi się do parametru funkcji, ale do wartości przekazanej przez USING klauzula. (Oba kosztują $1 w odpowiednim zakresie w tym prostym przykładzie).

Zwróć uwagę na przykładową wartość dla _sensors :każda kolumna jest rzutowana na typ text .

Ten rodzaj kodu jest bardzo podatny na wstrzyknięcie SQL . Używam quote_ident() aby się przed nim chronić. Łączenie kilku nazw kolumn w zmiennej _sensors zapobiega używaniu quote_ident() (i zazwyczaj jest to zły pomysł!). Upewnij się, że żadne złe rzeczy nie mogą się tam znajdować w inny sposób, na przykład indywidualnie uruchamiając nazwy kolumn przez quote_ident() zamiast. VARIADIC przychodzi mi do głowy parametr ...

Prostsze od PostgreSQL 9.1

W wersji 9.1 lub nowszej możesz użyć format() aby jeszcze bardziej uprościć:

RETURN QUERY EXECUTE format('
   SELECT datahora, %s  -- identifier passed as unescaped string
   FROM   %I            -- assuming the name is provided by user
   WHERE  id = $1
   ORDER  BY datahora'
  ,_sensors, _type)
USING  _id;

Ponownie, nazwy poszczególnych kolumn mogą być poprawnie zmieniane i byłby to czysty sposób.

Zmienna liczba kolumn współdzielących ten sam typ

Po zaktualizowaniu pytania wygląda na to, że typ zwrotu uległ zmianie

  • zmienna liczba kolumn
  • ale wszystkie kolumny tego samego typu double precision (alias float8 )

Użyj ARRAY w tym przypadku wpisz, aby zagnieździć zmienną liczbę wartości. Dodatkowo zwracam tablicę z nazwami kolumn:

CREATE OR REPLACE FUNCTION data_of(_id integer)
  RETURNS TABLE (datahora timestamp, names text[], values float8[])
  LANGUAGE plpgsql AS
$func$
DECLARE
   _sensors text := 'col1, col2, col3';  -- plain list of column names
   _type    text := 'foo';
BEGIN
   RETURN QUERY EXECUTE format('
      SELECT datahora
           , string_to_array($1)  -- AS names
           , ARRAY[%s]            -- AS values
      FROM   %s
      WHERE  id = $2
      ORDER  BY datahora'
    , _sensors, _type)
   USING  _sensors, _id;
END
$func$;

Różne kompletne typy stołów

Aby faktycznie zwrócić wszystkie kolumny tabeli , istnieje proste, wydajne rozwiązanie wykorzystujące typ polimorficzny :

CREATE OR REPLACE FUNCTION data_of(_tbl_type anyelement, _id int)
  RETURNS SETOF anyelement
  LANGUAGE plpgsql AS
$func$
BEGIN
   RETURN QUERY EXECUTE format('
      SELECT *
      FROM   %s  -- pg_typeof returns regtype, quoted automatically
      WHERE  id = $1
      ORDER  BY datahora'
    , pg_typeof(_tbl_type))
   USING  _id;
END
$func$;

Zadzwoń (ważne!):

SELECT * FROM data_of(NULL::pcdmet, 17);

Zastąp pcdmet w wywołaniu z dowolną inną nazwą tabeli.

Jak to działa?

anyelement to pseudo typ danych, typ polimorficzny, symbol zastępczy dla dowolnego typu danych nietablicowych. Wszystkie wystąpienia anyelement w funkcji ocenia się na ten sam typ, który został podany w czasie wykonywania. Dostarczając wartość określonego typu jako argument do funkcji, niejawnie definiujemy typ zwracany.

PostgreSQL automatycznie definiuje typ wiersza (złożony typ danych) dla każdej tworzonej tabeli, więc istnieje dobrze zdefiniowany typ dla każdej tabeli. Obejmuje to tabele tymczasowe, które są wygodne do użytku ad hoc.

Dowolny typ może być NULL . Oddaj NULL wartość, rzutuj na typ tabeli:NULL::pcdmet .

Teraz funkcja zwraca dobrze zdefiniowany typ wiersza i możemy użyć SELECT * FROM data_of() aby rozłożyć wiersz i uzyskać poszczególne kolumny.

pg_typeof(_tbl_type) zwraca nazwę tabeli jako typ identyfikatora obiektu regtype . Po automatycznej konwersji na text , identyfikatory są automatycznie ujęte w cudzysłów i kwalifikowane według schematu w razie potrzeby, automatyczna obrona przed wstrzyknięciem SQL. Może to nawet poradzić sobie z kwalifikowanymi według schematu nazwami tabel, w których quote_ident() zawiedzie. Zobacz:

  • Nazwa tabeli jako parametr funkcji PostgreSQL


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Read Committed jest koniecznością w przypadku rozproszonych baz danych SQL zgodnych z Postgres

  2. Czy istnieje coś takiego jak funkcja zip() w PostgreSQL, która łączy dwie tablice?

  3. Jak przechowywać tablicę lub wiele wartości w jednej kolumnie?

  4. Filtrowanie Django JSONField

  5. Typ danych Postgres ENUM czy SPRAWDŹ OGRANICZENIE?