Lebih cepat dengan hstore
Sejak Postgres 9.0 , dengan modul tambahan hstore
diinstal di database Anda ada solusi yang sangat sederhana dan cepat dengan #=
operator yang ...
ganti bidang [s] di
record
dengan nilai yang cocok darihstore
.
Untuk menginstal modul:
CREATE EXTENSION hstore;
Contoh:
SELECT my_record #= '"field"=>"value"'::hstore; -- with string literal
SELECT my_record #= hstore(field, value); -- with values
Nilai harus diberikan ke text
dan kembali, tentu saja.
Contoh fungsi plpgsql dengan lebih detail:
- Loop tak berujung dalam fungsi pemicu
- Tetapkan ke BARU dengan kunci di pemicu Postgres
Sekarang bekerja dengan json
/ jsonb
, juga!
Ada solusi serupa dengan json
(hal 9.3+) atau jsonb
(hal 9.4+)
SELECT json_populate_record (my_record, json_build_object('key', 'new-value');
Fungsionalitasnya tidak didokumentasikan, tetapi resmi sejak Postgres 13. Manual:
Namun, jika basis bukan NULL maka nilai yang dikandungnya akan digunakan untuk kolom yang tidak cocok.
Jadi, Anda dapat mengambil baris yang ada dan mengisi bidang arbitrer (menimpa apa yang ada di dalamnya).
Keuntungan utama json
vs hstore
:
- berfungsi dengan Postgres stok sehingga Anda tidak memerlukan modul tambahan.
- juga berfungsi untuk array bersarang dan tipe komposit.
Kerugian kecil:sedikit lebih lambat.
Lihat jawaban @Geir yang ditambahkan untuk detailnya.
Tanpa hstore
dan json
Jika Anda menggunakan versi yang lebih lama atau tidak dapat menginstal modul tambahan hstore
atau tidak dapat menganggap itu diinstal, ini adalah versi perbaikan dari apa yang saya posting sebelumnya. Masih lebih lambat dari hstore
operator, meskipun:
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$;
Telepon:
CREATE TEMP TABLE t( a int, b text); -- Composite type for testing
SELECT f_setfield(NULL::t, 'a', '1');
Catatan
-
Pemeran eksplisit dari nilai
_val
ke tipe data target tidak diperlukan, literal string dalam kueri dinamis akan dipaksakan secara otomatis, meniadakan subquery padapg_type
. Tapi saya mengambil satu langkah lebih jauh: -
Ganti
quote_literal(_val)
dengan penyisipan nilai langsung melaluiUSING
ayat. Menghemat satu panggilan fungsi dan dua gips, dan tetap lebih aman.text
dipaksa ke tipe target secara otomatis di PostgreSQL modern. (Tidak menguji dengan versi sebelum 9.1.) -
array_to_string(ARRAY())
lebih cepat daristring_agg()
. -
Tidak perlu variabel, tidak ada
DECLARE
. Lebih sedikit tugas. -
Tidak ada subquery dalam SQL dinamis.
($1).field
lebih cepat. -
pg_typeof(_comp_val)::text::regclass
melakukan hal yang sama seperti(SELECT typrelid FROM pg_catalog.pg_type WHERE oid = pg_typeof($1)::oid)
untuk tipe komposit yang valid, hanya lebih cepat.
Modifikasi terakhir ini dibangun dengan asumsi bahwapg_type.typname
selalu identik denganpg_class.relname
terkait untuk tipe komposit terdaftar, dan pemeran ganda dapat menggantikan subquery. Saya menjalankan tes ini di database besar untuk memverifikasi, dan hasilnya kosong seperti yang diharapkan:
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
- Penggunaan
INOUT
parameter meniadakan kebutuhan akanRETURN
yang eksplisit . Ini hanyalah jalan pintas notasi. Pavel tidak akan menyukainya, dia lebih sukaRETURN
explicit yang eksplisit pernyataan ...
Semuanya digabungkan menjadi dua kali lebih cepat seperti versi sebelumnya.
Jawaban asli (ketinggalan zaman):
Hasilnya adalah versi yang ~ 2,25 kali lebih cepat . Tapi saya mungkin tidak bisa melakukannya tanpa membangun versi kedua Pavel.
Selain itu, versi ini menghindari sebagian besar casting untuk mengirim teks dan kembali dengan melakukan semuanya dalam satu kueri, jadi seharusnya lebih sedikit kesalahan yang terjadi.
Diuji dengan PostgreSQL 9.0 dan 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$;