По-бързо с 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$;