PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

Рефакторирайте функция PL/pgSQL, за да върнете изхода от различни SELECT заявки

Динамичен SQL и RETURN тип


Искате да изпълните динамичен SQL . По принцип това е просто в plpgsql с помощта на EXECUTE . Нямате нужни курсор. Всъщност през повечето време е по-добре без изрични курсори.

Проблемът, с който се сблъсквате:искате да връщате записи от все още недефиниран тип . Функцията трябва да декларира своя тип връщане в RETURNS клауза (или с OUT или INOUT параметри). Във вашия случай ще трябва да се върнете към анонимните записи, защото номер , имена и типове на върнатите колони варират. Като:

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

Това обаче не е особено полезно. Трябва да предоставите списък с дефиниции на колони с всяко повикване. Като:

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

Но как бихте направили това, когато не знаете колоните предварително?
Бихте могли да използвате по-малко структурирани типове данни за документи като json , jsonb , hstore или xml . Вижте:

  • Как да съхранявам таблица с данни в база данни?

Но за целите на този въпрос нека приемем, че искате да върнете отделни, правилно въведени и именувани колони, доколкото е възможно.

Просто решение с фиксиран тип връщане

Колоната datahora изглежда като даденост, ще приема тип данни timestamp и че винаги има още две колони с различно име и тип данни.

Имена ще се откажем в полза на общите имена в връщания тип.
Типове ние също ще изоставим и ще прехвърлим всичко към text от всеки типът данни може да бъде прехвърлен към 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$;

Променливите _sensors и _type вместо това могат да бъдат входни параметри.

Обърнете внимание на RETURNS TABLE клауза.

Обърнете внимание на използването на RETURN QUERY EXECUTE . Това е един от по-елегантните начини за връщане на редове от динамична заявка.

Използвам име за параметъра на функцията, само за да направя USING клауза на RETURN QUERY EXECUTE по-малко объркващо. $1 в SQL низ не се отнася до параметъра на функцията, а до стойността, предадена с USING клауза. (И двете са $1 в съответния им обхват в този прост пример.)

Обърнете внимание на примерната стойност за _sensors :всяка колона се прехвърля към тип text .

Този вид код е много уязвим към SQL инжекция . Използвам quote_ident() за да се предпази от него. Обединяване на няколко имена на колони в променливата _sensors предотвратява използването на quote_ident() (и обикновено е лоша идея!). Уверете се, че там не може да има лоши неща по друг начин, например като стартирате поотделно имената на колоните чрез quote_ident() вместо. A VARIADIC параметър идва на ум ...

По-просто от PostgreSQL 9.1

С версия 9.1 или по-нова можете да използвате format() за допълнително опростяване:

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;

Отново, имената на отделните колони могат да бъдат екранирани правилно и това би било чистият начин.

Променлив брой колони, споделящи един и същи тип

След актуализиране на въпроса ви изглежда, че типът ви връщане е

  • променлива число от колони
  • но всички колони от един и същи тип double precision (псевдоним float8 )

Използвайте ARRAY въведете в този случай, за да вложите променлив брой стойности. Освен това връщам масив с имена на колони:

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

Различни типове пълни таблици

За действително връщане на всички колони на таблица , има просто, мощно решение, използващо полиморфен тип :

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

Обадете се (важно!):

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

Заменете pcdmet в повикването с всяко друго име на таблица.

Как работи това?

anyelement е псевдо тип данни, полиморфен тип, заместител за всеки тип данни без масив. Всички поява на anyelement във функцията се оценява до същия тип, предоставен по време на изпълнение. Чрез предоставяне на стойност от дефиниран тип като аргумент на функцията, ние имплицитно дефинираме връщания тип.

PostgreSQL автоматично дефинира тип ред (композитен тип данни) за всяка създадена таблица, така че има добре дефиниран тип за всяка таблица. Това включва временни таблици, което е удобно за ad hoc използване.

Всеки тип може да бъде NULL . Подайте NULL стойност, прехвърлена към типа на таблицата:NULL::pcdmet .

Сега функцията връща добре дефиниран тип ред и можем да използваме SELECT * FROM data_of() за разлагане на реда и получаване на отделни колони.

pg_typeof(_tbl_type) връща името на таблицата като тип идентификатор на обект regtype . При автоматично преобразуване в text , идентификаторите са автоматично двойни кавички и квалифицирани по схема ако е необходимо, автоматично се защитава срещу SQL инжекция. Това може дори да се справи с квалифицирани по схема имена на таблици, където quote_ident() би се провалил. Вижте:

  • Име на таблица като параметър на функцията на PostgreSQL


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Работи се към Postgres-XL 9.5

  2. Избиране на множество max() стойности с помощта на един SQL израз

  3. Добавете месеци към дата в PostgreSQL

  4. PostgreSQL потребителска група NL

  5. Как justify_interval() работи в PostgreSQL