理解Oracle Property Graph:以账户转账示例完成图特性最小测试

Oracle Property Graph 让关系型数据也能用图的方式查询。本文用一组转账数据,走一遍建表、建图、单跳与多跳查询的完整流程,并整理 ORA-40954: unknown label 的常见原因。

01 | 准备测试表与数据

建议在空白 schema 中执行。若对象已存在,先手工删除或更换名称。

复制代码

|-----------------------------------------------------------------|
| CREATE TABLE money_accounts ( |
| account_id NUMBER CONSTRAINT money_accounts_pk PRIMARY KEY, |
| account_name VARCHAR2(50) NOT NULL |
| ); |
| |
| CREATE TABLE money_transfers ( |
| transfer_id NUMBER CONSTRAINT money_transfers_pk PRIMARY KEY, |
| from_account_id NUMBER NOT NULL, |
| to_account_id NUMBER NOT NULL, |
| amount NUMBER(12,2), |
| transfer_time TIMESTAMP, |
| CONSTRAINT money_transfer_from_fk |
| FOREIGN KEY (from_account_id) |
| REFERENCES money_accounts(account_id), |
| CONSTRAINT money_transfer_to_fk |
| FOREIGN KEY (to_account_id) |
| REFERENCES money_accounts(account_id) |
| ); |

插入 6 个账户:

复制代码

|-------------------------------------------------|
| INSERT INTO money_accounts VALUES (1, '账户A'); |
| INSERT INTO money_accounts VALUES (2, '账户B'); |
| INSERT INTO money_accounts VALUES (3, '账户C'); |
| INSERT INTO money_accounts VALUES (4, '账户D'); |
| INSERT INTO money_accounts VALUES (5, '账户E'); |
| INSERT INTO money_accounts VALUES (6, '账户F'); |

构造一条连续转账链,并增加一条捷径:

快捷路径

A

B

C

D

E

F

复制代码

|--------------------------------------------------------------|
| INSERT INTO money_transfers |
| VALUES (101, 1, 2, 1000, TIMESTAMP '2026-09-01 09:00:00'); |
| INSERT INTO money_transfers |
| VALUES (102, 2, 3, 900, TIMESTAMP '2026-09-01 10:00:00'); |
| INSERT INTO money_transfers |
| VALUES (103, 3, 4, 800, TIMESTAMP '2026-09-01 11:00:00'); |
| INSERT INTO money_transfers |
| VALUES (104, 4, 5, 700, TIMESTAMP '2026-09-01 12:00:00'); |
| INSERT INTO money_transfers |
| VALUES (105, 5, 6, 600, TIMESTAMP '2026-09-01 13:00:00'); |
| -- B 直接转到 E,形成另一条路径 |
| INSERT INTO money_transfers |
| VALUES (106, 2, 5, 500, TIMESTAMP '2026-09-01 14:00:00'); |
| COMMIT; |

02 | 创建属性图

显式声明两个标签:ACCOUNT 是顶点标签,TRANSFER 是边标签。

复制代码

|------------------------------------------|
| CREATE PROPERTY GRAPH money_graph |
| VERTEX TABLES ( |
| money_accounts |
| KEY (account_id) |
| LABEL account |
| PROPERTIES ( |
| account_id, |
| account_name |
| ) |
| ) |
| EDGE TABLES ( |
| money_transfers |
| KEY (transfer_id) |
| SOURCE KEY (from_account_id) |
| REFERENCES money_accounts (account_id) |
| DESTINATION KEY (to_account_id) |
| REFERENCES money_accounts (account_id) |
| LABEL transfer |
| PROPERTIES ( |
| transfer_id, |
| amount, |
| transfer_time |
| ) |
| ); |

未加双引号的标识符会被 Oracle 规范化为大写,因此 LABEL account 可以通过 IS ACCOUNT 匹配。

03 | 单跳查询

复制代码

|--------------------------------------------------------|
| SELECT * |
| FROM GRAPH_TABLE( |
| money_graph |
| MATCH (a IS ACCOUNT)-[t IS TRANSFER]->(b IS ACCOUNT) |
| COLUMNS ( |
| a.account_id AS source_account, |
| a.account_name AS source_name, |
| t.amount AS amount, |
| b.account_id AS target_account, |
| b.account_name AS target_name |
| ) |
| ) |
| ORDER BY source_account, target_account; |

它应返回 6 条直接转账关系。

04 | 1~5 跳查询

复制代码

