匹配计算语句¶
匹配计算语句用于在临时图上执行迭代计算。它允许匹配特定的图模式,然后在匹配的子图上执行计算或更新操作。
Note
匹配计算不同于普通 MATCH 查询:普通MATCH用于检索数据并通常通过RETURN返回结果;匹配计算使用PER PATH或PER NODE对匹配到的路径或点执行计算,并且只能在临时图上运行。
前提条件¶
- 已创建临时图。更多信息,请参见 CREATE TEMPORARY GRAPH。
- 已导入数据。更多信息,请参见 IMPORT 。
语法¶
- match_compute_statement
- per_block
- active_set_operation
match_compute_statement ::=
'MATCH' graph_pattern (( per_block active_set_operation? ) | active_set_operation)
per_block ::=
( ('PER PATH' '{' procedure_body '}') ( 'PER NODE' '(' node_variable ')' '{' procedure_body '}' )* )
| ('PER NODE' '(' node_variable ')' '{' procedure_body '}')+
active_set_operation ::=
'FINALLY' '{' ('SET' active_set_variable ('=' | '|=') node_variable)+ '}'
参数¶
graph_pattern¶
匹配计算语句中的图模式。仅支持 0 跳(例如(a))和 1 跳(例如(a)-[e]->(b))的图模式。不支持match_mode和path_mode_prefix。
在graph_pattern中,可以指定WHERE子句将待遍历的点的范围限制为活动集变量中的点。
例如,(a)-[e]->(b) WHERE a IN active_set_variable AND a.id > 10匹配模式(a)-[e]->(b),其中a必须是活动集变量active_set_variable中的点且其id大于 10。
注意在每个图模式中,不管有多少个点变量,只允许一个活动集变量对一个点变量施加限制。例如,以下图模式均无效:
(a) WHERE a IN active_set_1 AND a IN active_set_2(a)-[e]->(b) WHERE a IN active_set_1 AND b IN active_set_2(a)-[e]->(b) WHERE a IN active_set_1 AND b IN active_set_1
per_block¶
per_block子句,用于为MATCH graph_pattern结果集中的每条路径或每个点执行一组语句。per_block子句有两种类型:PER PATH和PER NODE。
对于在match_compute_statement外部定义的非聚合值变量(比如原始值变量和活动集变量),不能在per_block内部执行赋值(=)操作更新它们的值。例如,以下代码无效:
'''
// `#match_compute_movie`为基于`movie`示例数据集创建的临时图
// 详情见下文示例。
USE #match_compute_movie {
VALUE x = 1
MATCH (a)
PER NODE (a) {
SET x = 2 // 无效
}
}
'''
但是,对于全局聚合值变量,可以在per_block内部执行聚合(+=)操作,但不能执行赋值操作(=)为它们赋值。例如:
'''
USE #match_compute_movie {
VALUE global_sum SumAgg<INT> = 0
SET @global_sum = 1 // 有效
MATCH (a)
PER NODE (a) {
SET @global_sum += 1 // 有效
SET @global_sum = 1 // 无效
}
}
'''
对于点绑定聚合值变量,可以在PER PATH子句中执行聚合操作,但不能执行赋值操作。然而,可以在PER NODE子句中为每个点执行聚合和赋值操作。例如:
'''
USE #match_compute_movie {
NODE VALUE node_sum SumAgg<INT> = 0
MATCH (a)-[e]->(b)
PER PATH {
SET a.@node_sum += 1 // 有效
SET a.@node_sum = 0 // 无效
}
PER NODE (a) {
SET a.@node_sum += 0 // 有效
SET a.@node_sum = 1 // 有效
}
}
'''
根据上述描述,下表总结了不同类型的变量在per_block中的使用限制:
| 变量类型 | 在PER PATH中的限制 |
在PER NODE中的限制 |
|---|---|---|
在match_compute_statement外部定义的非聚合值变量 |
❌ 禁止赋值操作 (=)❌ 禁止聚合操作 ( +=) |
❌ 禁止赋值操作 (=)❌ 禁止聚合操作 ( +=) |
| 全局聚合值变量 | ✅ 允许聚合操作 (+=)❌ 禁止赋值语句 ( =) |
✅ 允许聚合操作 (+=)❌ 禁止赋值语句 ( =) |
| 点绑定聚合值变量 | ✅ 允许聚合操作 (+=)❌ 禁止赋值语句 ( =) |
✅ 允许聚合操作 (+=)✅ 允许赋值操作 ( =) |
PER PATH¶
该子句为MATCH graph_pattern结果集中的每条路径执行过程体(procedure_body)内语句。
注意:PER PATH不能和 0 跳图模式一起使用。
PER NODE¶
该子句为MATCH graph_pattern结果集中每个由node_variable指定的点执行过程体(procedure_body)内语句。
此时,通常使用点绑定聚合值变量来存储和操作每个点特定的值。
procedure_body¶
要执行的一组语句。注意,在per_block的过程体中只允许以下操作和语句:
在PER NODE和PER PATH块内,不支持以下语句:
RETURN、LIMIT、ORDER BY、GROUP BY等查询级子句。MATCH、子查询、局部TABLE定义等通用查询语句。
node_variable¶
引用点的变量值表达式。node_variable必须在graph_pattern中已定义。
active_set_operation¶
FINALLY子句,其使用node_variable指定的点更新active_set_variable中的点。该子句可动态更新一个或多个活动集以便进一步计算。
active_set_operation有两种更新模式:=和|=:
=模式将active_set_variable当前持有的点集替换为node_variable指定的点集。|=模式将node_variable指定的点集添加到active_set_variable当前持有的点集中。
active_set_variable¶
存储一组点的活动集变量。更多信息,请参见活动集变量。
匹配计算语句中聚合值更新的可见性规则¶
全局聚合值¶
在PER PATH和PER NODE中的聚合操作(SET @global_agg += ...)只有在整个匹配计算语句执行完成后才可见。例如:
'''
USE #match_compute_movie {
VALUE global_sum SumAgg<INT> = 0
MATCH (a@User{id:1})-[e]->(b)
PER PATH {
SET @global_sum += 1
LOG_INFO("@global_sum == ",@global_sum) // 此处 @global_sum 仍为 0
}
LOG_INFO("@global_sum == ",@global_sum) // 此处可见 <per path clause> 的更新
}
'''
点绑定聚合值¶
PER PATH中的聚合操作(SET node.@agg += ...)在该路径子句完成后才可见。PER NODE中的赋值(=)和聚合(+=)操作都立即可见。
常见误区:在同一 PER PATH 中聚合后立即 EXPORT
由于PER PATH的聚合要等子句完成后才生效,如果在同一个PER PATH内先SET node.@agg += ...再EXPORT node.@agg,导出的将是聚合前的旧值。
-- ❌ 错误:score 导出的是聚合前的旧值(始终为初始值)
PER PATH {
SET v.@score += e.rate
EXPORT v.id, v.@score INTO result -- @score 此时仍为旧值
}
-- ✅ 正确:拆成两个 PER PATH,先聚合再导出
MATCH ... PER PATH { SET v.@score += e.rate }
MATCH ... PER PATH { EXPORT v.id, v.@score INTO result }
例如:
'''
USE #match_compute_movie {
NODE VALUE node_sum SumAgg<INT> = 0
MATCH (a@User{id:1})-[e]->(b)
PER PATH {
SET a.@node_sum += 1
LOG_INFO("a.@node_sum == ",a.@node_sum) // 此处 a.@node_sum 仍为 0
}
PER NODE (a) {
LOG_INFO("a.@node_sum == ",a.@node_sum) // 此处可见`PER PATH`的更新
SET a.@node_sum += 1 // (1)
LOG_INFO("(1) a.@node_sum == ",a.@node_sum) // 更新 (1) 已生效
SET a.@node_sum = 0 // (2)
LOG_INFO("(2) a.@node_sum == ",a.@node_sum) // 更新 (2) 已生效
}
}
'''
示例¶
以下示例以 Jaccard 相似度计算演示匹配计算语句。先基于示例数据集中的movie图创建临时图:
CREATE TEMPORARY GRAPH IF NOT EXISTS #match_compute_movie AS COPY OF movie
然后执行:
'''
USE #match_compute_movie {
/* ====== 声明 ====== */
VALUE source_id = 1 // 用于相似度计算的源用户 ID
VALUE source_edges SumAgg<INT64> = 0 // 源用户喜欢的电影
VALUE topk_similarity TopKAgg<5, similarity DOUBLE DESC, id INT DESC> // 前 5 位相似用户
NODE VALUE target_edges SumAgg<INT64> = 0 // 每个用户喜欢的电影
NODE VALUE intersection SumAgg<INT64> = 0 // 与源用户的共同喜欢的电影
VALUE v_set ACTIVE_SET // 源用户喜欢的电影
/* ====== 步骤 1:统计用户电影偏好 ====== */
// 统计所有用户喜欢的电影
MATCH (u@User)-[e:Watch]->(m:Movie)
PER PATH {
SET u.@target_edges += 1 // 每个用户的电影计数递增
}
/* ====== 步骤 2:收集源用户数据 ====== */
// 处理源用户的偏好
MATCH (u@User)-[:Watch]->(m:Movie)
WHERE u.id = source_id // 筛选源用户
PER NODE (u) {
SET @source_edges += u.@target_edges // 存储源用户的电影计数
}
FINALLY { SET v_set = m } // 用源用户的电影填充活动集
/* ====== 步骤 3:计算用户相似度 ====== */
// 查找源用户与其他用户的共同喜欢电影
MATCH (m:Movie)<-[:Watch]-(t@User)
WHERE t.id <> source_id AND m IN v_set // 排除源用户,使用源用户的电影作为起点
PER PATH {
SET t.@intersection += 1 // 统计共同电影
}
PER NODE (t) {
// 计算源用户和其他用户的共同喜欢电影的并集
VALUE union_size = t.@target_edges + @source_edges - t.@intersection
IF union_size <> 0 THEN {
VALUE similarity = CAST(t.@intersection AS DOUBLE) / union_size
// 存储到前 k 聚合器
SET @topk_similarity += RECORD{
similarity: similarity,
id: t.id
}
}
}
/* ====== 步骤 4:返回结果 ====== */
// 输出前 5 位相似用户
FOR r IN @topk_similarity
RETURN r.id AS user_id,
ROUND(r.similarity, 5) AS jaccard_similarity
}
'''
返回:
+---------+--------------------+
| user_id | jaccard_similarity |
+---------+--------------------+
| 409 | 0.55556 |
| 70 | 0.55556 |
| 282 | 0.54545 |
| 122 | 0.54545 |
| 39 | 0.54545 |
+---------+--------------------+