Sneller met hstore
Sinds Postgres 9.0 , met de extra module hstore
geïnstalleerd in uw database is er een zeer eenvoudige en snelle oplossing met de #=
operator die ...
vervang [s] velden in record
met overeenkomende waarden van hstore
.
Om de module te installeren:
CREATE EXTENSION hstore;
Voorbeelden:
SELECT my_record #= '"field"=>"value"'::hstore; -- with string literal
SELECT my_record #= hstore(field, value); -- with values
Waarden moeten worden gecast naar text
en terug natuurlijk.
Voorbeelden van plpgsql-functies met meer details:
- Eindeloze lus in triggerfunctie
- Toewijzen aan NIEUW met een toets in een Postgres-trigger
Werkt nu met json
/ jsonb
, ook!
Er zijn vergelijkbare oplossingen met json
(pag 9.3+) of jsonb
(pag 9.4+)
SELECT json_populate_record (my_record, json_build_object('key', 'new-value');
De functionaliteit was niet gedocumenteerd, maar het is officieel sinds Postgres 13. De handleiding:
Als de basis echter niet NULL is, worden de waarden die erin staan gebruikt voor niet-overeenkomende kolommen.
U kunt dus elke bestaande rij nemen en willekeurige velden invullen (overschrijven wat erin staat).
Grote voordelen van json
vs hstore
:
- werkt met standaard Postgres, dus je hebt geen extra module nodig.
- werkt ook voor geneste array- en samengestelde typen.
Klein nadeel:iets langzamer.
Zie het toegevoegde antwoord van @Geir voor details.
Zonder hstore
en json
Als u een oudere versie gebruikt of de extra module hstore
. niet kunt installeren of kan niet aannemen dat het is geïnstalleerd, hier is een verbeterde versie van wat ik eerder heb gepost. Nog steeds langzamer dan de hstore
operator echter:
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$;
Bel:
CREATE TEMP TABLE t( a int, b text); -- Composite type for testing
SELECT f_setfield(NULL::t, 'a', '1');
Opmerkingen
-
Een expliciete cast van de waarde
_val
naar het doelgegevenstype is niet nodig, een letterlijke tekenreeks in de dynamische query zou automatisch worden afgedwongen, waardoor de subquery oppg_type
wordt vermeden . Maar ik ging nog een stap verder: -
Vervang
quote_literal(_val)
met directe waarde-invoer via deUSING
clausule. Bespaart één functieaanroep en twee casts, en is sowieso veiliger.text
wordt automatisch gedwongen naar het doeltype in moderne PostgreSQL. (Niet getest met versies vóór 9.1.) -
array_to_string(ARRAY())
is sneller danstring_agg()
. -
Geen variabelen nodig, geen
DECLARE
. Minder opdrachten. -
Geen subquery in de dynamische SQL.
($1).field
is sneller. -
pg_typeof(_comp_val)::text::regclass
doet hetzelfde als(SELECT typrelid FROM pg_catalog.pg_type WHERE oid = pg_typeof($1)::oid)
voor geldige samengestelde typen, alleen sneller.
Deze laatste wijziging is gebaseerd op de veronderstelling datpg_type.typname
is altijd identiek aan de bijbehorendepg_class.relname
voor geregistreerde samengestelde typen, en de dubbele cast kan de subquery vervangen. Ik heb deze test in een grote database uitgevoerd om te verifiëren, en hij was leeg zoals verwacht:
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
- Het gebruik van een
INOUT
parameter vermijdt de noodzaak van een explicieteRETURN
. Dit is slechts een notatie sneltoets. Pavel zal het niet leuk vinden, hij geeft de voorkeur aan een explicieteRETURN
verklaring ...
Alles bij elkaar is dit twee keer zo snel als de vorige versie.
Oorspronkelijk (verouderd) antwoord:
Het resultaat is een versie die ~ 2,25 keer sneller is . Maar ik had het waarschijnlijk niet kunnen doen zonder voort te bouwen op Pavels tweede versie.
Bovendien vermijdt deze versie het grootste deel van de casting naar tekst en terug door alles binnen één enkele query te doen, dus het zou veel minder foutgevoelig moeten zijn.
Getest met PostgreSQL 9.0 en 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$;