|-----------------------------------------------------------|
| SELECT * |
| FROM GRAPH_TABLE( |
| money_graph |
| MATCH (a IS ACCOUNT)-[IS TRANSFER]->{1,5}(b IS ACCOUNT) |
| COLUMNS ( |
| a.account_id AS source_account, |
| a.account_name AS source_name, |
| b.account_id AS target_account, |
| b.account_name AS target_name |
| ) |
| ) |
| WHERE source_account = 1 |
| ORDER BY target_account; |

从账户 A 出发,可以匹配到:

  • A→B:1 跳
  • A→B→C:2 跳
  • A→B→C→D:3 跳
  • A→B→C→D→E:4 跳
  • A→B→C→D→E→F:5 跳
  • A→B→E:另一条 2 跳路径
  • A→B→E→F:另一条 3 跳路径

因此 E 和 F 可能出现多次,因为查询匹配的是"路径",而不只是去重后的终点。如果只想看可达账户,可以在外层去重:

复制代码

|-----------------------------------------------------------|
| SELECT DISTINCT target_account, target_name |
| FROM GRAPH_TABLE( |
| money_graph |
| MATCH (a IS ACCOUNT)-[IS TRANSFER]->{1,5}(b IS ACCOUNT) |
| COLUMNS ( |
| a.account_id AS source_account, |
| b.account_id AS target_account, |
| b.account_name AS target_name |
| ) |
| ) |
| WHERE source_account = 1 |
| ORDER BY target_account; |

05 | ORA-40954 常见原因

① 把表名当成标签名

图定义是 LABEL account,正确匹配是 (a IS ACCOUNT),而不是 (a IS MONEY_ACCOUNTS)IS 后面填的是 Label,不是底层表名。

② 没有显式声明预期标签

如果图定义没有 LABEL account,却查询 (a IS ACCOUNT),Oracle 就可能找不到该标签。建议在教学和生产 DDL 中显式声明 Label。

③ 双引号导致大小写敏感

若创建时写 LABEL "Account",标签名就是大小写敏感的 Account,查询必须写 (a IS "Account")(a IS ACCOUNT) 无法匹配。最佳实践是标签不使用双引号。

④ 标签存在,但元素类型用错

TRANSFER 只定义在边上,却写成 (a IS TRANSFER);或 ACCOUNT 只定义在顶点上,却写成 -[e IS ACCOUNT]->,都会出现标签无法用于当前位置的问题。

快速定位是哪一个标签有问题

先单独测试顶点标签:

复制代码

|----------------------------------------|
| SELECT * |
| FROM GRAPH_TABLE( |
| money_graph |
| MATCH (a IS ACCOUNT) |
| COLUMNS (a.account_id AS account_id) |
| ); |

再单独测试边标签:

复制代码

|------------------------------------------|
| SELECT * |
| FROM GRAPH_TABLE( |
| money_graph |
| MATCH (a)-[t IS TRANSFER]->(b) |
| COLUMNS (t.transfer_id AS transfer_id) |
| ); |

第一个失败说明 ACCOUNT 标签有问题;第二个失败说明 TRANSFER 标签有问题。

相关推荐
AlfredZhao5 小时前
Oracle Property Graph 结合 In-Memory 的性能验证
graph·in-memory
AlfredZhao6 天前
理解Oracle Property Graph:以账户转账示例完成图特性最小测试
graph
微小冷24 天前
Python图论库NetworkX初步
开发语言·python·图论·graph·networkx·digraph
小马过河R1 个月前
Graph Engineering 深度解析:模型越强,越需要给它画好“地图”
人工智能·langchain·graph·ai工程化·harness·驾驭工程
uncle_ll2 个月前
LangGraph 深度解析:用图结构构建下一代智能代理与多智能体系统
langchain·llm·agent·graph·langgraph
李燚2 个月前
ReAct Agent 源码拆解:Eino 如何把 Graph 变成 Agent(第64篇-E50)
ai·agent·react·graph·multi-agent·aiagent·eino
李燚2 个月前
Graph 执行引擎源码:Eino 的节点调度是怎么工作的(第56篇-E42)
golang·ai编程·graph·agent框架·eino
中间件XL2 个月前
ai-agent框架spring ai/alibaba 原理源码分析(五)graph III 图执行
graph·ai agent·spring ai·springaialibaba
李燚3 个月前
Graph 编排:不只是 ReAct 的通用 DAG
agent·workflow·graph·ai-agent·dag