简体   繁体   English

PostgreSQL:查询的动态 WHERE 条件

[英]PostgreSQL: Dynamic WHERE criteria for query

I'm working on a project where I need to create dynamic query filter rules within a table, to be used to filter data in another table.我正在做一个项目,我需要在一个表中创建动态查询过滤规则,用于过滤另一个表中的数据。 I am in need of some assistance to see whether this is viable, and if so, how the query might be formed.我需要一些帮助,看看这是否可行,如果可行,如何形成查询。

       people                                rules
+----+--------+-----+     +----+--------+-------+----------+----------+
| id |  name  | age |     | id |  type  | field | operator | criteria |
+----+--------+-----+     +----+--------+-------+----------+----------+
|  1 | Emma   |  34 |     |  1 | people | age   | >        | 30       |
|  2 | Larry  |  25 |     +----+--------+-------+----------+----------+
|  3 | Alice  |  22 |
|  4 | Thomas |  31 |
+----+--------+-----+

In this example, I want to query the "people" table by the using the criteria in the "rules" table.在这个例子中,我想通过使用“规则”表中的条件来查询“人”表。 I read about Common Table Expressions in PostgreSQL and thought I could potentially use it here, but thus far I've not been successful.我在 PostgreSQL 中阅读了有关 Common Table Expressions 的信息,并认为我可以在这里使用它,但到目前为止我还没有成功。 Here's what I've tried so far:这是我到目前为止所尝试的:

WITH    cte_rule AS (SELECT field FROM rules WHERE id = 1)
SELECT  *,
FROM    people
WHERE   (SELECT field FROM cte_rule) < 30;

This results in the following error returned from the database:这会导致从数据库返回以下错误:

Query 1 ERROR: ERROR:  operator does not exist: character varying < integer
LINE 4: WHERE (SELECT field FROM cte_rule) < 30;
                                           ^
HINT:  No operator matches the given name and argument types. You might need to add explicit type casts.

I'm trying to hard-code some of the values for now, and if successful with making part of the statement dynamic, then further extend it to be completely dynamic.我现在正在尝试对一些值进行硬编码,如果成功使部分语句动态化,则进一步将其扩展为完全动态的。

Any guidance would be very much appreciated.任何指导将不胜感激。

You will need dynamic SQL for this.为此,您将需要动态 SQL。

As both, the values and column names are dynamic and also the context in which they are applied this is a bit tricky.列名都是动态的,而且应用它们的上下文也有点棘手。

Something along the lines:类似的东西:

create or replace function evaluate_rule(p_row record, p_rule_id int)
  returns boolean
as
$$
declare
  l_expression text;
  l_rule rules;
  l_result boolean;
  l_values jsonb;
  l_type text;
begin
  
  select *
    into l_rule
  from rules
  where id = p_rule_id
    and type = pg_typeof(p_row)::text;
  
  if not found then 
    return false;
  end if;
  
  l_values := to_jsonb(p_row);
  l_type := jsonb_typeof(l_values -> l_rule.field);
  
  if l_type = 'number' then 
    l_expression := format('select %s %s %s', l_values ->> l_rule.field, l_rule.operator, l_rule.criteria);
  else 
    l_expression := format('select %L %s %L', l_values ->> l_rule.field, l_rule.operator, l_rule.criteria);
  end if;
  
  execute l_expression 
    into l_result;
    
  return l_result;
end;
$$
language plpgsql
stable;

Unfortunately, the conversion to JSONB in order to dynamically pick column values from the passed row does make you lose the data type information.不幸的是,为了从传递的行中动态选择列值而转换为 JSONB 确实会使您丢失数据类型信息。 But I can't really think of a different way to dynamically create an expression where the column names and values are not known beforehand.但是我真的想不出一种不同的方法来动态创建一个表达式,其中列名和值是事先不知道的。

If a rule specifies a column that doesn't exist, this would result in an expression like NULL > 42 which will evaluate as false.如果规则指定了一个不存在的列,这将导致像NULL > 42这样的表达式,其评估结果为 false。

The function assumes that there is exactly one rule for the combination of "type" and "id". function 假设“type”和“id”的组合只有一个规则。 If you don't care about whether the requested rule matches the passed table, you can remove the condition and type = pg_typeof(p_row)::text如果你不关心请求的规则是否与传递的表匹配,你可以去掉条件and type = pg_typeof(p_row)::text

Given this sample data:鉴于此示例数据:

create table rules (id int,   type  text, field text, operator text, criteria text);
insert into rules values 
(1, 'people', 'age', '>', '30'), 
(2, 'people', 'name', '=', 'Alice');


create table people (id int, name text, age int);
insert into people
values
  (1, 'Emma', 34),
  (2, 'Larry', 25),
  (3, 'Alice', 22),
  (4, 'Thomas', 31)
;

You can use it like this:你可以像这样使用它:

select p.*
from people p
where evaluate_rule(p, 1)

id | name   | age
---+--------+----
 1 | Emma   |  34
 4 | Thomas |  31

Of with the second rule:与第二条规则:

select p.*
from people p
where evaluate_rule(p, 2)

id | name  | age
---+-------+----
 3 | Alice |  22

Online example 在线示例


Edit编辑

It just occurred to me, that with Postgres 12 or later, this can actually be done without dynamic SQL or a helper function:我突然想到,使用 Postgres 12 或更高版本,这实际上可以在没有动态 SQL 或助手 function 的情况下完成:

with cte_rule as (
  select (concat('$.', field, ' ', operator, ' ', criteria))::jsonpath as rule
  from rules
  where id = 1
)
select p.*
from people p
where to_jsonb(p) @@ (select rule from cte_rule);

The operator needs to comply with the rules for SQL/JSON path language , eg you need to use == instead of = and double quotes for strings.运算符需要遵守SQL/JSON 路径语言的规则,例如,您需要使用==而不是= ,并为字符串使用双引号。 So my second example rule would need to be:所以我的第二个示例规则需要是:

insert into rules values 
(2, 'people', 'name', '==', '"Alice"');

Online example 在线示例


If using JSON path is an alternative you might want to think about storing the final expression in a single column rather than column name, operator and value in three different columns.如果使用 JSON 路径是另一种选择,您可能需要考虑将最终表达式存储在单个列中,而不是将列名、运算符和值存储在三个不同的列中。

Storing the expression in a jsonpath column has the benefit, that the syntax will be parsed and validated when you try to store the expression.将表达式存储在jsonpath列中的好处是,当您尝试存储表达式时,语法将被解析和验证。

create table rules (id int, type  text, expression jsonpath);
insert into rules values 
(1, 'people', '$.age > 30'), 
(2, 'people', '$.name == "Alice"');

Then you can use:然后你可以使用:

select p.*
from people p
where to_jsonb(p) @@ (select expression from rules where id = 1)

Online example 在线示例

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM