Skip to content
文档
开发文档
数据查询
通用查询条件概念

通用查询条件

1、中缀表达式

中缀表达式是最常见的表达式书写方式,特点是运算符位于两个操作数的中间。在日常数学计算和逻辑表达中非常普遍。例如, 1 < 2 就是一个非常简单的中缀表达式。在计算机领域,几乎所有的高级编程语言,都使用中缀表达式作为算术和逻辑运算的基础,如 amount > 100

由于中缀表达式的直观易懂,符合人类的思维习惯,并且可以组合出复杂的逻辑表达式,因此,OpenMeta 采用中缀表达式来构造数据查询条件。

中缀表达式非常灵活,既能够表达简单的二元关系,如 a > b,又能构建嵌套结构的复杂查询条件,如 (a > b AND c < d) OR (e = f AND g != h)。使用中缀表达式时有一个明确的规则,必须使用括号来明确表达不同条件组合的优先级。

其它运算表达式:

(1)波兰表达式(前缀表达式):是一种将运算符置于操作数之前的表示方法,计算顺序从右向左,易于计算机解析和执行,但组合条件的可读性比较差。

(2)逆波兰表达式(后缀表达式):与波兰表达式相反,将运算符置于操作数之后,计算顺序从左向右。同样,易于计算机执行计算过程,但是组合条件可读性比较差。

2、Operator 比较操作符

序号操作符代码枚举含义备注
1=EQUAL等于
2!=NOT_EQUAL不等于
3>GREATER_THAN大于
4>=GREATER_THAN_OR_EQUAL大于等于
5<LESS_THAN小于
6<=LESS_THAN_OR_EQUAL小于等于
7CONTAINSCONTAINS包含文本搜索,不支持索引
8NOT CONTAINSNOT_CONTAINS不包含文本搜索,不支持索引
9START WITHSTART_WITH以……开始文本搜索,支持索引
10NOT START WITHNOT_START_WITH不以…开始文本搜索,支持索引
11ININ在……中
12NOT INNOT_IN不在……中
13BETWEENBETWEEN在 … 和 … 之间闭区间
14NOT BETWEENNOT_BETWEEN不在 … 和 … 之间开区间
15IS SETIS_SET已设置过值
16IS NOT SETIS_NOT_SET未设置过值
17PARENT OFPARENT_OF查询所有上级允许多值,支持索引
18CHILD OFCHILD_OF查询所有下级允许多值,支持索引

2.1 CONTAINSNOT CONTAINS

文本搜索,CONTAINSNOT CONTAINS 分别对应 SQL 查询 LIKENOT LIKE模糊匹配。这两个操作符都不支持索引。

2.2 START WITHNOT START WITH

START WITHNOT START WITH 仅用于字符串的搜索匹配,分别对应 SQL查询 LIKENOT LIKE前缀匹配模式。这两个操作符是为了应对大数据量的直接搜索,可以支持数据库索引,在不考虑将数据存储到专用的搜索引擎时使用。

2.3 INNOT IN

支持多值匹配,Value 是数据集合形式。如 a IN [1, 2, 3]

2.4 BETWEENNOT BETWEEN

BETWEEN 是闭区间,Value是一个包含两个元素的数据列表,匹配范围包含左右两个元素本身,如 a ≤ field ≤ b

NOT BETWEEN 是开区间,匹配范围不包含左右两个元素本身,如 field < a OR field > b

2.5 IS SETIS NOT SET

IS SET 表示该字段已经设置过值,等同于 SQL 条件 IS NOT NULL

IS NOT SET 表示该字段未设置过值,等同于 SQL 条件 IS NULL

2.6 PARENT OFCHILD OF

在树形数据结构中,经常需要递归查询指定节点的所有父级节点,或指定节点的所有子节点,如上下级组织关系等。而且,这里的指定节点,根据业务场景的不同,可能是一个节点,也可能是多个节点。

OpenMeta 为了提高此类场景的查询性能,采用了 ID 路径查询 的机制,实现通过一条 SQL 查询出所有父节点或所有子节点。

(1)需要进行上下级查询的业务模型,如部门,增加 idPath 字段

(2)转为 id IN split(idPath, ’/’)

(3)等同于 START_WITH ,转为左匹配 (id_path LIKE ‘idPath%‘)

3、Filters 通用筛选条件

在企业级业务系统中,经常出现复合的筛选条件,如专业用户在客户端通过自定义搜索条件筛选数据,以及服务端根据业务属性组合条件控制数据访问范围,这些场景都需要系统支持动态组合的筛选条件。

OpenMeta 通过定义 Filters 对象,封装常见的数据转换和比较函数,在代码中传递和操作筛选条件。

3.1 Filters 结构化查询

结构化定义的 Filters 分为 Unit、Tree、Empty 三种类型,分别对应最小查询单元、查询结构树、空条件。所有的查询条件使用中缀表达式表示。支持客户端以单个字符串或字符串列表两种形式传递参数,如果是字符串格式,服务端程序将通过 JSON 反序列化字符串为 Filters 对象,如果是字符串列表,则直接调用 Filters 解析函数。

