Oracle Property Graph 让关系型数据也能用图的方式查询。本文用一组转账数据,走一遍建表、建图、单跳与多跳查询的完整流程,并整理 ORA-40954: unknown label 的常见原因。
01 | 准备测试表与数据
建议在空白 schema 中执行。若对象已存在,先手工删除或更换名称。
sql
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 个账户:
sql
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');
构造一条连续转账链,并增加一条捷径:
graph LR AA --> BB B --> CC C --> DD D --> EE E --> FF %% 分支/快捷路径 B -->|快捷路径| E
sql
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 是边标签。
sql
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 | 单跳查询
sql
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 跳查询
sql
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 可能出现多次,因为查询匹配的是"路径",而不只是去重后的终点。如果只想看可达账户,可以在外层去重:
sql
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]->,都会出现标签无法用于当前位置的问题。
快速定位是哪一个标签有问题
先单独测试顶点标签:
sql
SELECT *
FROM GRAPH_TABLE(
money_graph
MATCH (a IS ACCOUNT)
COLUMNS (a.account_id AS account_id)
);
再单独测试边标签:
sql
SELECT *
FROM GRAPH_TABLE(
money_graph
MATCH (a)-[t IS TRANSFER]->(b)
COLUMNS (t.transfer_id AS transfer_id)
);
第一个失败说明 ACCOUNT 标签有问题;第二个失败说明 TRANSFER 标签有问题。
关注我,和AI一起成长~