sql >> Database teknologi >  >> RDS >> PostgreSQL

Sådan indstilles værdien af ​​det sammensatte variabelfelt ved hjælp af dynamisk SQL

Hurtigere med hstore

Siden Postgres 9.0 , med det ekstra modul hstore installeret i din database er der en meget enkel og hurtig løsning med #= operatør, der ...

erstatte[s] felter i record med matchende værdier fra hstore .

Sådan installeres modulet:

CREATE EXTENSION hstore;

Eksempler:

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

Værdier skal castes til text og tilbage, naturligvis.

Eksempel plpgsql-funktioner med flere detaljer:

  • Endeløs sløjfe i triggerfunktion
  • Tildel til NY ved at indtaste en Postgres-trigger

Fungerer nu med json / jsonb også!

Der er lignende løsninger med json (s. 9.3+) eller jsonb (side 9.4+)

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

Funktionaliteten var udokumenteret, men den er officiel siden Postgres 13. Manualen:

Men hvis base ikke er NULL, vil de værdier, den indeholder, blive brugt til umatchede kolonner.

Så du kan tage en hvilken som helst eksisterende række og udfylde vilkårlige felter (overskrive, hvad der er i den).

Store fordele ved json vs hstore :

  • fungerer med lager Postgres, så du behøver ikke et ekstra modul.
  • fungerer også for indlejrede array- og sammensatte typer.

Mindre ulempe:lidt langsommere.

Se @Geirs tilføjede svar for detaljer.

Uden hstore og json

Hvis du er på en ældre version eller ikke kan installere det ekstra modul hstore eller ikke kan antage, at det er installeret, her er en forbedret version af det, jeg tidligere har postet. Stadig langsommere end hstore operatør, dog:

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 til:

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

Bemærkninger

  • En eksplicit cast af værdien _val til måldatatypen er ikke nødvendig, en streng bogstavelig i den dynamiske forespørgsel vil blive tvunget automatisk, hvilket undgår underforespørgslen på pg_type . Men jeg tog det et skridt videre:

  • Erstat quote_literal(_val) med direkte værdiindsættelse via USING klausul. Gemmer et funktionsopkald og to casts, og er alligevel sikrere. text tvinges automatisk til måltypen i moderne PostgreSQL. (Afprøvede ikke med versioner før 9.1.)

  • array_to_string(ARRAY()) er hurtigere end string_agg() .

  • Ingen variable nødvendige, ingen DECLARE . Færre opgaver.

  • Ingen underforespørgsel i den dynamiske SQL. ($1).field er hurtigere.

  • pg_typeof(_comp_val)::text::regclass
    gør det samme som
    (SELECT typrelid FROM pg_catalog.pg_type WHERE oid = pg_typeof($1)::oid)
    for gyldige sammensatte typer, bare hurtigere.
    Denne sidste ændring er bygget på den antagelse, at pg_type.typname er altid identisk med den tilknyttede pg_class.relname for registrerede komposittyper, og dobbeltstøbningen kan erstatte underforespørgslen. Jeg kørte denne test i en stor database for at bekræfte, og den kom op som forventet tom:

    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
  • Brugen af ​​en INOUT parameter undgår behovet for en eksplicit RETURN . Dette er blot en notationsgenvej. Pavel vil ikke lide det, han foretrækker en eksplicit RETURN erklæring ...

Alt sammen er dette dobbelt så hurtigt som den tidligere version.

Originalt (forældet) svar:

Resultatet er en version, der er ~ 2,25 gange hurtigere . Men jeg kunne nok ikke have gjort det uden at bygge videre på Pavels anden version.

Derudover undgår denne version det meste af castingen til tekst og tilbage ved at gøre alt inden for en enkelt forespørgsel, så den burde være meget mindre fejltilbøjelig.
Testet med PostgreSQL 9.0 og 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. Fejlfinding:MySQL/MariaDB-fejl #1044 Е Adgang nægtet for bruger

  2. Sådan fungerer Atan2d() i PostgreSQL

  3. Sådan åbnes en database i eksklusiv tilstand i Access 2016

  4. Hvordan kan du navngive datasættets tabeller, du returnerer i en lagret proc?