Source: https://dev-docs.yueshu.com.cn/v5.3.2/database-gql-reference/match-compute/ Documentation version: v5.3.2 # 匹配计算语句 匹配计算语句用于在[临时图](https://dev-docs.yueshu.com.cn/v5.3.2/database-gql-reference/ddl/graph-projection/create/)上执行迭代计算。它允许匹配特定的图模式,然后在匹配的子图上执行计算或更新操作。 Note 匹配计算不同于普通 [MATCH](https://dev-docs.yueshu.com.cn/v5.3.2/database-gql-reference/dql/match/) 查询:普通`MATCH`用于检索数据并通常通过`RETURN`返回结果;匹配计算使用`PER PATH`或`PER NODE`对匹配到的路径或点执行计算,并且只能在临时图上运行。 ## 前提条件 - 已创建临时图。更多信息,请参见 [CREATE TEMPORARY GRAPH](https://dev-docs.yueshu.com.cn/v5.3.2/database-gql-reference/ddl/graph-projection/create/)。 - 已导入数据。更多信息,请参见 [IMPORT ](https://dev-docs.yueshu.com.cn/v5.3.2/database-gql-reference/in-out/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` 匹配计算语句中的[图模式](https://dev-docs.yueshu.com.cn/v5.3.2/database-gql-reference/patterns/)。仅支持 0 跳(例如`(a)`)和 1 跳(例如`(a)-[e]->(b)`)的图模式。不支持`match_mode`和`path_mode_prefix`。 在`graph_pattern`中,可以指定`WHERE`子句将待遍历的点的范围限制为[活动集变量](https://dev-docs.yueshu.com.cn/v5.3.2/database-gql-reference/variable-definition/active-set-variable/)中的点。 例如,`(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 // 无效 } } ''' ```` 但是,对于[全局聚合值变量](https://dev-docs.yueshu.com.cn/v5.3.2/database-gql-reference/variable-definition/agg/#_2),可以在`per_block`内部执行聚合(`+=`)操作,但不能执行赋值操作(`=`)为它们赋值。例如: ```` ''' USE #match_compute_movie { VALUE global_sum SumAgg = 0 SET @global_sum = 1 // 有效 MATCH (a) PER NODE (a) { SET @global_sum += 1 // 有效 SET @global_sum = 1 // 无效 } } ''' ```` 对于[点绑定聚合值变量](https://dev-docs.yueshu.com.cn/v5.3.2/database-gql-reference/variable-definition/agg/#_3),可以在`PER PATH`子句中执行聚合操作,但不能执行赋值操作。然而,可以在`PER NODE`子句中为每个点执行聚合和赋值操作。例如: ```` ''' USE #match_compute_movie { NODE VALUE node_sum SumAgg = 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`)内语句。 此时,通常使用[点绑定聚合值变量](https://dev-docs.yueshu.com.cn/v5.3.2/database-gql-reference/variable-definition/agg/#_3)来存储和操作每个点特定的值。 #### `procedure_body` 要执行的一组语句。注意,在`per_block`的过程体中只允许以下操作和语句: - [原始值变量声明](https://dev-docs.yueshu.com.cn/v5.3.2/database-gql-reference/variable-definition/value-variable/) - [变量操作](https://dev-docs.yueshu.com.cn/v5.3.2/database-gql-reference/set/) - [控制流语句](https://dev-docs.yueshu.com.cn/v5.3.2/database-gql-reference/control-flow/) - [EXPORT 语句](https://dev-docs.yueshu.com.cn/v5.3.2/database-gql-reference/in-out/export/) - [日志语句](https://dev-docs.yueshu.com.cn/v5.3.2/database-gql-reference/log/) 在`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` 存储一组点的活动集变量。更多信息,请参见[活动集变量](https://dev-docs.yueshu.com.cn/v5.3.2/database-gql-reference/variable-definition/active-set-variable/)。 ## 匹配计算语句中聚合值更新的可见性规则 ### 全局聚合值 在`PER PATH`和`PER NODE`中的聚合操作(`SET @global_agg += ...`)只有在整个匹配计算语句执行完成后才可见。例如: ```` ''' USE #match_compute_movie { VALUE global_sum SumAgg = 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`中的聚合操作(`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 = 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 = 0 // 源用户喜欢的电影 VALUE topk_similarity TopKAgg<5, similarity DOUBLE DESC, id INT DESC> // 前 5 位相似用户 NODE VALUE target_edges SumAgg = 0 // 每个用户喜欢的电影 NODE VALUE intersection SumAgg = 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 | +---------+--------------------+ ````