Hurtigere med hstore
Siden Postgres 9.0 , med det ekstra modul hstore
installeret i din database er der en meget enkel og hurtig løsning med #=
operatør, der ...
erstatte[s] felter i record
med matchende værdier fra hstore
.
Sådan installeres modulet:
CREATE EXTENSION hstore;
Eksempler:
SELECT my_record #= '"field"=>"value"'::hstore; -- with string literal
SELECT my_record #= hstore(field, value); -- with values
Værdier skal castes til text
og tilbage, naturligvis.
Eksempel plpgsql-funktioner med flere detaljer:
- Endeløs sløjfe i triggerfunktion
- Tildel til NY ved at indtaste en Postgres-trigger
Fungerer nu med json
/ jsonb
også!
Der er lignende løsninger med json
(s. 9.3+) eller jsonb
(side 9.4+)
SELECT json_populate_record (my_record, json_build_object('key', 'new-value');
Funktionaliteten var udokumenteret, men den er officiel siden Postgres 13. Manualen:
Men hvis base ikke er NULL, vil de værdier, den indeholder, blive brugt til umatchede kolonner.
Så du kan tage en hvilken som helst eksisterende række og udfylde vilkårlige felter (overskrive, hvad der er i den).
Store fordele ved json
vs hstore
:
- fungerer med lager Postgres, så du behøver ikke et ekstra modul.
- fungerer også for indlejrede array- og sammensatte typer.
Mindre ulempe:lidt langsommere.
Se @Geirs tilføjede svar for detaljer.
Uden hstore
og json
Hvis du er på en ældre version eller ikke kan installere det ekstra modul hstore
eller ikke kan antage, at det er installeret, her er en forbedret version af det, jeg tidligere har postet. Stadig langsommere end hstore
operatør, dog:
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$;
Ring til:
CREATE TEMP TABLE t( a int, b text); -- Composite type for testing
SELECT f_setfield(NULL::t, 'a', '1');
Bemærkninger
-
En eksplicit cast af værdien
_val
til måldatatypen er ikke nødvendig, en streng bogstavelig i den dynamiske forespørgsel vil blive tvunget automatisk, hvilket undgår underforespørgslen påpg_type
. Men jeg tog det et skridt videre: -
Erstat
quote_literal(_val)
med direkte værdiindsættelse viaUSING
klausul. Gemmer et funktionsopkald og to casts, og er alligevel sikrere.text
tvinges automatisk til måltypen i moderne PostgreSQL. (Afprøvede ikke med versioner før 9.1.) -
array_to_string(ARRAY())
er hurtigere endstring_agg()
. -
Ingen variable nødvendige, ingen
DECLARE
. Færre opgaver. -
Ingen underforespørgsel i den dynamiske SQL.
($1).field
er hurtigere. -
pg_typeof(_comp_val)::text::regclass
gør det samme som(SELECT typrelid FROM pg_catalog.pg_type WHERE oid = pg_typeof($1)::oid)
for gyldige sammensatte typer, bare hurtigere.
Denne sidste ændring er bygget på den antagelse, atpg_type.typname
er altid identisk med den tilknyttedepg_class.relname
for registrerede komposittyper, og dobbeltstøbningen kan erstatte underforespørgslen. Jeg kørte denne test i en stor database for at bekræfte, og den kom op som forventet tom:
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
- Brugen af en
INOUT
parameter undgår behovet for en eksplicitRETURN
. Dette er blot en notationsgenvej. Pavel vil ikke lide det, han foretrækker en eksplicitRETURN
erklæring ...
Alt sammen er dette dobbelt så hurtigt som den tidligere version.
Originalt (forældet) svar:
Resultatet er en version, der er ~ 2,25 gange hurtigere . Men jeg kunne nok ikke have gjort det uden at bygge videre på Pavels anden version.
Derudover undgår denne version det meste af castingen til tekst og tilbage ved at gøre alt inden for en enkelt forespørgsel, så den burde være meget mindre fejltilbøjelig.
Testet med PostgreSQL 9.0 og 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$;