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 wpg_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, żepg_type.typname
jest zawsze identyczny z powiązanympg_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ę jawnegoRETURN
. To tylko skrót notacji. Pavelowi się to nie spodoba, woli wyraźnyRETURN
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$;