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
(aliasfloat8
)
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