web-dev-qa-db-ja.com

テーブルに数式を保存し、関数でその数式を使用する

PostgreSQL 9.1データベースがあり、その一部がエージェントの手数料を処理しています。各エージェントは、彼らが得る手数料の計算式を自分自身に持っています。エージェントごとのコミッションを生成する機能を持っていますが、エージェント数が増えると使えなくなります。非常に長いケースステートメントと繰り返しコードを実行することを余儀なくされたため、私の機能が非常に大きくなりました。

すべての数式には定数変数があります:

 d ..その月の稼働日数
 r ..獲得した新しいノード数
 l ..ロイヤルティスコア
 s ..サブエージェント手数料
 b。 。基本レート
 i ..獲得した収益

式は次のようになります。

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

各エージェントは、HR部門と支払い式を交渉します。では、式をエージェントテーブルに格納して、テーブルから式を取得して値で変換し、金額を計算するだけの小さな関数のようにできますか?

10
indago

準備する

数式は次のようになります。

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

変数を$n表記に置き換えて、plpgsql EXECUTEの値に直接置き換えることができるようにします(以下を参照)。

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

(人間の目のために)オリジナルの数式を追加で保存するか、次のような式を使用してこのフォームを動的に生成できます。

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');

確認してください、あなたの翻訳は健全です。 regexp式 の説明:

\ m ..単語の最初でのみ一致
\M ..単語の最後でのみ一致

4番目のパラメータ'g' ..グローバルに置換

コア機能

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; 

コール:

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

戻り値:

29.6000000000000000

主なポイント

  • この関数は6つの値のパラメーターとformula textを7番目として受け取ります。数式を最後に置くので、$1 .. $6の代わりに$2 .. $7を使用できます。読みやすさのためだけに。
    私は適切だと思った値にデータ型を割り当てました。適切なタイプを割り当てるか(基本的な健全性チェックを実装するため)、それらをすべてnumericにします。

  • USING句を使用して動的実行の値を渡します。これにより、キャストのやり取りが回避され、すべてがより簡単、安全、高速になります。

  • OUTパラメータを使用します。これは、よりエレガントで、構文が短くて明確になるためです。最後のRETURNは不要です。OUTパラメータの値は自動的に返されます。

  • マニュアルの @ Chrisによるセキュリティに関する講義 と、章 "SECURITY DEFINER関数の安全な書き込み" を検討してください。私の設計では、注入の単一ポイントは式自体です。

  • 一部のパラメーターのデフォルト を使用して、呼び出しをさらに簡略化できます。

6

セキュリティに関する考慮事項については、こちらをよくお読みください。基本的に、関数に任意のSQLを挿入しようとしています。したがって、非常に制限された権限を持つユーザーの下でこれを実行する必要があります。

  1. ユーザーを作成し、ユーザーからすべての権限を取り消します。これを行う場合と同じdbでpublicに権限を付与しないでください。

  2. 式を評価する関数を作成し、security definerそして所有者をその制限されたユーザーに変更します。

  3. 式を前処理してから、上で作成したeval()関数に渡します。必要であれば、別の関数でこれを行うことができます。

繰り返しになりますが、これはセキュリティに重大な影響を及ぼします。

編集:簡単なサンプルコード(テストされていませんが、ドキュメントに従っている場合はそこにアクセスできます):

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.
5
Chris Travers

単に式を格納してから実行する代わりの方法(これは Chrisが言及しているように、セキュリティの問題がありますformula_stepsと呼ばれる別のテーブルを作成することになります。それらが実行される順序。これはもう少し作業になりますが、より安全です。テーブルは次のようになります。

 formula_steps 
 ------------- 
 formula_step_id 
 formula_id(FK、エージェントテーブルによって参照される)
 input_1 
 input_2 
演算子(演算子記号を直接格納したくない場合は、許可された演算子のテーブルへのIDにすることもできます)
シーケンス

別のオプションは、サードパーティのライブラリ/ツールを使用して数式を評価することです。これにより、データベースがSQLインジェクションの影響を受けにくくなりますが、考えられるセキュリティの問題を外部ツールにシフトしました(まだかなり安全かもしれません)。


最後のオプションは、数式を評価するプロシージャを作成(またはダウンロード)することです。この問題には既知のアルゴリズムがあるため、オンラインで情報を見つけるのは難しくありません。