OQL:我们设计的 Ontology 查询语言(语法篇)
Tags: #OQL #QueryLanguage #BNF #GraphTraversal #MetricExpansion #智策平台
“系列:S3 数据基座 · 第 6 篇 | 难度:高级 | 阅读时间:20 分钟
OQL:我们设计的 Ontology 查询语言(语法篇)
Tags: #OQL #QueryLanguage #BNF #GraphTraversal #MetricExpansion #智策平台
#TL;DR
coomia-dip 平台为三表模型设计了专属查询语言——OQL(Ontology Query Language)。OQL 不是 SQL 的简单封装,而是从 Ontology 语义出发,融合实体查询、图遍历、指标展开和时间旅行的领域特定语言。本文完整呈现 OQL 的 BNF 语法定义、核心语句设计(FETCH / TRAVERSE / AGGREGATE / TIMELINE)、指标展开机制和图遍历语法。通过与 SQL、GraphQL、Cypher 的对比,阐明 OQL 在 Ontology 场景下的独特优势。
#1. 为什么需要专属查询语言
#1.1 SQL 的局限性
SQL 查询 Ontology 数据的痛点:
问题 1:类型无关查询需要大量 JSON 函数
SQL: SELECT JSON_EXTRACT(properties, '$.name') FROM entity_common
WHERE entity_type = 'Person'
AND JSON_EXTRACT(properties, '$.age') > 30
OQL: FETCH Person WHERE age > 30
问题 2:图遍历需要多层 JOIN
SQL: SELECT ... FROM entity_common ec1
JOIN entity_edge ee1 ON ...
JOIN entity_common ec2 ON ...
JOIN entity_edge ee2 ON ...
JOIN entity_common ec3 ON ... -- 3 跳需要 5 个 JOIN
OQL: TRAVERSE Person -> WorksAt -> Company -> LocatedIn -> City
问题 3:指标展开需要嵌套子查询
SQL: SELECT *, (SELECT COUNT(*) FROM entity_edge
WHERE source_id = ec.entity_id AND edge_type = 'Manages')
AS direct_reports FROM entity_common ec ...
OQL: FETCH Person WITH METRIC direct_reports
问题 4:时间旅行需要版本化 JOIN
SQL: 极其复杂的 Iceberg 时间旅行语法
OQL: FETCH Person AT TIME '2024-01-01T00:00:00Z'
#1.2 OQL 设计目标
OQL 设计目标矩阵:
┌─────────────────────┬──────────────────────────────────┐
│ 目标 │ 实现方式 │
├─────────────────────┼──────────────────────────────────┤
│ Ontology 原生 │ 以 Entity Type 为查询入口 │
│ │ 属性直接引用,无需 JSON 函数 │
├─────────────────────┼──────────────────────────────────┤
│ 图遍历内建 │ TRAVERSE 语句 + 路径表达式 │
│ │ 支持 1-N 跳遍历 │
├─────────────────────┼──────────────────────────────────┤
│ 指标展开 │ WITH METRIC 子句自动展开 │
│ │ 指标定义与查询分离 │
├─────────────────────┼──────────────────────────────────┤
│ 时间旅行 │ AT TIME / AT BRANCH 子句 │
│ │ 透明映射到 Iceberg 快照 │
├─────────────────────┼──────────────────────────────────┤
│ 可翻译到 SQL │ 最终编译为 Doris SQL 执行 │
│ │ 保持 OLAP 引擎的全部优化能力 │
└─────────────────────┴──────────────────────────────────┘
#2. BNF 语法定义
#2.1 顶层语法
(* OQL 顶层语法 - EBNF 表示 *)
oql_statement
::= fetch_statement
| traverse_statement
| aggregate_statement
| timeline_statement
| diff_statement
| mutate_statement
;
(* === FETCH 语句:实体查询 === *)
fetch_statement
::= 'FETCH' entity_type_ref
[ 'WHERE' predicate_expr ]
[ 'WITH' with_clause ( ',' with_clause )* ]
[ 'AT' temporal_clause ]
[ 'IN' world_ref ]
[ 'ORDER' 'BY' order_expr ( ',' order_expr )* ]
[ 'LIMIT' integer [ 'OFFSET' integer ] ]
;
(* === TRAVERSE 语句:图遍历 === *)
traverse_statement
::= 'TRAVERSE' entity_ref
traverse_path+
[ 'WHERE' predicate_expr ]
[ 'WITH' with_clause ( ',' with_clause )* ]
[ 'DEPTH' integer [ '..' integer ] ]
[ 'LIMIT' integer ]
;
(* === AGGREGATE 语句:聚合分析 === *)
aggregate_statement
::= 'AGGREGATE' entity_type_ref
'BY' group_expr ( ',' group_expr )*
'COMPUTE' agg_expr ( ',' agg_expr )*
[ 'WHERE' predicate_expr ]
[ 'HAVING' predicate_expr ]
[ 'AT' temporal_clause ]
[ 'IN' world_ref ]
[ 'ORDER' 'BY' order_expr ( ',' order_expr )* ]
[ 'LIMIT' integer ]
;
(* === TIMELINE 语句:事件时间线 === *)
timeline_statement
::= 'TIMELINE' entity_ref
[ 'EVENTS' event_type_list ]
[ 'FROM' datetime_expr 'TO' datetime_expr ]
[ 'WHERE' predicate_expr ]
[ 'ORDER' 'BY' 'event_time' ( 'ASC' | 'DESC' ) ]
[ 'LIMIT' integer ]
;
(* === DIFF 语句:分支对比 === *)
diff_statement
::= 'DIFF' entity_type_ref
'BETWEEN' branch_ref 'AND' branch_ref
[ 'WHERE' predicate_expr ]
;
#2.2 表达式语法
(* === 谓词表达式 === *)
predicate_expr
::= comparison_expr
| predicate_expr 'AND' predicate_expr
| predicate_expr 'OR' predicate_expr
| 'NOT' predicate_expr
| '(' predicate_expr ')'
| exists_expr
| contains_expr
| search_expr
| similar_expr
;
comparison_expr
::= property_ref comparator value_expr
;
comparator
::= '=' | '!=' | '>' | '>=' | '<' | '<='
| 'IN' | 'NOT' 'IN'
| 'LIKE' | 'NOT' 'LIKE'
| 'IS' 'NULL' | 'IS' 'NOT' 'NULL'
| 'BETWEEN' value_expr 'AND' value_expr
;
(* 全文搜索 *)
search_expr
::= 'SEARCH' '(' property_ref ',' string_literal ')'
;
(* 语义相似度 *)
similar_expr
::= 'SIMILAR' '(' string_literal ',' float_literal ')'
| 'SIMILAR' '(' vector_literal ',' float_literal ')'
;
(* 存在性检查 *)
exists_expr
::= 'EXISTS' '(' traverse_path [ 'WHERE' predicate_expr ] ')'
;
(* 数组包含 *)
contains_expr
::= property_ref 'CONTAINS' value_expr
| property_ref 'CONTAINS' 'ANY' '(' value_list ')'
| property_ref 'CONTAINS' 'ALL' '(' value_list ')'
;
(* === 属性引用 === *)
property_ref
::= identifier (* 顶层属性 *)
| identifier '.' identifier (* 嵌套属性 *)
| identifier '[' integer ']' (* 数组索引 *)
;
(* === 图遍历路径 === *)
traverse_path
::= '->' edge_type_ref [ '(' predicate_expr ')' ] '->' entity_type_ref
| '<-' edge_type_ref [ '(' predicate_expr ')' ] '<-' entity_type_ref
| '--' edge_type_ref [ '(' predicate_expr ')' ] '--' entity_type_ref
;
(* === 指标和计算属性 === *)
with_clause
::= 'METRIC' metric_name_list
| 'COMPUTED' computed_prop_list
| 'EDGES' edge_summary_list
;
metric_name_list
::= metric_name ( ',' metric_name )*
;
(* === 聚合函数 === *)
agg_expr
::= agg_function '(' property_ref ')' [ 'AS' alias ]
;
agg_function
::= 'COUNT' | 'SUM' | 'AVG' | 'MIN' | 'MAX'
| 'PERCENTILE' | 'STDDEV' | 'VARIANCE'
| 'COUNT_DISTINCT' | 'TOPN'
| 'TIME_BUCKET'
;
(* === 时间子句 === *)
temporal_clause
::= 'TIME' datetime_expr
| 'BRANCH' branch_name
| 'SNAPSHOT' snapshot_id
;
(* === World 引用 === *)
world_ref
::= 'WORLD' world_name
;
#3. 核心语句详解
#3.1 FETCH:实体查询
FETCH 语句是 OQL 最基础的查询,类似 SQL 的 SELECT:
┌───────────────────────────────────────────────────────────┐
│ FETCH 语句结构 │
│ │
│ FETCH <EntityType> │
│ WHERE <conditions> ← 过滤条件 │
│ WITH METRIC <metrics> ← 指标展开 │
│ AT TIME <timestamp> ← 时间旅行 │
│ IN WORLD <world> ← 指定 World │
│ ORDER BY <fields> ← 排序 │
│ LIMIT <n> OFFSET <m> ← 分页 │
└───────────────────────────────────────────────────────────┘
示例集合:
-- 基础查询:查找所有 30 岁以上的工程师
FETCH Person
WHERE department = 'Engineering' AND age > 30
ORDER BY hire_date DESC
LIMIT 20;
-- 嵌套属性查询
FETCH Person
WHERE address.city = 'Shanghai' AND skills CONTAINS 'Python';
-- 全文搜索 + 语义搜索组合
FETCH Document
WHERE SEARCH(content, '风险评估报告')
AND SIMILAR('分析信用风险的机器学习模型', 0.8)
ORDER BY _score DESC
LIMIT 10;
-- 带指标展开的查询
FETCH Person
WHERE department = 'Engineering'
WITH METRIC direct_reports, total_projects, avg_performance_score
ORDER BY direct_reports DESC;
-- 时间旅行查询
FETCH Person
WHERE department = 'Engineering'
AT TIME '2023-06-01T00:00:00Z'
IN WORLD 'production';
-- 存在性条件
FETCH Company
WHERE EXISTS(-> Employs -> Person WHERE role = 'CTO')
AND industry = 'Technology';
#3.2 TRAVERSE:图遍历
TRAVERSE 语句执行图遍历,替代多表 JOIN:
┌───────────────────────────────────────────────────────────┐
│ TRAVERSE 语句结构 │
│ │
│ TRAVERSE <start_entity> │
│ -> <EdgeType> -> <EntityType> ← 正向遍历 │
│ <- <EdgeType> <- <EntityType> ← 反向遍历 │
│ -- <EdgeType> -- <EntityType> ← 双向遍历 │
│ WHERE <conditions> ← 路径条件 │
│ DEPTH 1..3 ← 遍历深度 │
│ LIMIT <n> ← 结果限制 │
└───────────────────────────────────────────────────────────┘
示例集合:
-- 一跳遍历:某人在哪家公司工作
TRAVERSE Person('person-001')
-> WorksAt -> Company;
-- 两跳遍历:某公司所有员工的项目
TRAVERSE Company('company-001')
<- WorksAt <- Person
-> WorksOn -> Project;
-- 带条件的遍历:只看高级员工
TRAVERSE Company('company-001')
<- WorksAt(weight > 0.8) <- Person
WHERE level = 'Senior'
-> Manages -> Person;
-- 可变深度遍历:组织架构树
TRAVERSE Person('ceo-001')
-> Manages -> Person
DEPTH 1..5;
-- 双向遍历:找关联实体
TRAVERSE Device('device-001')
-- ConnectedTo -- Device
-- LocatedAt -- Location
DEPTH 1..3
LIMIT 50;
-- 路径模式匹配:供应链追溯
TRAVERSE Product('prod-001')
<- SuppliedBy <- Supplier
<- ManufacturedBy <- Factory
-> LocatedIn -> Region
WHERE Region.country = 'China';
#3.3 AGGREGATE:聚合分析
-- 按部门统计人数和平均年龄
AGGREGATE Person
BY department
COMPUTE COUNT(*) AS headcount,
AVG(age) AS avg_age,
MAX(salary) AS max_salary
WHERE is_active = true
ORDER BY headcount DESC;
-- 按时间桶统计事件
AGGREGATE Event
BY TIME_BUCKET(event_time, '1 HOUR') AS hour_bucket,
event_type
COMPUTE COUNT(*) AS event_count,
COUNT_DISTINCT(entity_id) AS unique_entities
WHERE severity IN ('error', 'critical')
AND event_time BETWEEN '2024-06-01' AND '2024-06-30'
ORDER BY hour_bucket ASC;
-- 嵌套聚合:TopN
AGGREGATE Transaction
BY source_account
COMPUTE SUM(amount) AS total_amount,
COUNT(*) AS tx_count,
TOPN(target_account, 5) AS top_targets
WHERE transaction_date >= '2024-01-01'
HAVING total_amount > 1000000
ORDER BY total_amount DESC
LIMIT 100;
#3.4 TIMELINE:事件时间线
-- 实体的完整时间线
TIMELINE Person('person-001')
FROM '2024-01-01' TO '2024-06-30'
ORDER BY event_time DESC
LIMIT 100;
-- 只看特定事件类型
TIMELINE Device('device-001')
EVENTS Alert, StatusChange, Measurement
FROM '2024-06-01' TO '2024-06-30'
WHERE severity IN ('error', 'critical')
ORDER BY event_time DESC;
-- 带关联追踪的时间线
TIMELINE Order('order-001')
EVENTS StatusChange
WHERE correlation_id = 'trace-abc-123'
ORDER BY event_time ASC;
#4. 指标展开机制
#4.1 指标定义
# metrics-registry/person-metrics.yaml
metrics:
- name: direct_reports
entity_type: Person
description: "直接下属人数"
type: count
definition:
edge_type: Manages
direction: outbound
count: targets
- name: total_projects
entity_type: Person
description: "参与项目总数"
type: count
definition:
edge_type: WorksOn
direction: outbound
count: targets
- name: avg_performance_score
entity_type: Person
description: "平均绩效得分"
type: aggregate
definition:
event_type: PerformanceReview
field: payload.score
function: avg
time_range: last_12_months
- name: alert_frequency
entity_type: Device
description: "告警频率(每天)"
type: rate
definition:
event_type: Alert
time_range: last_30_days
unit: per_day
#4.2 展开过程
指标展开的编译过程:
输入 OQL:
FETCH Person WHERE department = 'Eng'
WITH METRIC direct_reports, total_projects
编译步骤 1:查找指标定义
direct_reports → COUNT(edge WHERE Manages outbound)
total_projects → COUNT(edge WHERE WorksOn outbound)
编译步骤 2:生成子查询
┌─────────────────────────────────────────────┐
│ SELECT ec.*, │
│ (SELECT COUNT(*) │
│ FROM entity_edge ee │
│ WHERE ee.source_id = ec.entity_id │
│ AND ee.edge_type = 'Manages' │
│ AND ee.is_deleted = FALSE │
│ ) AS direct_reports, │
│ (SELECT COUNT(*) │
│ FROM entity_edge ee │
│ WHERE ee.source_id = ec.entity_id │
│ AND ee.edge_type = 'WorksOn' │
│ AND ee.is_deleted = FALSE │
│ ) AS total_projects │
│ FROM entity_common ec │
│ WHERE ec.entity_type = 'Person' │
│ AND JSON_EXTRACT(ec.properties, │
│ '$.department') = 'Eng' │
└─────────────────────────────────────────────┘
编译步骤 3:优化
- 子查询 → 左连接聚合(如果记录数多)
- 检查是否有预计算的物化视图
- 检查 computed_props 缓存
#5. 与其他查询语言对比
#5.1 对比矩阵
OQL vs 其他查询语言对比:
┌──────────────┬──────────┬──────────┬──────────┬──────────┐
│ 特性 │ OQL │ SQL │ Cypher │ GraphQL │
├──────────────┼──────────┼──────────┼──────────┼──────────┤
│ 实体类型感知 │ 原生 │ 手动 │ 标签 │ Schema │
│ 图遍历 │ TRAVERSE │ 多JOIN │ 原生 │ 嵌套 │
│ 聚合分析 │ AGGREGATE│ 原生 │ 弱 │ 弱 │
│ 全文搜索 │ SEARCH │ 扩展 │ 插件 │ 无 │
│ 向量搜索 │ SIMILAR │ 扩展 │ 无 │ 无 │
│ 时间旅行 │ AT TIME │ 无 │ 无 │ 无 │
│ 分支对比 │ DIFF │ 无 │ 无 │ 无 │
│ 指标展开 │ METRIC │ 子查询 │ 无 │ 无 │
│ 事件时间线 │ TIMELINE │ 手动 │ 路径 │ 无 │
│ 翻译到 SQL │ 是 │ N/A │ 否 │ 自定义 │
│ OLAP 优化 │ 继承 │ 原生 │ 弱 │ 弱 │
└──────────────┴──────────┴──────────┴──────────┴──────────┘
#5.2 等价查询对比
同一查询在四种语言中的写法:
需求:"找到张三管理的所有人,以及他们参与的项目"
OQL (4 行):
TRAVERSE Person('zhangsan')
-> Manages -> Person
-> WorksOn -> Project;
SQL (15 行):
SELECT p2.*, proj.*
FROM entity_common p1
JOIN entity_edge e1 ON e1.source_id = p1.entity_id
AND e1.edge_type = 'Manages'
JOIN entity_common p2 ON p2.entity_id = e1.target_id
JOIN entity_edge e2 ON e2.source_id = p2.entity_id
AND e2.edge_type = 'WorksOn'
JOIN entity_common proj ON proj.entity_id = e2.target_id
WHERE p1.entity_id = 'zhangsan'
AND p1.world_id = 'main'
AND e1.world_id = 'main'
AND p2.world_id = 'main'
AND e2.world_id = 'main'
AND proj.world_id = 'main';
Cypher (6 行):
MATCH (p1:Person {id: 'zhangsan'})
-[:Manages]->(p2:Person)
-[:WorksOn]->(proj:Project)
RETURN p2, proj;
GraphQL (12 行):
query {
person(id: "zhangsan") {
manages {
name
worksOn {
name
status
}
}
}
}
#6. 语法扩展:高级特性
#6.1 管道操作符
-- 管道操作符 |> 允许链式处理
FETCH Person
WHERE department = 'Engineering'
|> TRAVERSE -> WorksOn -> Project
|> AGGREGATE BY Project.status
COMPUTE COUNT(*) AS project_count;
-- 等价于嵌套查询,但可读性更好
#6.2 变量绑定
-- 使用 LET 绑定中间结果
LET engineers = FETCH Person WHERE department = 'Engineering';
LET high_performers = engineers WHERE performance_score > 90;
TRAVERSE high_performers
-> Manages -> Person
WITH METRIC direct_reports;
#6.3 模式匹配
-- 模式匹配:找出"三角形"关系
MATCH PATTERN
(a:Person) -> Knows -> (b:Person),
(b:Person) -> Knows -> (c:Person),
(c:Person) -> Knows -> (a:Person)
WHERE a.department != b.department
AND b.department != c.department
LIMIT 100;
#7. 类型系统
#7.1 值类型
OQL 值类型系统:
基本类型:
├── String "hello"
├── Integer 42
├── Float 3.14
├── Boolean true / false
├── DateTime '2024-06-15T10:30:00Z'
├── Date '2024-06-15'
├── Duration '30 DAYS' / '2 HOURS'
└── Null NULL
复合类型:
├── Array [1, 2, 3] / ['a', 'b']
├── Map {key: value}
├── Vector VECTOR([0.1, 0.2, ...])
└── GeoPoint GEO(31.2, 121.5)
引用类型:
├── EntityRef Person('id-001')
├── EdgeRef Edge('edge-001')
├── WorldRef WORLD('main')
└── BranchRef BRANCH('feature-x')
#7.2 函数库
-- 字符串函数
FETCH Person WHERE LOWER(name) LIKE '%zhang%';
FETCH Person WHERE LENGTH(description) > 100;
-- 数学函数
FETCH Device WHERE ABS(temperature - 25.0) < 2.0;
-- 日期函数
FETCH Person WHERE YEAR(hire_date) = 2023;
FETCH Event WHERE event_time > NOW() - INTERVAL '7 DAYS';
-- 数组函数
FETCH Person WHERE ARRAY_LENGTH(skills) >= 3;
FETCH Person WHERE ARRAY_OVERLAP(skills, ['Python', 'Java']);
-- 地理函数
FETCH Store WHERE GEO_DISTANCE(location, GEO(31.2, 121.5)) < 5000;
#8. 错误处理与提示
#8.1 语法错误提示
OQL 友好的错误提示:
输入: FETCH Perso WHERE age > 30
错误: Unknown entity type 'Perso'. Did you mean 'Person'?
FETCH Perso WHERE age > 30
^^^^^
Available types: Person, Company, Device, Document
输入: FETCH Person WERE age > 30
错误: Expected 'WHERE', found 'WERE'. Did you mean 'WHERE'?
FETCH Person WERE age > 30
^^^^
输入: FETCH Person WHERE ages > 30
错误: Unknown property 'ages' on type 'Person'. Did you mean 'age'?
Available properties: age (Int), name (String), email (String)...
#Key Takeaways
-
OQL 将 Ontology 语义提升为查询语言的一等公民:实体类型、关系遍历、事件时间线都有专属语法,避免了 SQL 中大量的 JSON 函数和多表 JOIN。
-
四大核心语句覆盖所有查询场景:FETCH(实体查询)、TRAVERSE(图遍历)、AGGREGATE(聚合分析)、TIMELINE(事件时间线),加上 DIFF(分支对比)覆盖了 Ontology 数据的全部访问模式。
-
指标展开机制实现查询与指标定义的解耦:WITH METRIC 子句自动展开预定义指标,用户无需关心底层的子查询和聚合逻辑。
-
OQL 最终编译为 SQL 执行:保留了 Doris OLAP 引擎的全部优化能力(物化视图、向量化执行、列式存储),不牺牲性能换取易用性。
-
渐进式复杂度设计:简单查询极其简洁(
FETCH Person WHERE age > 30),复杂查询通过组合语法元素实现,学习曲线平滑。
#Next Article
下一篇 S3-07《OQL 解析器实现:从文本到 AST》 将深入 OQL 解析器的实现细节,包括词法分析器(Lexer)、递归下降解析器(Parser)、抽象语法树(AST)构建、查询优化器和执行计划生成。
Tags: #OQL #OntologyQueryLanguage #BNF #GraphTraversal #MetricExpansion #FETCH #TRAVERSE #AGGREGATE #TIMELINE #智策平台 #coomia-dip #数据基座