(1)最小查询单元

Filter的最小查询单元对象为 FilterUnit,结构为 [field, operator, value] ,即 [字段名,操作符,比较值],如查询名称等于 Tom 的用户: ["name", "=", "Tom"]

FilterUnit 的操作符 operator 对大小写不敏感。具体支持的操作符清单,参考 Operator 比较操作符 章节。

(2)查询结构树

查询结构树,也即多个查询单元的嵌套与组合。如 [A OR B] AND [C OR D OR [E AND F] OR G],其中,每个独立的大写字母,都代表一个 [field, operator, value] 结构的最小查询单元 FilterUnit 。示例如下: [["name", "=", "Tom"], "OR", ["code", "=", "A100"]], "AND", ["priority", ">", 1]]

查询结构树支持组合条件的无限嵌套。其中,查询条件之间的逻辑操作符仅接受 ANDOR,对大小写不敏感,未传递逻辑操作符时默认为 AND。如 [["name", "=", "Tom"], ["priority", ">", 1]]  的逻辑操作符默认为 AND

(3)字段间比较

OpenMeta 支持在筛选条件中,进行字段间比较。比如在需要筛选 fieldA > fieldB 数据的场景 ,可以将 FilterUnit: [field, operator, value] 结构的 value属性,替换为保留字段名的占位符 @{fieldName} ,如 ["fieldA", ">", "@{FieldB}"]

3.2 Filters 级联查询

在企业级业务系统中,经常会遇到级联查询的场景,OpenMeta 支持通过链式操作符 . 连接关系型字段,基于关联模型的数据条件,查询主模型的数据。

级联查询的定义格式为 [field1.field2.field3.field4Name, operator, value],除末级字段外,字段链上字段类型只能是OneToOne、ManyToOne、OneToMany、ManyToMany类型。可以在全局配置参数中限定最大级联层级。如 customerId.industryId.name = "互联网" 表示查询互联网行业客户的订单数据。

OpenMeta 的级联查询,支持 Filters 中包含多个级联条件、多级级联等复合查询条件,也即每一个查询单元 FilterUnit 都可以是级联查询。

级联查询是一种高级查询技术,它作为系统支持复杂业务需求的补充手段,允许用户执行多层次的数据关联查询。在OneToOne、ManyToOne级联字段查询场景中,服务端会通过多表 join 查询查询数据,如果遇到高频的多层次级联查询场景,应当从模型结构上进行优化,比如在业务表中增加冗余字段,减少级联查询的层级,提升查询的性能。对于大数据量的关联查询场景,考虑使用ES检索引擎。

3.3 XToMany 查询条件

在 OpenMeta 上下文中,XToMany 代表了 OneToMany、ManyToMany 这两种字段类型,这两种字段类型的数据处理,在很多场景下的具有一定的相似性。

在查询条件中遇到 OneToMany、ManyToMany 字段时,OpenMeta 会先根据 Many 端模型的筛选条件查询数据,将查询结果聚合到主模型的查询条件后,再执行最终的主模型数据查询,也即至少经过两次 SQL 查询。

OneToMany、ManyToMany 字段的查询条件,跟其它字段类型的逻辑处理稍有不同。当筛选条件操作符为肯定式(即不包含 NOT 类型的操作符),任意一条关联模型的数据命中时,则代表命中关联的主模型数据。否定式子查询(操作符为NOT EQUAL, NOT CONTAINS, NOT IN),关联模型的数据都不命中时,才返回关联的主模型数据。

4、Filters 语义化查询

4.1 Filters 语义化查询简介

在客户端查询场景中,专业用户可以通过定义 DSL 查询数据,如 name = "Tom" OR total > 200 此时,需要提供尽可能简化、但又保持灵活性不变的 Filters 定义方法。OpenMeta 除了支持结构化的查询方法,也支持使用语义化的字符串进行查询。

  • 语义化查询,示例1:
name = "Tom" OR code = "A100"

等价于结构化查询:

[
    ["name", "=", "Tom"],
    "OR",
    ["code", "=", "A100"]
]
  • 语义化查询,示例2:
(name = "Tom" OR code = "A100") AND priority > 1

等价于结构化查询

[
    [
        ["name", "=", "Tom"],
        "OR",
        ["code", "=", "A100"]
    ],
    "AND",
    ["priority", ">", 1]
]

在语义化查询中,如果 value 值是字符串类型,仍然需要保留双引号,如果 value 值类型是多个值,需要使用 [] 中括号。

4.2 语义化查询的实现

在语义化查询场景,OpenMeta 使用 ANTLR 语法解析,将用户输入的语义表达式转换成 Filters 对象。其中语法解析规则定义如下:

grammar FilterExpr;
 
expr:   expr AND expr               # AndExpr
    |   expr OR expr                # OrExpr
    |   '(' expr ')'                # ParenExpr
    |   unit                        # UnitExpr
    ;
 
