Rychlejší s hstore
Od Postgres 9.0 s přídavným modulem hstore
nainstalované ve vaší databázi existuje velmi jednoduché a rychlé řešení s #=
operátor, který ...
nahradit [s] pole v
record
s odpovídajícími hodnotami zhstore
.
Instalace modulu:
CREATE EXTENSION hstore;
Příklady:
SELECT my_record #= '"field"=>"value"'::hstore; -- with string literal
SELECT my_record #= hstore(field, value); -- with values
Hodnoty musí být přetypovány do text
a zpět, samozřejmě.
Příklad funkcí plpgsql s dalšími podrobnostmi:
- Nekonečná smyčka ve funkci spouštění
- Přiřadit k NEW pomocí klíče ve spouštěči Postgres
Nyní funguje s json
/ jsonb
také!
Podobná řešení existují s json
(str. 9.3+) nebo jsonb
(str. 9.4+)
SELECT json_populate_record (my_record, json_build_object('key', 'new-value');
Funkce byla nezdokumentovaná, ale je oficiální od Postgres 13. Manuál:
Pokud však základ není NULL, pak hodnoty, které obsahuje, budou použity pro neodpovídající sloupce.
Můžete tedy vzít jakýkoli existující řádek a vyplnit libovolná pole (přepsat to, co v něm je).
Hlavní výhody json
vs hstore
:
- funguje se skladem Postgres, takže nepotřebujete další modul.
- funguje také pro vnořené pole a složené typy.
Menší nevýhoda:trochu pomalejší.
Podrobnosti naleznete v přidané odpovědi @Geir.
Bez hstore
a json
Pokud používáte starší verzi nebo nemůžete nainstalovat doplňkový modul hstore
nebo nemohu předpokládat, že je nainstalován, zde je vylepšená verze toho, co jsem zveřejnil dříve. Stále pomalejší než hstore
operátor, ačkoli:
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$;
Volejte:
CREATE TEMP TABLE t( a int, b text); -- Composite type for testing
SELECT f_setfield(NULL::t, 'a', '1');
Poznámky
-
Explicitní přetypování hodnoty
_val
na cílový datový typ není nutný, řetězcový literál v dynamickém dotazu by byl automaticky vynucen, čímž by se vyloučil poddotaz napg_type
. Ale udělal jsem to ještě o krok dál: -
Nahraďte
quote_literal(_val)
s přímým vkládáním hodnoty pomocíUSING
doložka. Ušetří jedno volání funkce a dvě přetypování a je každopádně bezpečnější.text
je v moderním PostgreSQL automaticky převeden na cílový typ. (Netestováno s verzemi před 9.1.) -
array_to_string(ARRAY())
je rychlejší nežstring_agg()
. -
Nejsou potřeba žádné proměnné, žádné
DECLARE
. Méně úkolů. -
Žádný poddotaz v dynamickém SQL.
($1).field
je rychlejší. -
pg_typeof(_comp_val)::text::regclass
dělá totéž jako(SELECT typrelid FROM pg_catalog.pg_type WHERE oid = pg_typeof($1)::oid)
pro platné složené typy, jen rychleji.
Tato poslední úprava je založena na předpokladu, žepg_type.typname
je vždy identické s přidruženýmpg_class.relname
pro registrované složené typy a dvojité přetypování může nahradit dílčí dotaz. Pro ověření jsem provedl tento test ve velké databázi a podle očekávání vyšel prázdný:
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
- Použití
INOUT
parametr odstraňuje potřebu explicitníhoRETURN
. Toto je pouze notační zkratka. Pavlovi se to nebude líbit, preferuje výslovnéRETURN
prohlášení ...
Vše dohromady je dvakrát rychlejší jako předchozí verze.
Původní (zastaralá) odpověď:
Výsledkem je verze, která je ~ 2,25krát rychlejší . Ale asi bych to nedokázal, aniž bych nestavěl na Pavlově druhé verzi.
Tato verze se navíc vyhne většině castingu na text a zpět tím, že uděláte vše v rámci jednoho dotazu, takže by měl být mnohem méně náchylný k chybám.
Testováno s PostgreSQL 9.0 a 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$;