PostgreSQL
 sql >> Baza danych >  >> RDS >> PostgreSQL

Jak ustawić wartość pola zmiennej złożonej za pomocą dynamicznego SQL?

Szybciej z hstore

Od Postgresa 9.0 , z dodatkowym modułem hstore zainstalowane w Twojej bazie danych jest bardzo proste i szybkie rozwiązanie z #= operator, który ...

zamień[s] pola w record z pasującymi wartościami z hstore .

Aby zainstalować moduł:

CREATE EXTENSION hstore;

Przykłady:

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

Wartości muszą być rzutowane na text i oczywiście z powrotem.

Przykładowe funkcje plpgsql z większą ilością szczegółów:

  • Niekończąca się pętla w funkcji wyzwalania
  • Przypisz do NEW za pomocą klucza w wyzwalaczu Postgres

Teraz działa z json / jsonb też!

Istnieją podobne rozwiązania z json (str. 9.3+) lub jsonb (str. 9.4+)

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

Funkcjonalność była nieudokumentowana, ale jest oficjalna od czasu Postgres 13. Instrukcja:

Jeśli jednak podstawa nie jest równa NULL, wartości, które zawiera, zostaną użyte dla niedopasowanych kolumn.

Możesz więc wziąć dowolny istniejący wiersz i wypełnić dowolne pola (zastępując to, co w nim jest).

Główne zalety json vs hstore :

  • współpracuje ze standardowym Postgresem, więc nie potrzebujesz dodatkowego modułu.
  • działa również z zagnieżdżonymi tablicami i typami złożonymi.

Drobna wada:trochę wolniej.

Zobacz dodaną odpowiedź @Geira, aby uzyskać szczegółowe informacje.

Bez hstore i json

Jeśli korzystasz ze starszej wersji lub nie możesz zainstalować dodatkowego modułu hstore lub nie mogę założyć, że jest zainstalowany, oto ulepszona wersja tego, co opublikowałem wcześniej. Wciąż wolniej niż hstore operator, chociaż:

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

Zadzwoń:

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

Notatki

  • Jawne rzutowanie wartości _val do docelowego typu danych nie jest konieczne, literał ciągu w zapytaniu dynamicznym byłby automatycznie wymuszony, omijając podzapytanie w pg_type . Ale poszedłem o krok dalej:

  • Zastąp quote_literal(_val) z bezpośrednim wstawianiem wartości za pomocą USING klauzula. Zapisuje jedno wywołanie funkcji i dwa rzutowania, a mimo to jest bezpieczniejsze. text jest automatycznie przekonwertowany na typ docelowy we współczesnym PostgreSQL. (Nie testowano z wersjami przed 9.1.)

  • array_to_string(ARRAY()) jest szybszy niż string_agg() .

  • Żadne zmienne nie są potrzebne, nie ma DECLARE . Mniej zadań.

  • Brak podzapytania w dynamicznym SQL. ($1).field jest szybszy.

  • pg_typeof(_comp_val)::text::regclass
    robi to samo, co
    (SELECT typrelid FROM pg_catalog.pg_type WHERE oid = pg_typeof($1)::oid)
    dla prawidłowych typów złożonych, po prostu szybciej.
    Ta ostatnia modyfikacja jest zbudowana na założeniu, że pg_type.typname jest zawsze identyczny z powiązanym pg_class.relname dla zarejestrowanych typów złożonych, a podwójne rzutowanie może zastąpić podzapytanie. Przeprowadziłem ten test w dużej bazie danych w celu weryfikacji i zgodnie z oczekiwaniami okazał się pusty:

    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
  • Użycie INOUT parametr eliminuje potrzebę jawnego RETURN . To tylko skrót notacji. Pavelowi się to nie spodoba, woli wyraźny RETURN oświadczenie ...

Wszystko razem jest dwa razy szybsze jak w poprzedniej wersji.

Oryginalna (nieaktualna) odpowiedź:

Rezultatem jest wersja, która jest ~ 2,25 razy szybsza . Ale prawdopodobnie nie mógłbym tego zrobić bez budowania na drugiej wersji Pawła.

Ponadto ta wersja unika większości castingów do tekstu i z powrotem, wykonując wszystko w ramach jednego zapytania, więc powinno być znacznie mniej podatne na błędy.
Testowane z PostgreSQL 9.0 i 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. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Uruchom zapytanie z LIMIT/OFFSET, a także uzyskaj całkowitą liczbę wierszy

  2. Dołącz do czterech stołów z udziałem LEFT JOIN bez duplikatów

  3. Jak usunąć wartość typu enum w postgresie?

  4. Postgres FOR LOOP

  5. Jak obliczyć medianę w PostgreSQL