AND:    'AND';
OR:     'OR';
 
unit:   FIELD OPERATOR value        # FilterUnitExpr
    ;
 
value: singleValue                  # SingleValueExpr
     | listValue                    # ListValueExpr
     ;
 
singleValue: NUMBER
           | BOOLEAN
           | QUOTED_STRING
           ;
 
listValue: '[' singleValue (',' singleValue)* ']'
          ;
 
FIELD:  [a-z][a-zA-Z0-9]*;
OPERATOR: '='
        | '!='
        | '>'
        | '>='
        | '<'
        | '<='
        | 'CONTAINS'
        | 'NOT CONTAINS'
        | 'START WITH'
        | 'NOT START WITH'
        | 'IN'
        | 'NOT IN'
        | 'BETWEEN'
        | 'NOT BETWEEN'
        | 'IS SET'
        | 'IS NOT SET'
        | 'PARENT OF'
        | 'CHILD OF';
 
NUMBER: [0-9]+ ('.' [0-9]+)?;
BOOLEAN: 'true' | 'false';
QUOTED_STRING: '"' (~["\\] | '\\' .)* '"';  // Double-quoted string, supports escape characters
 
WS: [ \t\r\n]+ -> skip;                     // Ignore whitespace

语义搜索表达式的 visitor 定义如下:

public class FilterExprVisitorImpl extends FilterExprBaseVisitor<Filters> {
    @Override
    public Filters visitParenExpr(FilterExprParser.ParenExprContext ctx) {
        return visit(ctx.expr());
    }
 
    @Override
    public Filters visitAndExpr(FilterExprParser.AndExprContext ctx) {
        Filters left = visit(ctx.expr(0));
        Filters right = visit(ctx.expr(1));
        return Filters.and(left, right);
    }
 
    @Override
    public Filters visitOrExpr(FilterExprParser.OrExprContext ctx) {
        Filters left = visit(ctx.expr(0));
        Filters right = visit(ctx.expr(1));
        return Filters.or(left, right);
    }
 
    @Override
    public Filters visitUnitExpr(FilterExprParser.UnitExprContext ctx) {
        if (ctx.unit() instanceof FilterExprParser.FilterUnitExprContext unitContext) {
            String field = unitContext.FIELD().getText();
            Operator operator = Operator.of(unitContext.OPERATOR().getText());
            Object value = parseValue(unitContext.value());
            return Filters.of(field, operator, value);
        }
        throw new IllegalArgumentException("Unsupported unit expression: " + ctx.unit().getClass().getName());
    }
 
    private Object parseValue(FilterExprParser.ValueContext ctx) {
        if (ctx instanceof FilterExprParser.SingleValueExprContext singleValueCtx) {
            FilterExprParser.SingleValueContext singleValue = singleValueCtx.singleValue();
            return parseSingleValue(singleValue);
        } else if (ctx instanceof FilterExprParser.ListValueExprContext listValueCtx) {
            List<Object> list = new ArrayList<>();
            if (listValueCtx.listValue() instanceof FilterExprParser.ListValueContext valueListContext) {
                for (FilterExprParser.SingleValueContext valueCtx : valueListContext.singleValue()) {
                    list.add(parseSingleValue(valueCtx)); // Recursively parse each value in the list
                }
            }
            return list;
        }
        throw new IllegalArgumentException("Unsupported value context");
    }
}

4.3 两种方式的性能比较

使用 JMH 进行基准测试,1s 内执行 10,000 次,分别解析如下查询条件:

(1)语义化查询

((name = "Te st" AND code IN ["A01"]) OR version NOT IN [1]) AND priority != 21

(2)结构化场景

[
    [
        [
            ["name", "=", "Te st"],
            "AND",
            ["code", "IN", ["A01"]]
        ],
        "OR",
        ["version", "NOT IN", [1]]
    ],
    "AND",
    ["priority","!=",21]
]

测试结果如下:

Performance

测试结果说明,使用结构化查询解析效率相对较高。因此,OpenMeta 服务端默认使用结构化查询和存储,同时,支持客户端语义化查询。但这并与意味着使用语义化查询效率很低,客户端完全可以从用户体验角度考虑使用语义化查询,或自然语言查询。

4.4 基于 AI 的自然语言查询

语义化查询是一种介于自然语言查询与结构化查询之间的方法。在 AI Agent 应用场景中,AI 能够利用模型元数据将自然语言查询转换为语义化查询,以执行复杂的数据查询。

为什么不直接将自然语言转换为 SQL 语句?在企业级业务系统中,一个查询过程不仅包括用户指定的查询条件,还需考虑用户权限范围、模型的查询约束,以及可能涉及的选项集、用户语言、关联模型数据和动态计算数据等因素。OpenMeta 已经在其语义化查询和结构化查询的底层实现中支持这些场景。

因此,将用户的自然语言转换为系统支持的语义化查询条件,是实现 AI Agent 与业务系统整合的一个有效途径。


Copyright ©2025 OpenMeta