Snabbare med hstore
Sedan Postgres 9.0 , med tilläggsmodulen hstore
installerat i din databas finns det en mycket enkel och snabb lösning med #=
operatör som ...
ersätt[s] fält i
record
med matchande värden frånhstore
.
Så här installerar du modulen:
CREATE EXTENSION hstore;
Exempel:
SELECT my_record #= '"field"=>"value"'::hstore; -- with string literal
SELECT my_record #= hstore(field, value); -- with values
Värden måste castas till text
och tillbaka, uppenbarligen.
Exempel plpgsql-funktioner med mer information:
- Oändlig slinga i triggerfunktion
- Tilldela till NYTT genom att knappa in en Postgres-utlösare
Fungerar nu med json
/ jsonb
också!
Det finns liknande lösningar med json
(sid 9.3+) eller jsonb
(sid 9.4+)
SELECT json_populate_record (my_record, json_build_object('key', 'new-value');
Funktionen var odokumenterad, men den är officiell sedan Postgres 13. Manualen:
Men om basen inte är NULL kommer värdena den innehåller att användas för omatchade kolumner.
Så du kan ta vilken befintlig rad som helst och fylla godtyckliga fält (skriva över vad som finns i den).
Stora fördelar med json
kontra hstore
:
- fungerar med lager Postgres så att du inte behöver en extra modul.
- fungerar även för kapslade array- och sammansatta typer.
Mindre nackdel:lite långsammare.
Se @Geirs tillagda svar för detaljer.
Utan hstore
och json
Om du använder en äldre version eller inte kan installera tilläggsmodulen hstore
eller inte kan anta att det är installerat, här är en förbättrad version av det jag postade tidigare. Fortfarande långsammare än hstore
operatör, dock:
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:
CREATE TEMP TABLE t( a int, b text); -- Composite type for testing
SELECT f_setfield(NULL::t, 'a', '1');
Anteckningar
-
En explicit cast av värdet
_val
att måldatatypen inte är nödvändig, en bokstavlig sträng i den dynamiska frågan skulle tvingas fram automatiskt, vilket eliminerar underfrågan påpg_type
. Men jag tog det ett steg längre: -
Ersätt
quote_literal(_val)
med direkt värdeinsättning viaUSING
klausul. Sparar ett funktionssamtal och två casts, och är säkrare ändå.text
tvingas automatiskt till måltypen i modern PostgreSQL. (Testade inte med versioner före 9.1.) -
array_to_string(ARRAY())
är snabbare änstring_agg()
. -
Inga variabler behövs, ingen
DECLARE
. Färre uppdrag. -
Ingen underfråga i dynamisk SQL.
($1).field
är snabbare. -
pg_typeof(_comp_val)::text::regclass
gör samma sak som(SELECT typrelid FROM pg_catalog.pg_type WHERE oid = pg_typeof($1)::oid)
för giltiga sammansatta typer, bara snabbare.
Denna sista ändringen bygger på antagandet attpg_type.typname
är alltid identisk med den associeradepg_class.relname
för registrerade komposittyper, och dubbelgjutningen kan ersätta underfrågan. Jag körde det här testet i en stor databas för att verifiera, och det blev tomt som förväntat:
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
- Användningen av en
INOUT
parametern undviker behovet av en explicitRETURN
. Det här är bara en notationsgenväg. Pavel kommer inte att gilla det, han föredrar en tydligRETURN
uttalande ...
Allt tillsammans är det dubbelt så snabbt som den tidigare versionen.
Originalt (föråldrat) svar:
Resultatet är en version som är ~ 2,25 gånger snabbare . Men jag hade nog inte kunnat göra det utan att bygga vidare på Pavels andra version.
Dessutom undviker den här versionen större delen av castingen till text och tillbaka genom att göra allt inom en enda fråga, så det borde vara mycket mindre felbenäget.
Testat med PostgreSQL 9.0 och 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$;