PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

So legen Sie den Wert eines zusammengesetzten Variablenfelds mit dynamischem SQL fest

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 auf pg_type umgangen würde . Aber ich bin noch einen Schritt weiter gegangen:

  • Ersetzen Sie quote_literal(_val) mit direkter Werteinfügung über den USING 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 als string_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, dass pg_type.typname ist immer identisch mit dem zugehörigen pg_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 explizites RETURN überflüssig . Dies ist nur eine notatorische Abkürzung. Pavel wird das nicht gefallen, er bevorzugt ein explizites RETURN 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$;