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');
构造一条连续转账链,并增加一条捷径:
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 标签有问题。
关注我,和AI一起成长~