sql >> Databáze >  >> RDS >> PostgreSQL

Jak nastavit hodnotu pole složené proměnné pomocí dynamického SQL

Rychlejší s hstore

Od Postgres 9.0 s přídavným modulem hstore nainstalované ve vaší databázi existuje velmi jednoduché a rychlé řešení s #= operátor, který ...

nahradit [s] pole v record s odpovídajícími hodnotami z hstore .

Instalace modulu:

CREATE EXTENSION hstore;

Příklady:

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

Hodnoty musí být přetypovány do text a zpět, samozřejmě.

Příklad funkcí plpgsql s dalšími podrobnostmi:

  • Nekonečná smyčka ve funkci spouštění
  • Přiřadit k NEW pomocí klíče ve spouštěči Postgres

Nyní funguje s json / jsonb také!

Podobná řešení existují s json (str. 9.3+) nebo jsonb (str. 9.4+)

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

Funkce byla nezdokumentovaná, ale je oficiální od Postgres 13. Manuál:

Pokud však základ není NULL, pak hodnoty, které obsahuje, budou použity pro neodpovídající sloupce.

Můžete tedy vzít jakýkoli existující řádek a vyplnit libovolná pole (přepsat to, co v něm je).

Hlavní výhody json vs hstore :

  • funguje se skladem Postgres, takže nepotřebujete další modul.
  • funguje také pro vnořené pole a složené typy.

Menší nevýhoda:trochu pomalejší.

Podrobnosti naleznete v přidané odpovědi @Geir.

Bez hstore a json

Pokud používáte starší verzi nebo nemůžete nainstalovat doplňkový modul hstore nebo nemohu předpokládat, že je nainstalován, zde je vylepšená verze toho, co jsem zveřejnil dříve. Stále pomalejší než hstore operátor, ačkoli:

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

Volejte:

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

Poznámky

  • Explicitní přetypování hodnoty _val na cílový datový typ není nutný, řetězcový literál v dynamickém dotazu by byl automaticky vynucen, čímž by se vyloučil poddotaz na pg_type . Ale udělal jsem to ještě o krok dál:

  • Nahraďte quote_literal(_val) s přímým vkládáním hodnoty pomocí USING doložka. Ušetří jedno volání funkce a dvě přetypování a je každopádně bezpečnější. text je v moderním PostgreSQL automaticky převeden na cílový typ. (Netestováno s verzemi před 9.1.)

  • array_to_string(ARRAY()) je rychlejší než string_agg() .

  • Nejsou potřeba žádné proměnné, žádné DECLARE . Méně úkolů.

  • Žádný poddotaz v dynamickém SQL. ($1).field je rychlejší.

  • pg_typeof(_comp_val)::text::regclass
    dělá totéž jako
    (SELECT typrelid FROM pg_catalog.pg_type WHERE oid = pg_typeof($1)::oid)
    pro platné složené typy, jen rychleji.
    Tato poslední úprava je založena na předpokladu, že pg_type.typname je vždy identické s přidruženým pg_class.relname pro registrované složené typy a dvojité přetypování může nahradit dílčí dotaz. Pro ověření jsem provedl tento test ve velké databázi a podle očekávání vyšel prázdný:

    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
  • Použití INOUT parametr odstraňuje potřebu explicitního RETURN . Toto je pouze notační zkratka. Pavlovi se to nebude líbit, preferuje výslovné RETURN prohlášení ...

Vše dohromady je dvakrát rychlejší jako předchozí verze.

Původní (zastaralá) odpověď:

Výsledkem je verze, která je ~ 2,25krát rychlejší . Ale asi bych to nedokázal, aniž bych nestavěl na Pavlově druhé verzi.

Tato verze se navíc vyhne většině castingu na text a zpět tím, že uděláte vše v rámci jednoho dotazu, takže by měl být mnohem méně náchylný k chybám.
Testováno s PostgreSQL 9.0 a 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. Co je SQL Injection?

  2. Jak naplnit druhý rozevírací seznam na základě výběru prvního rozevíracího seznamu pomocí jQuery/AJAX a PHP/MySQL?

  3. UnicodeEncodeError:Kodek 'latin-1' nemůže kódovat znak

  4. Převeďte razítko MySql DateTime do formátu Datum v JavaScriptu