Schneller mit hstore
Seit Postgres 9.0 , mit dem Zusatzmodul hstore
in Ihrer Datenbank installiert haben, gibt es mit dem #=
eine sehr einfache und schnelle Lösung Betreiber, der ...
ersetzen[s] Felder in record
mit übereinstimmenden Werten aus hstore
.
So installieren Sie das Modul:
CREATE EXTENSION hstore;
Beispiele:
SELECT my_record #= '"field"=>"value"'::hstore; -- with string literal
SELECT my_record #= hstore(field, value); -- with values
Werte müssen in text
umgewandelt werden und zurück, natürlich.
Beispiele für plpgsql-Funktionen mit weiteren Details:
- Endlosschleife in Triggerfunktion
- Neu durch Schlüssel in einem Postgres-Trigger zuweisen
Funktioniert jetzt mit json
/ jsonb
, auch!
Es gibt ähnliche Lösungen mit json
(Seite 9.3+) oder jsonb
(Seite 9.4+)
SELECT json_populate_record (my_record, json_build_object('key', 'new-value');
Die Funktionalität war undokumentiert, ist aber seit Postgres 13 offiziell. Das Handbuch:
Wenn base jedoch nicht NULL ist, werden die darin enthaltenen Werte für nicht übereinstimmende Spalten verwendet.
Sie können also jede vorhandene Zeile nehmen und beliebige Felder füllen (überschreiben, was darin enthalten ist).
Wesentliche Vorteile von json
vs hstore
:
- funktioniert mit Standard-Postgres, sodass Sie kein zusätzliches Modul benötigen.
- funktioniert auch für verschachtelte Arrays und zusammengesetzte Typen.
Kleiner Nachteil:etwas langsamer.
Weitere Informationen finden Sie in der hinzugefügten Antwort von @Geir.
Ohne hstore
und json
Wenn Sie auf einer älteren Version sind oder das Zusatzmodul hstore
nicht installieren können oder nicht davon ausgehen kann, dass es installiert ist, hier ist eine verbesserte Version dessen, was ich zuvor gepostet habe. Immer noch langsamer als der hstore
Operator, obwohl:
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$;
Aufruf:
CREATE TEMP TABLE t( a int, b text); -- Composite type for testing
SELECT f_setfield(NULL::t, 'a', '1');
Notizen
-
Eine explizite Umwandlung des Werts
_val
zum Zieldatentyp ist nicht erforderlich, ein Zeichenfolgenliteral in der dynamischen Abfrage würde automatisch erzwungen, wodurch die Unterabfrage aufpg_type
umgangen würde . Aber ich bin noch einen Schritt weiter gegangen: -
Ersetzen Sie
quote_literal(_val)
mit direkter Werteinfügung über denUSING
Klausel. Spart einen Funktionsaufruf und zwei Casts und ist sowieso sicherer.text
wird in modernem PostgreSQL automatisch zum Zieltyp gezwungen. (Nicht mit Versionen vor 9.1 getestet.) -
array_to_string(ARRAY())
ist schneller alsstring_agg()
. -
Keine Variablen erforderlich, kein
DECLARE
. Weniger Aufgaben. -
Keine Unterabfrage im dynamischen SQL.
($1).field
ist schneller. -
pg_typeof(_comp_val)::text::regclass
macht dasselbe wie(SELECT typrelid FROM pg_catalog.pg_type WHERE oid = pg_typeof($1)::oid)
für gültige zusammengesetzte Typen nur schneller.
Diese letzte Änderung basiert auf der Annahme, dasspg_type.typname
ist immer identisch mit dem zugehörigenpg_class.relname
für registrierte zusammengesetzte Typen, und die doppelte Umwandlung kann die Unterabfrage ersetzen. Ich habe diesen Test in einer großen Datenbank durchgeführt, um dies zu überprüfen, und er war wie erwartet leer:
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
- Die Verwendung eines
INOUT
Parameter macht ein explizitesRETURN
überflüssig . Dies ist nur eine notatorische Abkürzung. Pavel wird das nicht gefallen, er bevorzugt ein explizitesRETURN
Aussage ...
Alles zusammen ist doppelt so schnell wie die vorherige Version.
Ursprüngliche (veraltete) Antwort:
Das Ergebnis ist eine ~ 2,25-mal schnellere Version . Aber ich hätte es wahrscheinlich nicht geschafft, ohne auf Pavels zweiter Version aufzubauen.
Außerdem vermeidet diese Version den größten Teil des Castings zu Text und zurück, indem alles innerhalb einer einzigen Abfrage erledigt wird, daher sollte es viel weniger fehleranfällig sein.
Getestet mit PostgreSQL 9.0 und 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$;