PostgreSQL
 sql >> Teknologi Basis Data >  >> RDS >> PostgreSQL

Cara mengatur nilai bidang variabel komposit menggunakan SQL dinamis

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 dari hstore .

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 pada pg_type . Tapi saya mengambil satu langkah lebih jauh:

  • Ganti quote_literal(_val) dengan penyisipan nilai langsung melalui USING 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 dari string_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 bahwa pg_type.typname selalu identik dengan pg_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 akan RETURN yang eksplisit . Ini hanyalah jalan pintas notasi. Pavel tidak akan menyukainya, dia lebih suka RETURN 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$;


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Apa sebenarnya yang dilakukan pg_escape_string?

  2. BERGABUNG (PILIH ... ) pada 1=1?

  3. Optimasi Kueri di PostgreSQL. JELASKAN Dasar-dasar – Bagian 1

  4. PSQLException:ResultSet tidak diposisikan dengan benar, mungkin Anda perlu menelepon berikutnya

  5. Apa cara tercepat untuk memotong cap waktu menjadi 5 menit di Postgres?