sql >> Databáze >  >> RDS >> PostgreSQL

Refaktorujte funkci PL/pgSQL, abyste vrátili výstup různých SELECT dotazů

Dynamické SQL a RETURN typ


Chcete spustit dynamické SQL . V plpgsql je to v principu jednoduché s pomocí EXECUTE . nepotřebujete kurzor. Ve skutečnosti je vám většinou lépe bez explicitních kurzorů.

Problém, na který narazíte:chcete vrátit záznamy dosud nedefinovaného typu . Funkce musí deklarovat svůj návratový typ v RETURNS klauzule (nebo s OUT nebo INOUT parametry). Ve vašem případě byste se museli vrátit k anonymním záznamům, protože číslo , jména a typy vrácených sloupců se liší. Jako:

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

To však není nijak zvlášť užitečné. S každým voláním musíte poskytnout seznam definic sloupců. Jako:

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

Ale jak byste to vůbec udělali, když předem neznáte sloupce?
Mohli byste použít méně strukturované datové typy dokumentů, jako je json , jsonb , hstore nebo xml . Viz:

  • Jak uložit tabulku dat do databáze?

Ale pro účely této otázky předpokládejme, že chcete co nejvíce vrátit jednotlivé, správně zadané a pojmenované sloupce.

Jednoduché řešení s pevným typem návratu

Sloupec datahora Zdá se, že je to dané, budu předpokládat datový typ timestamp a že vždy existují dva další sloupce s různým názvem a typem dat.

Jména opustíme ve prospěch obecných jmen v návratovém typu.
Typy také opustíme a přeneseme vše na text od každého datový typ lze přetypovat 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$;

Proměnné _sensors a _type místo toho mohou být vstupní parametry.

Všimněte si RETURNS TABLE doložka.

Všimněte si použití RETURN QUERY EXECUTE . To je jeden z elegantnějších způsobů, jak vrátit řádky z dynamického dotazu.

Používám název pro parametr funkce, abych vytvořil USING klauzule RETURN QUERY EXECUTE méně matoucí. $1 v řetězci SQL neodkazuje na parametr funkce, ale na hodnotu předávanou pomocí USING doložka. (Oba jsou náhodou $1 v jejich příslušném rozsahu v tomto jednoduchém příkladu.)

Všimněte si vzorové hodnoty pro _sensors :každý sloupec je přetypován do typu text .

Tento druh kódu je velmi zranitelný vůči vložení SQL . Používám quote_ident() chránit před tím. Shrnutí několika názvů sloupců v proměnné _sensors zabraňuje použití quote_ident() (a obvykle je to špatný nápad!). Zajistěte, aby se tam žádné špatné věci nemohly dostat jiným způsobem, například individuálním spuštěním názvů sloupců pomocí quote_ident() namísto. A VARIADIC parametr mě napadá ...

Jednodušší od PostgreSQL 9.1

S verzí 9.1 nebo novější můžete použít format() pro další zjednodušení:

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;

Opět platí, že názvy jednotlivých sloupců by mohly být správně escapovány a byl by to čistý způsob.

Proměnný počet sloupců sdílejících stejný typ

Po aktualizaci vaší otázky to vypadá, že váš typ návratu má

  • proměnná číslo sloupců
  • ale všechny sloupce stejného typu double precision (jinak float8 )

Použijte ARRAY zadejte v tomto případě vnořte proměnný počet hodnot. Navíc vracím pole s názvy sloupců:

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ůzné kompletní typy tabulek

Chcete-li skutečně vrátit všechny sloupce tabulky , existuje jednoduché a výkonné řešení využívající polymorfní typ :

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

Zavolejte (důležité!):

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

Nahraďte pcdmet ve volání s jakýmkoli jiným názvem tabulky.

Jak to funguje?

anyelement je pseudo datový typ, polymorfní typ, zástupný symbol pro jakýkoli datový typ bez pole. Všechny výskyty anyelement ve funkci vyhodnotit na stejný typ poskytnutý za běhu. Dodáním hodnoty definovaného typu jako argumentu funkci implicitně definujeme návratový typ.

PostgreSQL automaticky definuje typ řádku (složený datový typ) pro každou vytvořenou tabulku, takže pro každou tabulku existuje dobře definovaný typ. To zahrnuje dočasné tabulky, což je vhodné pro použití ad-hoc.

Jakýkoli typ může být NULL . Odevzdejte NULL hodnotu, přetypovat do tabulky typu:NULL::pcdmet .

Nyní funkce vrací dobře definovaný typ řádku a můžeme použít SELECT * FROM data_of() rozložit řádek a získat jednotlivé sloupce.

pg_typeof(_tbl_type) vrátí název tabulky jako typ identifikátoru objektu regtype . Po automatickém převedení na text , jsou identifikátory automaticky uváděny do dvojitých uvozovek a jsou kvalifikovány podle schématu v případě potřeby automatická obrana proti SQL injection. To se dokonce může vypořádat s názvy tabulek kvalifikovanými pro schéma, kde quote_ident() by selhal. Viz:

  • Název tabulky jako parametr funkce PostgreSQL


  1. Jak vypočítat průměrný prodej za týden v MySQL

  2. Chyba MySQL 1215:Nelze přidat omezení cizího klíče

  3. Jak mohu použít ROW_NUMBER()?

  4. Převeďte časové razítko Unixu na člověkem čitelné datum pomocí MySQL