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

Как да зададете стойност на полето за съставна променлива с помощта на динамичен SQL

По-бързо с hstore

От Postgres 9.0 , с допълнителния модул hstore инсталирано във вашата база данни има много просто и бързо решение с #= оператор, който ...

замести [s] полета в record със съвпадащи стойности от hstore .

За да инсталирате модула:

CREATE EXTENSION hstore;

Примери:

SELECT my_record #= '"field"=>"value"'::hstore;  -- with string literal
SELECT my_record #= hstore(field, value);        -- with values

Стойностите трябва да се прехвърлят към text и обратно, очевидно.

Примерни plpgsql функции с повече подробности:

  • Безкраен цикъл в тригерна функция
  • Присвояване на НОВО чрез ключ в задействане на Postgres

Вече работи с json / jsonb също!

Има подобни решения с json (стр. 9.3+) или jsonb (стр. 9.4+)

SELECT json_populate_record (my_record, json_build_object('key', 'new-value');

Функционалността беше недокументирана, но е официална след Postgres 13. Ръководството:

Въпреки това, ако базата не е NULL, тогава стойностите, които съдържа, ще се използват за несъвпадащи колони.

Така че можете да вземете всеки съществуващ ред и да попълните произволни полета (презаписвайки това, което има в него).

Основни предимства на json срещу hstore :

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

Малък недостатък:малко по-бавен.

Вижте добавения отговор на @Geir за подробности.

Без hstore и json

Ако сте на по-стара версия или не можете да инсталирате допълнителния модул hstore или не може да приеме, че е инсталиран, ето подобрена версия на това, което публикувах по-рано. Все още по-бавно от hstore оператор обаче:

CREATE OR REPLACE FUNCTION f_setfield(INOUT _comp_val anyelement
                                          , _field text, _val text)
  RETURNS anyelement
  LANGUAGE plpgsql STABLE AS
$func$
BEGIN

EXECUTE 'SELECT ' || array_to_string(ARRAY(
      SELECT CASE WHEN attname = _field
                THEN '$2'
                ELSE '($1).' || quote_ident(attname)
             END AS fld
      FROM   pg_catalog.pg_attribute
      WHERE  attrelid = pg_typeof(_comp_val)::text::regclass
      AND    attnum > 0
      AND    attisdropped = FALSE
      ORDER  BY attnum
      ), ',')
USING  _comp_val, _val
INTO   _comp_val;

END
$func$;

Обадете се:

CREATE TEMP TABLE t( a int, b text);  -- Composite type for testing
SELECT f_setfield(NULL::t, 'a', '1');

Бележки

  • Изрично прехвърляне на стойността _val към целевия тип данни не е необходимо, литерал на низ в динамичната заявка ще бъде принуден автоматично, премахвайки подзаявката на pg_type . Но направих още една крачка напред:

  • Заменете quote_literal(_val) с директно вмъкване на стойност чрез USING клауза. Спестява едно извикване на функция и две прехвърляния и така или иначе е по-безопасно. text се принуждава към целевия тип автоматично в съвременния PostgreSQL. (Не е тестван с версии преди 9.1.)

  • array_to_string(ARRAY()) е по-бърз от string_agg() .

  • Не са необходими променливи, няма DECLARE . По-малко задачи.

  • Няма подзаявка в динамичния SQL. ($1).field е по-бързо.

  • pg_typeof(_comp_val)::text::regclass
    прави същото като
    (SELECT typrelid FROM pg_catalog.pg_type WHERE oid = pg_typeof($1)::oid)
    за валидни съставни типове, просто по-бързо.
    Тази последна модификация е изградена въз основа на предположението, че pg_type.typname винаги е идентичен със свързания pg_class.relname за регистрирани съставни типове, а двойното прехвърляне може да замени подзаявката. Проведох този тест в голяма база данни, за да потвърдя, и той се оказа празен, както се очакваше:

    SELECT *
    FROM   pg_catalog.pg_type t
    JOIN   pg_namespace  n ON n.oid = t.typnamespace
    WHERE  t.typrelid > 0  -- exclude non-composite types
    AND    t.typrelid IS DISTINCT FROM
          (quote_ident(n.nspname ) || '.' || quote_ident(typname))::regclass
  • Използването на INOUT параметър премахва необходимостта от изрично RETURN . Това е само нотационен пряк път. Павел няма да го хареса, той предпочита изрично RETURN изявление...

Всичко взето заедно, това е два пъти по-бързо като предишната версия.

Оригинален (неактуален) отговор:

Резултатът е версия, която е ~ 2,25 пъти по-бърза . Но вероятно не бих могъл да го направя без да надграждам втората версия на Павел.

Освен това тази версияизбягва по-голямата част от кастинга към текст и обратно, като правите всичко в рамките на една заявка, така че би трябвало да е много по-малко податливо на грешки.
Тестван с PostgreSQL 9.0 и 9.1 .

CREATE FUNCTION f_setfield(_comp_val anyelement, _field text, _val text)
  RETURNS anyelement
  LANGUAGE plpgsql STABLE AS
$func$
DECLARE
   _list text;
BEGIN
_list := (
   SELECT string_agg(x.fld, ',')
   FROM  (
      SELECT CASE WHEN a.attname = $2
              THEN quote_literal($3) || '::'|| (SELECT quote_ident(typname)
                                                FROM   pg_catalog.pg_type
                                                WHERE  oid = a.atttypid)
              ELSE quote_ident(a.attname)
             END AS fld
      FROM   pg_catalog.pg_attribute a 
      WHERE  a.attrelid = (SELECT typrelid
                           FROM   pg_catalog.pg_type
                           WHERE  oid = pg_typeof($1)::oid)
      AND    a.attnum > 0
      AND    a.attisdropped = false
      ORDER  BY a.attnum
      ) x
   );

EXECUTE 'SELECT ' || _list || ' FROM  (SELECT $1.*) x'
USING  $1
INTO   $1;

RETURN $1;
END
$func$;


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Бавно подреждане на заявките по колона в обединена таблица

  2. Failover &Failback за PostgreSQL на Microsoft Azure

  3. Изчисляване на кумулативна сума в PostgreSQL

  4. Развързване на масива с едно ниво

  5. Актуализации на инструментите за тестване на PostgreSQL с архив за сравнителни показатели