sql >> Database >  >> RDS >> PostgreSQL

Hoe de waarde van het samengestelde variabele veld in te stellen met behulp van dynamische SQL

Sneller met hstore

Sinds Postgres 9.0 , met de extra module hstore geïnstalleerd in uw database is er een zeer eenvoudige en snelle oplossing met de #= operator die ...

vervang [s] velden in record met overeenkomende waarden van hstore .

Om de module te installeren:

CREATE EXTENSION hstore;

Voorbeelden:

SELECT my_record #= '"field"=>"value"'::hstore;  -- with string literal
SELECT my_record #= hstore(field, value);        -- with values

Waarden moeten worden gecast naar text en terug natuurlijk.

Voorbeelden van plpgsql-functies met meer details:

  • Eindeloze lus in triggerfunctie
  • Toewijzen aan NIEUW met een toets in een Postgres-trigger

Werkt nu met json / jsonb , ook!

Er zijn vergelijkbare oplossingen met json (pag 9.3+) of jsonb (pag 9.4+)

SELECT json_populate_record (my_record, json_build_object('key', 'new-value');

De functionaliteit was niet gedocumenteerd, maar het is officieel sinds Postgres 13. De handleiding:

Als de basis echter niet NULL is, worden de waarden die erin staan ​​gebruikt voor niet-overeenkomende kolommen.

U kunt dus elke bestaande rij nemen en willekeurige velden invullen (overschrijven wat erin staat).

Grote voordelen van json vs hstore :

  • werkt met standaard Postgres, dus je hebt geen extra module nodig.
  • werkt ook voor geneste array- en samengestelde typen.

Klein nadeel:iets langzamer.

Zie het toegevoegde antwoord van @Geir voor details.

Zonder hstore en json

Als u een oudere versie gebruikt of de extra module hstore . niet kunt installeren of kan niet aannemen dat het is geïnstalleerd, hier is een verbeterde versie van wat ik eerder heb gepost. Nog steeds langzamer dan de hstore operator echter:

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$;

Bel:

CREATE TEMP TABLE t( a int, b text);  -- Composite type for testing
SELECT f_setfield(NULL::t, 'a', '1');

Opmerkingen

  • Een expliciete cast van de waarde _val naar het doelgegevenstype is niet nodig, een letterlijke tekenreeks in de dynamische query zou automatisch worden afgedwongen, waardoor de subquery op pg_type wordt vermeden . Maar ik ging nog een stap verder:

  • Vervang quote_literal(_val) met directe waarde-invoer via de USING clausule. Bespaart één functieaanroep en twee casts, en is sowieso veiliger. text wordt automatisch gedwongen naar het doeltype in moderne PostgreSQL. (Niet getest met versies vóór 9.1.)

  • array_to_string(ARRAY()) is sneller dan string_agg() .

  • Geen variabelen nodig, geen DECLARE . Minder opdrachten.

  • Geen subquery in de dynamische SQL. ($1).field is sneller.

  • pg_typeof(_comp_val)::text::regclass
    doet hetzelfde als
    (SELECT typrelid FROM pg_catalog.pg_type WHERE oid = pg_typeof($1)::oid)
    voor geldige samengestelde typen, alleen sneller.
    Deze laatste wijziging is gebaseerd op de veronderstelling dat pg_type.typname is altijd identiek aan de bijbehorende pg_class.relname voor geregistreerde samengestelde typen, en de dubbele cast kan de subquery vervangen. Ik heb deze test in een grote database uitgevoerd om te verifiëren, en hij was leeg zoals verwacht:

    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
  • Het gebruik van een INOUT parameter vermijdt de noodzaak van een expliciete RETURN . Dit is slechts een notatie sneltoets. Pavel zal het niet leuk vinden, hij geeft de voorkeur aan een expliciete RETURN verklaring ...

Alles bij elkaar is dit twee keer zo snel als de vorige versie.

Oorspronkelijk (verouderd) antwoord:

Het resultaat is een versie die ~ 2,25 keer sneller is . Maar ik had het waarschijnlijk niet kunnen doen zonder voort te bouwen op Pavels tweede versie.

Bovendien vermijdt deze versie het grootste deel van de casting naar tekst en terug door alles binnen één enkele query te doen, dus het zou veel minder foutgevoelig moeten zijn.
Getest met PostgreSQL 9.0 en 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$;


  1. PostgreSQL-databases migreren van op locatie naar de cloud met AWS RDS

  2. Postgres integer-arrays als parameters?

  3. Een relatie maken in SQL Server 2017

  4. Meerdere datacenterconfiguraties met Galera Cluster voor MySQL of MariaDB