Menyimpan rumus dalam sebuah tabel dan menggunakan rumus dalam suatu fungsi

10

Saya memiliki database PostgreSQL 9.1 di mana sebagian menangani komisi agen. Setiap agen memiliki rumus penghitungan sendiri berapa banyak komisi yang mereka dapatkan. Saya memiliki fungsi untuk menghasilkan jumlah komisi yang harus didapatkan setiap agen, tetapi menjadi tidak mungkin untuk digunakan ketika jumlah agen bertambah. Saya dipaksa untuk melakukan beberapa pernyataan kasus yang sangat panjang dan kode berulang, yang membuat fungsi saya sangat besar.

Semua formula memiliki variabel konstan:

d .. hari kerja bulan itu
r .. node baru bertambah
l .. skor loyalitas
s .. komisi subagen
b .. tarif dasar
i .. pendapatan diperoleh

Rumusnya bisa seperti:

d*b+(l*4+r)+(i/d)+s

Setiap agen menegosiasikan formula pembayaran dengan departemen SDM. Jadi bisakah saya menyimpan rumus di tabel agen lalu memiliki fungsi kecil yang hanya mendapatkan rumus dari tabel dan menerjemahkannya dengan nilai dan menghitung jumlahnya?

indago
sumber

Jawaban:

6

Mempersiapkan

Rumus Anda terlihat seperti ini:

d*b+(l*4+r)+(i/d)+s

Saya akan mengganti variabel dengan $nnotasi sehingga mereka dapat diganti dengan nilai langsung di plpgsql EXECUTE(lihat di bawah):

$1*$5+($3*4+$2)+($6/$1)+$4

Anda dapat menyimpan formula asli Anda tambahan (untuk mata manusia) atau menghasilkan formulir ini secara dinamis dengan ekspresi seperti:

SELECT regexp_replace(regexp_replace(regexp_replace(
       regexp_replace(regexp_replace(regexp_replace(
      'd*b+(l*4+r)+(i/d)+s'
      , '\md\M', '$1', 'g')
      , '\mr\M', '$2', 'g')
      , '\ml\M', '$3', 'g')
      , '\ms\M', '$4', 'g')
      , '\mb\M', '$5', 'g')
      , '\mi\M', '$6', 'g');

Pastikan saja, terjemahan Anda baik-baik saja. Beberapa penjelasan untuk ekspresi regexp :

\ m .. hanya cocok di awal kata
\ M .. hanya cocok di akhir kata

Parameter 4 'g'.. ganti secara global

Fungsi inti

CREATE OR REPLACE FUNCTION f_calc(
    d int         --  days worked that month
   ,r int         --  new nodes accuired
   ,l int         --  loyalty score
   ,s numeric     --  subagent commission
   ,b numeric     --  base rate
   ,i numeric     --  revenue gained
   ,formula text
   ,OUT result numeric
)  RETURNS numeric AS
$func$
BEGIN    
   EXECUTE 'SELECT '|| formula
   INTO   result
   USING  $1, $2, $3, $4, $5, $6;                                          
END
$func$ LANGUAGE plpgsql SECURITY DEFINER IMMUTABLE; 

Panggilan:

SELECT f_calc(1, 2, 3, 4.1, 5.2, 6.3, '$1*$5+($3*4+$2)+($6/$1)+$4');

Pengembalian:

29.6000000000000000

Poin utama

  • Fungsi ini mengambil 6 parameter nilai dan formula textsebagai 7. Saya menempatkan formula terakhir, jadi kita bisa menggunakan $1 .. $6bukan $2 .. $7. Hanya demi keterbacaan.
    Saya menetapkan tipe data untuk nilai-nilai yang saya lihat cocok. Tetapkan jenis yang tepat (untuk menerapkan pemeriksaan kewarasan dasar) atau buat semuanya numeric:

  • Masukkan nilai untuk eksekusi dinamis dengan USINGklausa. Ini menghindari bolak-balik dan membuat segalanya lebih sederhana, lebih aman dan lebih cepat.

  • Saya menggunakan OUTparameter karena itu lebih elegan dan membuat sintaks yang lebih jelas lebih pendek. Final RETURNtidak diperlukan, nilai parameter OUT dikembalikan secara otomatis.

  • Pertimbangkan kuliah tentang keamanan oleh @Chris dan bab "Menulis Fungsi Penjamin Keamanan dengan Aman" dalam manual. Dalam desain saya, satu-satunya titik injeksi adalah formula itu sendiri.

  • Anda dapat menggunakan default untuk beberapa parameter untuk lebih menyederhanakan panggilan.

Erwin Brandstetter
sumber
5

Harap baca ini dengan seksama mengenai pertimbangan keamanan. Pada dasarnya Anda mencoba untuk menyuntikkan SQL sewenang-wenang dalam fungsi Anda. Karenanya, Anda harus menjalankan ini di bawah pengguna dengan izin yang sangat terbatas.

  1. Buat pengguna dan cabut semua izin darinya. Jangan berikan izin kepada publik dalam db yang sama saat Anda melakukan ini.

  2. Buat fungsi untuk mengevaluasi ekspresi, buat security definerdan ubah pemilik menjadi pengguna yang dibatasi itu.

  3. Memproses ulang ekspresi dan meneruskannya ke fungsi eval () yang Anda buat di atas. Anda dapat melakukan ini di fungsi lain jika perlu,

