Refactor a funkcja PL / pgSQL zwracająca wyjście różnych zapytań SELECT
Napisałem funkcję, która wyświetla zapytanie PostgreSQL SELECT
dobrze uformowane w formie tekstowej. Teraz nie chcę już wypisywać tekstu, ale faktycznie uruchamiam wygenerowaną instrukcję SELECT
z bazą danych i zwracam wynik - tak jak samo zapytanie.
Co mam do tej pory:
CREATE OR REPLACE FUNCTION data_of(integer)
RETURNS text AS
$BODY$
DECLARE
sensors varchar(100); -- holds list of column names
type varchar(100); -- holds name of table
result text; -- holds SQL query
-- declare more variables
BEGIN
-- do some crazy stuff
result := 'SELECT\r\nDatahora,' || sensors ||
'\r\n\r\nFROM\r\n' || type ||
'\r\n\r\nWHERE\r\id=' || $1 ||'\r\n\r\nORDER BY Datahora;';
RETURN result;
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
ALTER FUNCTION data_of(integer) OWNER TO postgres;
sensors
przechowuje listę nazw kolumn dla tabeli type
. Są one deklarowane i wypełniane w trakcie wykonywania funkcji. Ostatecznie posiadają wartości like:
sensors
:'column1, column2, column3'
Z wyjątkiemDatahora
(timestamp
) wszystkie kolumny są typudouble precision
.type
:'myTable'
Może to być nazwa jednej z czterech tabel. Każda ma inne kolumny, z wyjątkiem wspólnej kolumnyDatahora
.
Definicja bazowych tabel .
Zmienna sensors
będzie przechowywać wszystkie kolumny wyświetlane tutaj dla odpowiedniej tabeli w type
. Na przykład: If type
is pcdmet
then sensors
będzie 'datahora,dirvento,precipitacao,pressaoatm,radsolacum,tempar,umidrel,velvento'
Zmienne są używane do budowy instrukcji SELECT
, która jest przechowywana w result
. Like:
SELECT Datahora, column1, column2, column3
FROM myTable
WHERE id=20
ORDER BY Datahora;
W tej chwili moja funkcja zwraca to polecenie jako text
. Kopiuję-wklejam i wykonuję w pgAdmin lub przez psql. Chcę to zautomatyzować, uruchomić zapytanie automatycznie i zwrócić wynik. Jak mogę to zrobić?
3 answers
Dynamiczny SQL i RETURN
typ
(najlepsze zostawiłem na koniec, Czytaj dalej!)
Chcesz wykonać dynamiczny SQL. Zasadniczo jest to proste w plpgsql przy pomocy EXECUTE
. Nie potrzebujesz kursora - w rzeczywistości przez większość czasu lepiej jest ci bez wyraźnych kursorów.
Znajdź przykłady NA SO za pomocą wyszukiwania .
Problem, który napotkasz: chcesz zwrócić rekordy jeszcze nieokreślone Typ . Funkcja musi zadeklarować typ powrotu z RETURNS
klauzula (lub z parametrami OUT
lub INOUT
). W Twoim przypadku musiałbyś wrócić do anonimowych rekordów, ponieważ liczba, nazwy i typy zwracanych kolumn są różne. Like:
CREATE FUNCTION data_of(integer)
RETURNS SETOF record AS ...
Jednak nie jest to szczególnie przydatne. W ten sposób będziesz musiał podać listę definicji kolumn przy każdym wywołaniu funkcji. Like:
SELECT * FROM data_of(17)
AS foo (colum_name1 integer
, colum_name2 text
, colum_name3 real);
Ale jak byś nawet zrobić to, kiedy nie znasz kolumn wcześniej?
Możesz korzystać z mniej ustrukturyzowanych typów danych dokumentów, takich jak json
, jsonb
, hstore
lub xml
:
Ale na potrzeby tego pytania Załóżmy, że chcesz zwracać pojedyncze, poprawnie wpisane i nazwane kolumny w jak największym stopniu.
Proste rozwiązanie ze stałym zwrotem typ
Kolumna datahora
wydaje się być podana, zakładam typ danych timestamp
i że zawsze są jeszcze dwie kolumny o różnej nazwie i typie danych.
nazwy porzucimy na rzecz nazw ogólnych w typie return.
typy również porzucimy i oddamy wszystkie do text
ponieważ każdy typ danych może zostać oddany do text
.
CREATE OR REPLACE FUNCTION data_of(_id integer)
RETURNS TABLE (datahora timestamp, col2 text, col3 text) 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$ LANGUAGE plpgsql;
Jak to działa?
Można wprowadzić zmienne
_sensors
i_type
zamiast tego parametry.Uwaga na
RETURNS TABLE
klauzula.Zwróć uwagę na użycie
RETURN QUERY EXECUTE
. Jest to jeden z bardziej eleganckich sposobów zwracania wierszy z dynamicznego zapytania.Używam nazwy dla parametru function, tylko po to, aby
USING
klauzulaRETURN QUERY EXECUTE
była mniej myląca.$1
W Sql-string nie odnosi się do parametru function, ale do wartości przekazanej z klauzuląUSING
. (Oba zdają się być$1
w swoich odpowiednich zakres w tym prostym przykładzie.)Zwróć uwagę na przykładową wartość dla
_sensors
: każda kolumna jest oddana do typutext
.Ten rodzaj kodu jest bardzo podatny na SQL injection. Używam
quote_ident()
żeby się przed nim chronić. Połączenie kilku nazw kolumn w zmiennej_sensors
zapobiega użyciuquote_ident()
(i jest zazwyczaj złym pomysłem!). Upewnij się, że żadne złe rzeczy nie mogą być tam w inny sposób, na przykład poprzez indywidualne bieganie nazwy kolumn zamiastquote_ident()
. Na myśl przychodzi parametrVARIADIC
...
Prostsze z 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łyby być odpowiednio unikane i byłyby czystym sposobem.
Zmienna liczba kolumn dzielących ten sam typ
Po aktualizacji Twojego pytania wygląda na to, że Twój typ powrotu ma
- a zmienna liczba kolumn
- ale wszystkie kolumny tego samego typu
double precision
(aliasfloat8
)
Ponieważ musimy zdefiniować typ RETURN
funkcji uciekam się w tym przypadku do typu ARRAY
, który może pomieścić zmienną liczbę wartości. Dodatkowo zwracam tablicę z nazwami kolumn, więc możesz również przeanalizować nazwy z wyniku:
CREATE OR REPLACE FUNCTION data_of(_id integer)
RETURNS TABLE (datahora timestamp, names text[], values float8[] ) 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$ LANGUAGE plpgsql;
Różne typy kompletnych tabel
Jeśli naprawdę próbujesz wrócić wszystkie kolumny tabeli (na przykład jedna z tabel na połączonej stronie , następnie użyj tego prostego, bardzo wydajnego rozwiązania z typ polimorficzny:
CREATE OR REPLACE FUNCTION data_of(_tbl_type anyelement, _id int)
RETURNS SETOF anyelement 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$ LANGUAGE plpgsql;
Telefon:
SELECT * FROM data_of(NULL::pcdmet, 17);
Zastąp pcdmet
w wywołaniu dowolną inną nazwą tabeli.
anyelement
jest pseudo typem danych, typem polimorficznym, symbolem zastępczym dla dowolnego typu danych innego niż tablica. Wszystkie wystąpieniaanyelement
w funkcji oceń na ten sam typ podany w czasie wykonywania. Dostarczając do funkcji wartość określonego typu jako argument, domyślnie definiujemy typ zwracany.PostgreSQL automatycznie definiuje typ wiersza (złożony typ danych) dla każdej utworzonej tabeli, więc dla każdej tabeli jest dobrze zdefiniowany Typ. Obejmuje to tymczasowe tabele, które są wygodne do użytku ad hoc.
Każdy typ może być
NULL
. Przekazujemy więcNULL
wartość, rzucamy do tabeli Typ.Teraz funkcja zwraca dobrze zdefiniowany typ wiersza i możemy użyć
SELECT * FROM data_of(...)
do rozłożenia wiersza i uzyskania pojedynczych kolumn.pg_typeof(_tbl_type)
zwraca nazwę tabeli jako typ identyfikatora obiekturegtype
. Po automatycznej konwersji natext
, identyfikatory są automatycznie podwójnie cytowane i w razie potrzeby kwalifikowane do schematu. Dlatego SQL injection nie jest możliwe. To może nawet radzić sobie ze schematem-kwalifikowane table-names wherequote_ident()
would fail .
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-26 13:18:07
Prawdopodobnie będziesz chciał zwrócić kursor . Spróbuj czegoś takiego (nie próbowałem):
CREATE OR REPLACE FUNCTION data_of(integer)
RETURNS refcursor AS
$BODY$
DECLARE
--Declaring variables
ref refcursor;
BEGIN
-- make sure `sensors`, `type`, $1 variable has valid value
OPEN ref FOR 'SELECT Datahora,' || sensors ||
' FROM ' || type ||
' WHERE nomepcd=' || $1 ||' ORDER BY Datahora;';
RETURN ref;
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
ALTER FUNCTION data_of(integer) OWNER TO postgres;
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-31 12:52:40
Przykro mi to mówić, ale twoje pytanie jest bardzo niejasne. Jednak poniżej znajdziesz samodzielny przykład tworzenia i używania funkcji, która zwraca zmienną kursora. Mam nadzieję, że to pomoże !
begin;
create table test (id serial, data1 text, data2 text);
insert into test(data1, data2) values('one', 'un');
insert into test(data1, data2) values('two', 'deux');
insert into test(data1, data2) values('three', 'trois');
create function generate_query(query_name refcursor, columns text[])
returns refcursor
as $$
begin
open query_name for execute
'select id, ' || array_to_string(columns, ',') || ' from test order by id';
return query_name;
end;
$$ language plpgsql;
select generate_query('english', array['data1']);
fetch all in english;
select generate_query('french', array['data2']);
fetch all in french;
move absolute 0 from french; -- do it again !
fetch all in french;
select generate_query('all_langs', array['data1','data2']);
fetch all in all_langs;
-- this will raise in runtime as there is no data3 column in the test table
select generate_query('broken', array['data3']);
rollback;
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-31 19:01:35