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
(aliasfloat8
)
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