sql >> Databasteknik >  >> RDS >> PostgreSQL

Refaktorera en PL/pgSQL-funktion för att returnera utdata från olika SELECT-frågor

Dynamisk SQL och RETURN typ


Du vill köra dynamisk SQL . I princip är det enkelt i plpgsql med hjälp av EXECUTE . Du behöver inte en markör. Faktum är att du oftast har det bättre utan explicita markörer.

Problemet du stöter på:du vill returnera poster av ännu odefinierad typ . En funktion måste deklarera sin returtyp i RETURNS klausul (eller med OUT eller INOUT parametrar). I ditt fall skulle du behöva falla tillbaka till anonyma poster, eftersom nummer , namn och typer av returnerade kolumner varierar. Gilla:

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

Detta är dock inte särskilt användbart. Du måste tillhandahålla en kolumndefinitionslista med varje samtal. Gilla:

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

Men hur skulle du ens göra detta när du inte känner till kolumnerna i förväg?
Du kan använda mindre strukturerade dokumentdatatyper som json , jsonb , hstore eller xml . Se:

  • Hur lagrar man en datatabell i databasen?

Men för denna frågas syfte, låt oss anta att du vill returnera individuella, korrekt skrivna och namngivna kolumner så mycket som möjligt.

Enkel lösning med fast returtyp

Kolumnen datahora verkar vara en given, jag antar datatypen timestamp och att det alltid finns ytterligare två kolumner med olika namn och datatyp.

Namn vi överger till förmån för generiska namn i returtypen.
Typer vi överger också och castar allt till text sedan varje datatyp kan castas till 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$;

Variablerna _sensors och _type kan vara indataparametrar istället.

Notera RETURNS TABLE klausul.

Notera användningen av RETURN QUERY EXECUTE . Det är ett av de mer eleganta sätten att returnera rader från en dynamisk fråga.

Jag använder ett namn för funktionsparametern, bara för att göra USING sats av RETURN QUERY EXECUTE mindre förvirrande. $1 i SQL-strängen hänvisar inte till funktionsparametern utan till värdet som skickas med USING klausul. (Båda råkar vara $1 i deras respektive omfattning i detta enkla exempel.)

Notera exempelvärdet för _sensors :varje kolumn casts till typ text .

Den här typen av kod är mycket sårbar för SQL-injektion . Jag använder quote_ident() för att skydda sig mot det. Slår ihop ett par kolumnnamn i variabeln _sensors förhindrar användningen av quote_ident() (och är vanligtvis en dålig idé!). Se till att inga dåliga saker kan finnas där på något annat sätt, till exempel genom att individuellt köra kolumnnamnen genom quote_ident() istället. En VARIADIC parameter kommer att tänka på ...

Enklare sedan PostgreSQL 9.1

Med version 9.1 eller senare kan du använda format() för att ytterligare förenkla:

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;

Återigen, individuella kolumnnamn kunde escapes korrekt och skulle vara det rena sättet.

Variabelt antal kolumner som delar samma typ

Efter att din fråga har uppdaterats ser det ut som att din returtyp har

  • ett variabelt nummer av kolumner
  • men alla kolumner av samma typ double precision (alias float8 )

Använd en ARRAY skriv i det här fallet för att kapsla ett variabelt antal värden. Dessutom returnerar jag en array med kolumnnamn:

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

Olika kompletta tabelltyper

För att faktiskt returnera alla kolumner i en tabell , det finns en enkel, kraftfull lösning som använder en polymorf 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$;

Ring (viktigt!):

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

Ersätt pcdmet i samtalet med något annat tabellnamn.

Hur fungerar det här?

anyelement är en pseudodatatyp, en polymorf typ, en platshållare för alla icke-arraydatatyper. Alla förekomster av anyelement i funktionen utvärdera till samma typ som angavs vid körning. Genom att ange ett värde av en definierad typ som argument till funktionen, definierar vi implicit returtypen.

PostgreSQL definierar automatiskt en radtyp (en sammansatt datatyp) för varje skapad tabell, så det finns en väldefinierad typ för varje tabell. Detta inkluderar tillfälliga tabeller, vilket är bekvämt för ad hoc-användning.

Vilken typ som helst kan vara NULL . Lämna in en NULL värde, cast till tabelltypen:NULL::pcdmet .

Nu returnerar funktionen en väldefinierad radtyp och vi kan använda SELECT * FROM data_of() för att dekomponera raden och få enskilda kolumner.

pg_typeof(_tbl_type) returnerar namnet på tabellen som objektidentifierare typ regtype . När den konverteras automatiskt till text , identifierare är automatiskt dubbla citattecken och schemakvalificerade vid behov, försvara sig mot SQL-injektion automatiskt. Detta kan till och med hantera schemakvalificerade tabellnamn där quote_ident() skulle misslyckas. Se:

  • Tabellnamn som en PostgreSQL-funktionsparameter


  1. MariaDB DAY() Förklarad

  2. Prestanda för COUNT SQL-funktion

  3. DBA - Hur man dödar alla databasprocesser på SQL Server

  4. Har PL/SQL-utgångar i realtid