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

Memfaktorkan ulang fungsi PL/pgSQL untuk mengembalikan output dari berbagai kueri SELECT

SQL Dinamis dan RETURN ketik


Anda ingin menjalankan SQL dinamis . Pada prinsipnya, itu sederhana di plpgsql dengan bantuan EXECUTE . Anda tidak membutuhkan sebuah kursor. Faktanya, sebagian besar waktu Anda lebih baik tanpa kursor eksplisit.

Masalah yang Anda hadapi:Anda ingin mengembalikan catatan dengan jenis yang belum ditentukan . Sebuah fungsi perlu mendeklarasikan tipe kembaliannya di RETURNS klausa (atau dengan OUT atau INOUT parameter). Dalam kasus Anda, Anda harus kembali ke catatan anonim, karena nomor , nama dan jenis kolom yang dikembalikan bervariasi. Seperti:

CREATE FUNCTION data_of(integer)
  RETURNS SETOF record AS ...

Namun, ini tidak terlalu berguna. Anda harus memberikan daftar definisi kolom dengan setiap panggilan. Seperti:

SELECT * FROM data_of(17)
AS foo (colum_name1 integer
      , colum_name2 text
      , colum_name3 real);

Tetapi bagaimana Anda akan melakukan ini, ketika Anda tidak mengetahui kolom sebelumnya?
Anda dapat menggunakan tipe data dokumen yang kurang terstruktur seperti json , jsonb , hstore atau xml . Lihat:

  • Bagaimana cara menyimpan tabel data dalam database?

Namun, untuk tujuan pertanyaan ini, anggaplah Anda ingin mengembalikan kolom individual, diketik dengan benar, dan diberi nama sebanyak mungkin.

Solusi sederhana dengan tipe pengembalian tetap

Kolom datahora tampaknya diberikan, saya akan menganggap tipe data timestamp dan selalu ada dua kolom lagi dengan nama dan tipe data yang berbeda.

Nama kami akan mengabaikannya demi nama generik dalam tipe pengembalian.
Jenis kita juga akan mengabaikannya, dan membuang semuanya ke text sejak setiap tipe data dapat dilemparkan ke text .

CREATE OR REPLACE FUNCTION data_of(_id integer)
  RETURNS TABLE (datahora timestamp, col2 text, col3 text)
  LANGUAGE plpgsql AS
$func$
DECLARE
   _sensors text := 'col1::text, col2::text';  -- cast each col to text
   _type    text := 'foo';
BEGIN
   RETURN QUERY EXECUTE '
      SELECT datahora, ' || _sensors || '
      FROM   ' || quote_ident(_type) || '
      WHERE  id = $1
      ORDER  BY datahora'
   USING  _id;
END
$func$;

Variabel _sensors dan _type bisa menjadi parameter input sebagai gantinya.

Perhatikan RETURNS TABLE klausa.

Perhatikan penggunaan RETURN QUERY EXECUTE . Itu adalah salah satu cara yang lebih elegan untuk mengembalikan baris dari kueri dinamis.

Saya menggunakan nama untuk parameter fungsi, hanya untuk membuat USING klausa RETURN QUERY EXECUTE kurang membingungkan. $1 di SQL-string tidak merujuk ke parameter fungsi tetapi ke nilai yang diteruskan dengan USING ayat. (Keduanya kebetulan $1 dalam ruang lingkup masing-masing dalam contoh sederhana ini.)

Perhatikan nilai contoh untuk _sensors :setiap kolom dicetak untuk mengetik text .

Kode semacam ini sangat rentan terhadap injeksi SQL . Saya menggunakan quote_ident() untuk melindunginya. Menyatukan beberapa nama kolom dalam variabel _sensors mencegah penggunaan quote_ident() (dan biasanya merupakan ide yang buruk!). Pastikan tidak ada hal buruk di sana dengan cara lain, misalnya dengan menjalankan nama kolom satu per satu melalui quote_ident() sebagai gantinya. Sebuah VARIADIC parameter muncul di pikiran ...

Sederhana sejak PostgreSQL 9.1

Dengan versi 9.1 atau yang lebih baru, Anda dapat menggunakan format() untuk lebih menyederhanakan:

