sql >> Databasteknik >  >> RDS >> PostgreSQL

Hur man ställer in värdet på det sammansatta variabelfältet med hjälp av dynamisk SQL

Snabbare med hstore

Sedan Postgres 9.0 , med tilläggsmodulen hstore installerat i din databas finns det en mycket enkel och snabb lösning med #= operatör som ...

ersätt[s] fält i record med matchande värden från hstore .

Så här installerar du modulen:

CREATE EXTENSION hstore;

Exempel:

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

Värden måste castas till text och tillbaka, uppenbarligen.

Exempel plpgsql-funktioner med mer information:

  • Oändlig slinga i triggerfunktion
  • Tilldela till NYTT genom att knappa in en Postgres-utlösare

Fungerar nu med json / jsonb också!

Det finns liknande lösningar med json (sid 9.3+) eller jsonb (sid 9.4+)

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

Funktionen var odokumenterad, men den är officiell sedan Postgres 13. Manualen:

Men om basen inte är NULL kommer värdena den innehåller att användas för omatchade kolumner.

Så du kan ta vilken befintlig rad som helst och fylla godtyckliga fält (skriva över vad som finns i den).

Stora fördelar med json kontra hstore :

  • fungerar med lager Postgres så att du inte behöver en extra modul.
  • fungerar även för kapslade array- och sammansatta typer.

Mindre nackdel:lite långsammare.

Se @Geirs tillagda svar för detaljer.

Utan hstore och json

Om du använder en äldre version eller inte kan installera tilläggsmodulen hstore eller inte kan anta att det är installerat, här är en förbättrad version av det jag postade tidigare. Fortfarande långsammare än hstore operatör, dock:

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:

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

Anteckningar

  • En explicit cast av värdet _val att måldatatypen inte är nödvändig, en bokstavlig sträng i den dynamiska frågan skulle tvingas fram automatiskt, vilket eliminerar underfrågan på pg_type . Men jag tog det ett steg längre:

  • Ersätt quote_literal(_val) med direkt värdeinsättning via USING klausul. Sparar ett funktionssamtal och två casts, och är säkrare ändå. text tvingas automatiskt till måltypen i modern PostgreSQL. (Testade inte med versioner före 9.1.)

  • array_to_string(ARRAY()) är snabbare än string_agg() .

  • Inga variabler behövs, ingen DECLARE . Färre uppdrag.

  • Ingen underfråga i dynamisk SQL. ($1).field är snabbare.

  • pg_typeof(_comp_val)::text::regclass
    gör samma sak som
    (SELECT typrelid FROM pg_catalog.pg_type WHERE oid = pg_typeof($1)::oid)
    för giltiga sammansatta typer, bara snabbare.
    Denna sista ändringen bygger på antagandet att pg_type.typname är alltid identisk med den associerade pg_class.relname för registrerade komposittyper, och dubbelgjutningen kan ersätta underfrågan. Jag körde det här testet i en stor databas för att verifiera, och det blev tomt som förväntat:

    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
  • Användningen av en INOUT parametern undviker behovet av en explicit RETURN . Det här är bara en notationsgenväg. Pavel kommer inte att gilla det, han föredrar en tydlig RETURN uttalande ...

Allt tillsammans är det dubbelt så snabbt som den tidigare versionen.

Originalt (föråldrat) svar:

Resultatet är en version som är ~ 2,25 gånger snabbare . Men jag hade nog inte kunnat göra det utan att bygga vidare på Pavels andra version.

Dessutom undviker den här versionen större delen av castingen till text och tillbaka genom att göra allt inom en enda fråga, så det borde vara mycket mindre felbenäget.
Testat med PostgreSQL 9.0 och 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. Hur man fångar och analyserar SQL Server-händelser

  2. Vad är sql-anslutningssträngen jag behöver använda för att komma åt localhost\SQLEXPRESS med Windows-autentisering eller SQL-autentisering?

  3. Varför tillåter MySQL grupp efter frågor UTAN aggregatfunktioner?

  4. Rails/Postgresql SQL-skillnader med datum