如何编写Postgres动态SQL语句来计算存储在行中的表达式?

如何编写Postgres动态SQL语句来计算存储在行中的表达式?,sql,postgresql,dynamic-sql,Sql,Postgresql,Dynamic Sql,我正在使用PostgreSQL,我需要编写SQL语句来解决这两个表的以下问题: Table "Transaction" Column | Type | Collation | Nullable | Default ----------------+--------------------------------+---------

我正在使用PostgreSQL,我需要编写SQL语句来解决这两个表的以下问题:

                                  Table "Transaction"
    Column      |              Type              | Collation | Nullable |      Default
----------------+--------------------------------+-----------+----------+-------------------
 id             | text                           |           | not null |
 amount         | integer                        |           | not null |
 merchant_name  | text                           |           | not null |

                                 Table "Rule"
     Column     |              Type              | Collation | Nullable |      Default
----------------+--------------------------------+-----------+----------+-------------------
 id             | text                           |           | not null |
 key            | text                           |           | not null |
 sql_expression | text                           |           | not null |
例如,有两个规则行:

  id  |       key       |       sql_expression
------+-----------------+-----------------------
   1  | amount          | > 100
   2  | merchant_name   | ~* '.*amazon.*'
给定一个事务id(让我们调用匹配的事务行
“this\u Transaction”
),我希望找到所有规则行,其中由“this\u Transaction”构造的条件的每个规则行的键和sql\u表达式值计算为true。在本例中,如果
“本次交易”.amount>100,则需要规则1,如果
“本次交易”.merchant\u name~*'.*亚马逊。*'

我不知道怎么写这个WHERE表达式。下面提供了一个不工作的SQL伪代码

SELECT "Rule".* FROM "Rule",
  (
    SELECT "Transaction".*
    FROM "Transaction"
    WHERE "Transaction".id = $1
  ) AS "this_transaction"
WHERE "this_transaction".("Rule".key) "Rule".sql_expression;

$1 = 'some_transaction_id'
如果有人对如何写WHERE表达式或用其他方式解决问题有任何建议,请让我知道。谢谢你的时间和考虑

另外,在我们的情况下,我们会有许多规则行,这些规则行会被许多用户不断地创建、更新和删除。请建议一个支持此灵活系统的解决方案。谢谢。

这是一个棘手的地形

您可以编写一个集合返回函数,该函数接受事务ID,迭代规则,如果存在具有给定ID和规则表达式的事务,则返回规则

CREATE FUNCTION transaction_rule
                (_transaction transaction.id%TYPE)
                RETURNS SETOF rule
AS
$$
DECLARE
  _exists boolean;
  _rule rule;
BEGIN
  FOR _rule IN (SELECT *
                       FROM rule) LOOP
    EXECUTE format(concat('SELECT EXISTS (SELECT *',
                          '                      FROM transaction',
                          '                      WHERE id = $1',
                          '                      AND %I %s);'),
                   _rule.key,
                   _rule.sql_expression)
            USING _transaction
            INTO _exists;
    IF _exists THEN
      RETURN NEXT _rule;
    END IF;
  END LOOP;  
END;
$$
LANGUAGE plpgsql;
但这可能会表现得很糟糕。它很容易出错,任何可以更改规则的人都可能会滥用它进行SQL注入攻击

如果需要将规则存储在数据库中而不是应用程序中,您可能可以使用事务视图并将规则添加为列。大致如下:

CREATE VIEW transaction_rules
AS
SELECT t.*,
       amount > 100 rule_1,
       merchant_name ~* '.*amazon.*' rule_2
       FROM transaction t;

现在应该很简单:

SELECT "Rule".* FROM "Rule",
  (
    SELECT "Transaction".*
    FROM "Transaction"
    WHERE "Transaction".id = $1
  ) AS "this_transaction"
WHERE eval_bool("this_transaction"::"Transaction", "Rule".key, "Rule".sql_expression);

请注意,第一个值(
data
)应该是注册类型的值。因此,我们需要将子查询结果强制转换为
“Transaction”类型

,这将很难使用语句来实现。更典型的是,您只需要一些固定数量的语句,这些语句涵盖了您的所有预期逻辑。谢谢您的建议。我想使用
CREATE视图
方法,但在本例中,您在CREATE视图中硬编码了2条规则。在我们的情况下,许多规则行可能会被不同的用户不断创建、更新和删除。有没有办法在创建视图中包含所有规则行?@AndyShih:视图的列可能有一些限制。但这应该很高。也许你应该研究一下,看看你是否会超过它。我尝试了你的解决方案,但得到了
错误:无法识别记录数据类型
中的“金额”列,所以我将数据类型更改为
交易
,这就成功了!谢谢大家!@是的,我明白了。子查询似乎存在问题。但是,您可以不更改函数的参数类型而强制转换值:
。。。其中eval_bool(“此事务”):“事务”、“规则”.key、“规则”.sql_表达式)它使函数更通用(可用于任何表)是的,这更好!非常感谢。
SELECT "Rule".* FROM "Rule",
  (
    SELECT "Transaction".*
    FROM "Transaction"
    WHERE "Transaction".id = $1
  ) AS "this_transaction"
WHERE eval_bool("this_transaction"::"Transaction", "Rule".key, "Rule".sql_expression);