RETURN QUERY EXECUTE format('
   SELECT datahora, %s  -- identifier passed as unescaped string
   FROM   %I            -- assuming the name is provided by user
   WHERE  id = $1
   ORDER  BY datahora'
  ,_sensors, _type)
USING  _id;

Sekali lagi, nama kolom individu dapat diloloskan dengan benar dan akan menjadi cara yang bersih.

Jumlah variabel kolom yang memiliki tipe yang sama

Setelah pertanyaan Anda diperbarui, sepertinya tipe pengembalian Anda telah

  • variabel angka kolom
  • tetapi semua kolom dengan tipe yang sama double precision (alias float8 )

Gunakan ARRAY ketik dalam kasus ini untuk menyarangkan sejumlah nilai variabel. Selain itu, saya mengembalikan array dengan nama kolom:

CREATE OR REPLACE FUNCTION data_of(_id integer)
  RETURNS TABLE (datahora timestamp, names text[], values float8[])
  LANGUAGE plpgsql AS
$func$
DECLARE
   _sensors text := 'col1, col2, col3';  -- plain list of column names
   _type    text := 'foo';
BEGIN
   RETURN QUERY EXECUTE format('
      SELECT datahora
           , string_to_array($1)  -- AS names
           , ARRAY[%s]            -- AS values
      FROM   %s
      WHERE  id = $2
      ORDER  BY datahora'
    , _sensors, _type)
   USING  _sensors, _id;
END
$func$;

Berbagai jenis tabel lengkap

Untuk benar-benar mengembalikan semua kolom tabel , ada solusi sederhana dan kuat menggunakan tipe polimorfik :

CREATE OR REPLACE FUNCTION data_of(_tbl_type anyelement, _id int)
  RETURNS SETOF anyelement
  LANGUAGE plpgsql AS
$func$
BEGIN
   RETURN QUERY EXECUTE format('
      SELECT *
      FROM   %s  -- pg_typeof returns regtype, quoted automatically
      WHERE  id = $1
      ORDER  BY datahora'
    , pg_typeof(_tbl_type))
   USING  _id;
END
$func$;

Telepon (penting!):

SELECT * FROM data_of(NULL::pcdmet, 17);

Ganti pcdmet dalam panggilan dengan nama tabel lainnya.

Bagaimana cara kerjanya?

anyelement adalah tipe data semu, tipe polimorfik, pengganti untuk semua tipe data non-array. Semua kemunculan anyelement dalam fungsi mengevaluasi ke jenis yang sama yang disediakan pada waktu berjalan. Dengan memberikan nilai dari tipe yang ditentukan sebagai argumen ke fungsi, kami secara implisit mendefinisikan tipe yang dikembalikan.

PostgreSQL secara otomatis mendefinisikan tipe baris (tipe data komposit) untuk setiap tabel yang dibuat, jadi ada tipe yang terdefinisi dengan baik untuk setiap tabel. Ini termasuk tabel sementara, yang nyaman untuk penggunaan ad-hoc.

Jenis apa pun dapat berupa NULL . Berikan NULL nilai, masukkan ke jenis tabel:NULL::pcdmet .

Sekarang fungsi mengembalikan tipe baris yang terdefinisi dengan baik dan kita dapat menggunakan SELECT * FROM data_of() untuk menguraikan baris dan mendapatkan kolom individual.

pg_typeof(_tbl_type) mengembalikan nama tabel sebagai tipe pengenal objek regtype . Ketika secara otomatis dikonversi ke text , pengidentifikasi secara otomatis dikutip ganda dan memenuhi syarat skema jika diperlukan, bertahan melawan injeksi SQL secara otomatis. Ini bahkan dapat menangani nama tabel yang memenuhi syarat skema di mana quote_ident() akan gagal. Lihat:

  • Nama tabel sebagai parameter fungsi PostgreSQL


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Bagaimana cara mengatur ulang SUM yang berjalan setelah mencapai ambang batas?

  2. Panggilan fungsi PostgreSQL

  3. Kemajuan dalam peningkatan online

  4. Memetakan array dengan Hibernate

  5. Postgres 9.4 jsonb array sebagai tabel