将公式存储在表格中并在函数中使用该公式

ind*_*ago 10 postgresql database-design stored-procedures functions postgresql-9.1

我有一个 PostgreSQL 9.1 数据库,其中的一部分处理代理佣金。每个代理都有他/她自己的计算佣金的公式。我有一个函数来生成每个代理应该获得的佣金数量,但是随着代理数量的增加,它变得无法使用。被迫做一些非常长的case语句和重复的代码,这让我的函数变得非常大。

所有公式都有常量变量:

d .. 那个月的工作天数
r .. 获得新节点
l .. 忠诚度得分
s .. 子代理佣金
b .. 基本利率
我 .. 收入增加

公式可以是这样的:

d*b+(l*4+r)+(i/d)+s
Run Code Online (Sandbox Code Playgroud)

每个代理与人力资源部协商支付公式。那么我可以将公式存储在代理表中,然后像一个小函数一样从表中获取公式并将其转换为值并计算数量吗?

Erw*_*ter 6

准备

您的公式如下所示:

d*b+(l*4+r)+(i/d)+s
Run Code Online (Sandbox Code Playgroud)

我会用$n符号替换变量,以便它们可以直接在 plpgsql 中替换为值EXECUTE(见下文):

$1*$5+($3*4+$2)+($6/$1)+$4
Run Code Online (Sandbox Code Playgroud)

您可以额外存储您的原始公式(对于人眼)或使用以下表达式动态生成此表单:

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');
Run Code Online (Sandbox Code Playgroud)

只要确保你的翻译是合理的。正则表达式的一些解释:

\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; 
Run Code Online (Sandbox Code Playgroud)

称呼:

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

返回:

29.6000000000000000
Run Code Online (Sandbox Code Playgroud)

要点

  • 该函数采用 6 个值参数并formula text作为第 7 个参数。我把公式放在最后,所以我们可以用$1 .. $6代替$2 .. $7。只是为了可读性。
    我为我认为合适的值分配了数据类型。分配适当的类型(以实现基本的健全性检查)或只是让它们全部numeric

  • 使用USING子句传递动态执行的值。这避免了来回转换,并使一切变得更简单、更安全、更快捷。

  • 我使用OUT参数是因为它更优雅,并且可以使语法更短更清晰。RETURN不需要final ,自动返回 OUT 参数的值。

  • 考虑@Chris 的安全讲座和手册中的“安全地编写 SECURITY DEFINER 函数”一章。在我的设计中,单点注射是配方本身。

  • 您可以使用某些参数的默认值来进一步简化调用。


Chr*_*ers 5

请仔细阅读有关安全注意事项的内容。本质上,您正在尝试在您的函数中注入任意 SQL。因此,您需要在具有高度限制权限的用户下运行此程序。

  1. 创建一个用户并撤销其所有权限。不要在执行此操作时在同一数据库中向 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.
Run Code Online (Sandbox Code Playgroud)