sql >> Database teknologi >  >> RDS >> PostgreSQL

Refaktorer en PL/pgSQL-funktion for at returnere output fra forskellige SELECT-forespørgsler

Dynamisk SQL og RETURN type


Du ønsker at udføre dynamisk SQL . I princippet er det enkelt i plpgsql ved hjælp af EXECUTE . Du behøver ikke behøve en markør. Faktisk er du det meste af tiden bedre stillet uden eksplicitte markører.

Det problem, du støder på:du vil returnere poster af endnu udefineret type . En funktion skal erklære sin returtype i RETURNS klausul (eller med OUT eller INOUT parametre). I dit tilfælde ville du skulle falde tilbage til anonyme optegnelser, fordi nummer , navne og typer af returnerede kolonner varierer. Ligesom:

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

Dette er dog ikke særlig nyttigt. Du skal angive en kolonnedefinitionsliste med hvert opkald. Ligesom:

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

Men hvordan ville du overhovedet gøre dette, når du ikke kender kolonnerne på forhånd?
Du kunne bruge mindre strukturerede dokumentdatatyper som json , jsonb , hstore eller xml . Se:

  • Hvordan gemmer man en datatabel i databasen?

Men med henblik på dette spørgsmål, lad os antage, at du ønsker at returnere individuelle, korrekt indtastede og navngivne kolonner så meget som muligt.

Simpel løsning med fast returtype

Kolonnen datahora synes at være en given, jeg antager datatypen timestamp og at der altid er to kolonner mere med varierende navn og datatype.

Navne vi opgiver til fordel for generiske navne i returtypen.
Typer vi opgiver også og sender alt til text siden hver datatypen kan castes til 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$;

Variablerne _sensors og _type kunne være inputparametre i stedet for.

Bemærk RETURNS TABLE klausul.

Bemærk brugen af ​​RETURN QUERY EXECUTE . Det er en af ​​de mere elegante måder at returnere rækker fra en dynamisk forespørgsel på.

Jeg bruger et navn til funktionsparameteren, bare for at lave USING klausul af RETURN QUERY EXECUTE mindre forvirrende. $1 i SQL-strengen henviser ikke til funktionsparameteren, men til den værdi, der sendes med USING klausul. (Begge er tilfældigvis $1 i deres respektive omfang i dette simple eksempel.)

Bemærk eksempelværdien for _sensors :hver kolonne castes til at skrive text .

Denne type kode er meget sårbar over for SQL-injektion . Jeg bruger quote_ident() at beskytte mod det. Slår et par kolonnenavne sammen i variablen _sensors forhindrer brugen af ​​quote_ident() (og er typisk en dårlig idé!). Sørg for, at der ikke kan være dårlige ting derinde på en anden måde, for eksempel ved individuelt at køre kolonnenavnene gennem quote_ident() i stedet. En VARIADIC parameter kommer til at tænke på ...

Enklere siden PostgreSQL 9.1

Med version 9.1 eller nyere kan du bruge format() for yderligere at forenkle:

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;

Igen kunne individuelle kolonnenavne undslippes korrekt og ville være den rene måde.

Variabelt antal kolonner, der deler samme type

Efter dit spørgsmål er opdateret ser det ud til, at din returtype har

  • et variabelt tal af kolonner
  • men alle kolonner af samme type double precision (alias float8 )

Brug en ARRAY skriv i dette tilfælde for at indlejre et variabelt antal værdier. Derudover returnerer jeg et array med kolonnenavne:

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

Forskellige komplette tabeltyper

For faktisk at returnere alle kolonner i en tabel , er der en enkel, kraftfuld løsning, der bruger en polymorf type :

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 (vigtigt!):

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

Erstat pcdmet i opkaldet med et hvilket som helst andet tabelnavn.

Hvordan fungerer dette?

anyelement er en pseudodatatype, en polymorf type, en pladsholder for enhver ikke-array-datatype. Alle forekomster af anyelement i funktionen evaluere til den samme type, der blev angivet ved kørsel. Ved at angive en værdi af en defineret type som argument til funktionen, definerer vi implicit returtypen.

PostgreSQL definerer automatisk en rækketype (en sammensat datatype) for hver tabel oprettet, så der er en veldefineret type for hver tabel. Dette inkluderer midlertidige tabeller, hvilket er praktisk til ad hoc-brug.

Enhver type kan være NULL . Aflever en NULL værdi, cast til tabeltypen:NULL::pcdmet .

Nu returnerer funktionen en veldefineret rækketype, og vi kan bruge SELECT * FROM data_of() for at dekomponere rækken og få individuelle kolonner.

pg_typeof(_tbl_type) returnerer navnet på tabellen som objektidentifikator type regtype . Når den automatisk konverteres til text , er identifikatorer automatisk dobbelte anførselstegn og skema-kvalificerede om nødvendigt forsvar mod SQL-injektion automatisk. Dette kan endda håndtere skema-kvalificerede tabelnavne, hvor quote_ident() ville mislykkes. Se:

  • Tabelnavn som en PostgreSQL-funktionsparameter


  1. Oracle SQL:Forstår du adfærden af ​​SYS_GUID(), når den er til stede i en inline-visning?

  2. Flere planer for en identisk forespørgsel

  3. Rank-funktion i MySQL med Order By-klausul

  4. Hvorfor bliver SQL Server-skalære funktioner langsommere?