Perhatikan lagi, ini memiliki implikasi keamanan yang serius.

Sunting: Kode sampel singkat (belum diuji tetapi harus membawa Anda ke sana jika Anda mengikuti dokumen):

CREATE OR REPLACE FUNCTION eval_numeric(text) returns numeric language plpgsql security definer immutable as
$$
declare retval numeric;
begin

execute $e$ SELECT ($1)::numeric$e$ into retval;
return retval;
end;
$$;

ALTER FUNCTION eval_numeric OWNER TO jailed_user;

CREATE OR REPLACE FUNCTION foo(expression text, a numeric, b numeric) returns numeric language sql immutable as $$
select eval(regexp_replace(regexp_replace($1, 'a', $2, 'g'), 'b', '$3', 'g'));
$$; -- can be security invoker, but eval needs to be jailed.
Chris Travers
sumber
"menjadikannya definisi keamanan" benar-benar membingungkan, dapatkah Anda menjelaskan?
jcolebrand
1
PostgreSQL memiliki dua mode keamanan yang fungsinya dapat dijalankan. INVOKER KEAMANAN adalah default. DEFINER KEAMANAN berarti "dijalankan dengan konteks keamanan pemilik fungsi" seperti bit SETUID pada * nix. Untuk membuat definisi keamanan fungsi, Anda dapat menentukan ini dalam deklarasi fungsi ( CREATE FUNCTION foo(text) returns text IMMUTABLE LANGUAGE SQL SECURITY DEFINER AS $$...) atau Anda dapatALTER FUNCTION foo(text) SECURITY DEFINER
Chris Travers
Oh, jadi itu PG PG khusus. Kena kau. Seharusnya digunakan backticks dalam Jawaban ;-)
jcolebrand
@ ChrisTravers saya mengharapkan beberapa kode sampel untuk mengevaluasi rumus yaitu a+bdisimpan dalam kolom jenis teks dalam sebuah tabel maka saya memiliki fungsi foo(a int, b int,formula text)jika mendapat rumus adalah + b bagaimana saya bisa membuat fungsi benar-benar melakukan + b bukannya saya harus memiliki pernyataan kasus yang sangat panjang untuk semua formula yang mungkin dan mengulang kode di semua segmen?
indago
1
@ Indago, saya pikir Anda ingin memecah ini menjadi dua lapisan karena masalah keamanan. Yang pertama adalah lapisan interpolasi. Anda dapat menggunakan regex di PostgreSQL untuk melakukan ini. Di tingkat yang lebih rendah, Anda pada dasarnya menjalankan ini dalam fungsi SQL yang dipenjara. Anda benar-benar perlu memperhatikan keamanan jika Anda ingin melakukan ini, dan Anda juga harus memperhatikan nilai pengembalian. Tanpa mengetahui lebih banyak, sulit untuk melakukan banyak hal dengan kode samople tetapi akan mengubah jawabannya.
Chris Travers
2

Alternatif untuk hanya menyimpan formula dan kemudian mengeksekusinya (yang seperti yang disebutkan Chris, memiliki masalah keamanan ) adalah dengan memiliki tabel terpisah yang disebut formula_stepsyang pada dasarnya akan berisi variabel dan operator dan urutan di mana mereka dieksekusi. Ini akan menjadi pekerjaan yang sedikit lebih, tetapi akan lebih aman. Tabelnya mungkin terlihat seperti ini:

formula_steps
-------------
  formula_step_id
  formula_id (FK, dirujuk oleh tabel agen)
  input_1
  input_2
  operator (juga bisa menjadi ID ke tabel operator yang diizinkan, jika Anda tidak ingin menyimpan simbol operator secara langsung)
  urutan

Pilihan lain adalah menggunakan beberapa pustaka / alat pihak ketiga untuk mengevaluasi ekspresi matematika. Ini akan membuat basis data Anda kurang rentan terhadap injeksi SQL, tetapi sekarang Anda baru saja memindahkan kemungkinan masalah keamanan ke alat eksternal Anda (yang mungkin masih cukup aman).


Opsi terakhir adalah menulis (atau mengunduh) prosedur yang mengevaluasi ekspresi matematika. Ada algoritma yang diketahui untuk masalah ini, jadi tidak sulit untuk menemukan info online.

FrustratedWithFormsDesigner
sumber
1
+1 untuk opsi ketiga. Jika semua input potensial diketahui, hard code pilih masing-masing input dan gantilah (jika diperlukan) ke dalam formula yang disimpan sebagai teks, kemudian gunakan pustaka rutin untuk mengevaluasi aritmatika. Risiko injeksi SQL dihilangkan.
Joel Brown