如何编写 Postgres Dynamic SQL 语句来评估存储在行中的表达式?
How to write Postgres Dynamic SQL statement to evaluate expressions stored in rows?
我正在使用 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 |
例如,有 2 个规则行:
id | key | sql_expression
------+-----------------+-----------------------
1 | amount | > 100
2 | merchant_name | ~* '.*amazon.*'
给定一个事务 ID(我们称匹配的事务行 "this_transaction"
),我想找到所有规则行,其中条件由“this_transaction”和每个规则行的键和 sql_expression 值评估为真。在这个例子中,如果 "this_transaction".amount > 100
,我想要规则 1,如果 "this_transaction".merchant_name ~* '.*amazon.*'
,我想要规则 2
我不知道这个WHERE表达式怎么写。下面提供了一个无效的 SQL 伪代码。
SELECT "Rule".* FROM "Rule",
(
SELECT "Transaction".*
FROM "Transaction"
WHERE "Transaction".id =
) AS "this_transaction"
WHERE "this_transaction".("Rule".key) "Rule".sql_expression;
= 'some_transaction_id'
如果有人对如何编写 WHERE 表达式或以其他方式解决问题有任何建议,请告诉我。感谢您的时间和考虑。
P.S。在我们的情况下,我们将有许多规则行,这些行会被许多用户不断地创建、更新和删除。请提出一个支持这个灵活系统的解决方案。谢谢。
这是一个棘手的地形。
您可以编写一个集返回函数,它接受一个交易 ID,如果存在具有给定 ID 和规则表达式的交易,则迭代规则和returns规则。
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 = ',
' 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;
create or replace function eval_bool(data record, key text, expr text)
returns bool stable strict language plpgsql
as $$
declare
result bool;
begin
execute 'select .' || key || ' ' || expr into result using data;
return result;
end $$;
-- test:
select relname
from pg_class as t
where eval_bool(t, 'relname', 'like ''%class%''');
┌───────────────────────────────────┐
│ relname │
├───────────────────────────────────┤
│ pg_class_oid_index │
│ pg_class_relname_nsp_index │
│ pg_class_tblspc_relfilenode_index │
│ pg_opclass_am_name_nsp_index │
│ pg_opclass_oid_index │
│ pg_class │
│ pg_opclass │
└───────────────────────────────────┘
现在应该很简单:
SELECT "Rule".* FROM "Rule",
(
SELECT "Transaction".*
FROM "Transaction"
WHERE "Transaction".id =
) AS "this_transaction"
WHERE eval_bool("this_transaction"::"Transaction", "Rule".key, "Rule".sql_expression);
请注意,第一个值 (data
) 应该是已注册类型的值。所以我们需要将子查询结果转换为 "Transaction" type
.
我正在使用 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 |
例如,有 2 个规则行:
id | key | sql_expression
------+-----------------+-----------------------
1 | amount | > 100
2 | merchant_name | ~* '.*amazon.*'
给定一个事务 ID(我们称匹配的事务行 "this_transaction"
),我想找到所有规则行,其中条件由“this_transaction”和每个规则行的键和 sql_expression 值评估为真。在这个例子中,如果 "this_transaction".amount > 100
,我想要规则 1,如果 "this_transaction".merchant_name ~* '.*amazon.*'
我不知道这个WHERE表达式怎么写。下面提供了一个无效的 SQL 伪代码。
SELECT "Rule".* FROM "Rule",
(
SELECT "Transaction".*
FROM "Transaction"
WHERE "Transaction".id =
) AS "this_transaction"
WHERE "this_transaction".("Rule".key) "Rule".sql_expression;
= 'some_transaction_id'
如果有人对如何编写 WHERE 表达式或以其他方式解决问题有任何建议,请告诉我。感谢您的时间和考虑。
P.S。在我们的情况下,我们将有许多规则行,这些行会被许多用户不断地创建、更新和删除。请提出一个支持这个灵活系统的解决方案。谢谢。
这是一个棘手的地形。
您可以编写一个集返回函数,它接受一个交易 ID,如果存在具有给定 ID 和规则表达式的交易,则迭代规则和returns规则。
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 = ',
' 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;
create or replace function eval_bool(data record, key text, expr text)
returns bool stable strict language plpgsql
as $$
declare
result bool;
begin
execute 'select .' || key || ' ' || expr into result using data;
return result;
end $$;
-- test:
select relname
from pg_class as t
where eval_bool(t, 'relname', 'like ''%class%''');
┌───────────────────────────────────┐
│ relname │
├───────────────────────────────────┤
│ pg_class_oid_index │
│ pg_class_relname_nsp_index │
│ pg_class_tblspc_relfilenode_index │
│ pg_opclass_am_name_nsp_index │
│ pg_opclass_oid_index │
│ pg_class │
│ pg_opclass │
└───────────────────────────────────┘
现在应该很简单:
SELECT "Rule".* FROM "Rule",
(
SELECT "Transaction".*
FROM "Transaction"
WHERE "Transaction".id =
) AS "this_transaction"
WHERE eval_bool("this_transaction"::"Transaction", "Rule".key, "Rule".sql_expression);
请注意,第一个值 (data
) 应该是已注册类型的值。所以我们需要将子查询结果转换为 "Transaction" type
.