Source: https://dev-docs.nebula-graph.io/v5.3.2/database-gql-reference/match-compute/ Documentation version: v5.3.2 # Match compute statement The match compute statement performs iterative computations on [temporary graphs](https://dev-docs.nebula-graph.io/v5.3.2/database-gql-reference/ddl/graph-projection/create/). It lets you match specific graph patterns and then execute computations or updates on the matched subgraphs. Note Match computation differs from an ordinary [MATCH](https://dev-docs.nebula-graph.io/v5.3.2/database-gql-reference/dql/match/) query. An ordinary `MATCH` retrieves data and usually returns results with `RETURN`. Match computation uses `PER PATH` or `PER NODE` to compute over matched paths or nodes and can run only on temporary graphs. ## Prerequisites A temporary graph is created. For more information, see [CREATE TEMPORARY GRAPH](https://dev-docs.nebula-graph.io/v5.3.2/database-gql-reference/ddl/graph-projection/create/). Data has been imported. For more information, see [IMPORT](https://dev-docs.nebula-graph.io/v5.3.2/database-gql-reference/in-out/import/). ## Syntax diagramsource 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)+ '}' ```` ## Parameters ### `graph_pattern` The [graph pattern](https://dev-docs.nebula-graph.io/v5.3.2/database-gql-reference/patterns/) in match compute statements. Only 0-hop (e.g., `(a)`) and 1-hop (e.g., `(a)-[e]->(b)`) graph patterns are supported. `match_mode` and `path_mode_prefix` are not supported. Within `graph_pattern`, you can specify a `WHERE` clause to restrict the nodes to be traversed to those held by an [active set variable](https://dev-docs.nebula-graph.io/v5.3.2/database-gql-reference/variable-definition/active-set-variable/). For example, `(a)-[e]->(b) WHERE a IN active_set_variable AND a.id > 10` matches the pattern `(a)-[e]->(b)` where `a` is in the set of nodes held by the active set variable `active_set_variable` and has an `id` greater than 10. Note that in each graph pattern, only one restriction imposed by one active set variable is allowed regardless of the number of node variables. For example, the following graph patterns are invalid: - `(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` The `per_block` clause that executes a set of statements for each path or node in the result set of `MATCH graph_pattern`. The `per_block` clause has two types: `PER PATH` and `PER NODE`. For non-aggregator value variables (such as primitive value variables and active set variables) that are defined outside `match_compute_statement`, you cannot perform assignment operations (`=`) to update their values inside `per_block`. For example, the following code does not work: ```` ''' // `#match_compute_movie` is a temporary graph // created based on the `movie` dataset // See Examples section for more details USE #match_compute_movie { VALUE x = 1 MATCH (a) PER NODE (a) { SET x = 2 // Invalid } } ''' ```` However, for [global aggregator value variables](https://dev-docs.nebula-graph.io/v5.3.2/database-gql-reference/variable-definition/agg/#global_aggregator_value_variables), you can perform aggregation operations (`+=`) but not assignment operations (`=`) to update their values inside `per_block`. For example: ```` ''' USE #match_compute_movie { VALUE global_sum SumAgg = 0 SET @global_sum = 1 // Valid MATCH (a) PER NODE (a) { SET @global_sum += 1 // Valid SET @global_sum = 1 // Invalid } } ''' ```` For [node-attached aggregator value variables](https://dev-docs.nebula-graph.io/v5.3.2/database-gql-reference/variable-definition/agg/#node-attached_aggregator_value_variables), you can perform aggregation operations but not assignment operations inside the `PER PATH` clause. However, you can perform both aggregation and assignment operations for each node inside the `PER NODE` clause. For example: ```` ''' USE #match_compute_movie { NODE VALUE node_sum SumAgg = 0 MATCH (a)-[e]->(b) PER PATH { SET a.@node_sum += 1 // Valid SET a.@node_sum = 0 // Invalid } PER NODE (a) { SET a.@node_sum += 0 // Valid SET a.@node_sum = 1 // Valid } } ''' ```` Based on the above description, the following table summarizes the restrictions on the use of different types of variables in `per_block`: | Variable type | Restriction in `PER PATH` | Restriction in `PER NODE` | Non-aggregator value variables defined outside `match_compute_statement` | ❌ Prohibit assignment operations (`=`) ❌ Prohibit aggregation operations (`+=`) | ❌ Prohibit assignment operations (`=`) ❌ Prohibit aggregation operations (`+=`) | Global aggregator value variables | ✅ Allow aggregation operations (`+=`) ❌ Prohibit assignment operations (`=`) | ✅ Allow aggregation operations (`+=`) ❌ Prohibit assignment operations (`=`) | Node-attached aggregator value variables | ✅ Allow aggregation operations (`+=`) ❌ Prohibit assignment operations (`=`) | ✅ Allow aggregation operations (`+=`) ✅ Allow assignment operations (`=`) []() #### `PER PATH` This clause specifies the statements within the procedure body (`procedure_body`) are executed for each path in the result set of `MATCH graph_pattern`. Note that `PER PATH` cannot be used with 0-hop graph patterns. []() #### `PER NODE` This clause specifies the statements within the procedure body (`procedure_body`) are executed for each node specified by `node_variable` in the result set of `MATCH graph_pattern`. In this case, [node-attached aggregator value variables](https://dev-docs.nebula-graph.io/v5.3.2/database-gql-reference/variable-definition/agg/#node-attached_aggregator_value_variables) are commonly used to store and manipulate values specific to each node. #### `procedure_body` A set of statements to be executed. Note that only the following statements and operations are allowed in the procedure body of a `per_block`: - [Primitive value declarations](https://dev-docs.nebula-graph.io/v5.3.2/database-gql-reference/variable-definition/value-variable/) - [Variable manipulations](https://dev-docs.nebula-graph.io/v5.3.2/database-gql-reference/set/) - [Control flow statements](https://dev-docs.nebula-graph.io/v5.3.2/database-gql-reference/control-flow/) - [EXPORT statements](https://dev-docs.nebula-graph.io/v5.3.2/database-gql-reference/in-out/export/) - [Logging statements](https://dev-docs.nebula-graph.io/v5.3.2/database-gql-reference/log/) For more information, see [Procedure body](https://dev-docs.nebula-graph.io/v5.3.2/database-gql-reference/procedures/create/#procedure_body). The following statements are not supported inside `PER NODE` and `PER PATH` blocks: - Query-level clauses such as `RETURN`, `LIMIT`, `ORDER BY`, and `GROUP BY` - General query statements such as `MATCH`, subqueries, and local `TABLE` definitions ### `node_variable` A variable value expression that evaluates to a reference of a node. `node_variable` must already be defined in `graph_pattern`. ### `active_set_operation` The `FINALLY` clause that updates `active_set_variable` with the nodes specified by `node_variable`. This clause allows you to dynamically update one or more active sets for further computations. `active_set_operation` has two update modes: `=` and `|=`: - The `=` mode replaces the current set of nodes held by `active_set_variable` with the set of nodes specified by `node_variable`. - The `|=` mode adds the set of nodes specified by `node_variable` to the current set of nodes held by `active_set_variable`. #### `active_set_variable` An active set variable that holds a set of nodes. For more information, see [Active set variable](https://dev-docs.nebula-graph.io/v5.3.2/database-gql-reference/variable-definition/active-set-variable/). ## Visibility rules for aggregator updates in match compute statements ### Global aggregator value Aggregations (`SET @global_agg += ...`) in both `PER PATH` and `PER NODE` become visible only after the entire match compute statement completes execution. For example: ```` ''' 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 is still 0 here } LOG_INFO("@global_sum == ",@global_sum) // Updates from are visible here } ''' ```` ### Node-attached aggregator value - Aggregations (`SET node.@agg += ...`) in `PER PATH` become visible after the path clause completes. - Both assignment (`=`) and aggregation (`+=`) operations in `PER NODE` are immediately visible. Common pitfall: aggregating and then exporting in the same `PER PATH` Because aggregations in `PER PATH` do not take effect until the clause completes, if you first run `SET node.@agg += ...` and then `EXPORT node.@agg` in the same `PER PATH`, the exported value is the old value before aggregation. ```` // Incorrect: score exports the old pre-aggregation value PER PATH { SET v.@score += e.rate EXPORT v.id, v.@score INTO result } // Correct: split into two PER PATH blocks MATCH ... PER PATH { SET v.@score += e.rate } MATCH ... PER PATH { EXPORT v.id, v.@score INTO result } ```` For example: ```` ''' 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 is still 0 here } PER NODE (a) { LOG_INFO("a.@node_sum == ",a.@node_sum) // Updates from `PER PATH` are visible here SET a.@node_sum += 1 // (1) LOG_INFO("(1) a.@node_sum == ",a.@node_sum) // Update (1) has taken effect SET a.@node_sum = 0 // (2) LOG_INFO("(2) a.@node_sum == ",a.@node_sum) // Update (2) has taken effect } } ''' ```` ## Examples The following example uses Jaccard similarity to demonstrate match compute statements. First, create a temporary graph from the `movie` graph in the example dataset: ```` ''' // Create the temporary graph `#match_compute_movie` CREATE TEMPORARY GRAPH IF NOT EXISTS #match_compute_movie AS COPY OF movie ''' ```` ```` ''' USE #match_compute_movie { /* ====== DECLARATIONS ====== */ VALUE source_id = 1 // Source user ID for similarity calculation VALUE source_edges SumAgg = 0 // Movies liked by the source user VALUE topk_similarity TopKAgg<5, similarity DOUBLE DESC, id INT DESC> // Top-5 similar users NODE VALUE target_edges SumAgg = 0 // Movies liked per user NODE VALUE intersection SumAgg = 0 // Common movies liked with the source user VALUE v_set ACTIVE_SET // Movies liked by the source user /* ====== STEP 1: COUNT USER MOVIE PREFERENCES ====== */ // Count movies liked by all users MATCH (u@User)-[e:Watch]->(m:Movie) PER PATH { SET u.@target_edges += 1 // Increment movie count per user } /* ====== STEP 2: COLLECT SOURCE USER DATA ====== */ // Process source user's preferences MATCH (u@User)-[:Watch]->(m:Movie) WHERE u.id = source_id // Filter to the source user PER NODE (u) { SET @source_edges += u.@target_edges // Store the source user's movie count } FINALLY { SET v_set = m } // Populate active set with the source's movies /* ====== STEP 3: CALCULATE USER SIMILARITIES ====== */ // Find common movies between source and other users MATCH (m:Movie)<-[:Watch]-(t@User) WHERE t.id <> source_id AND m IN v_set // Exclude the source user and use source's movies as starting point PER PATH { SET t.@intersection += 1 // Count common movies } PER NODE (t) { // Calculate the union size of movies liked by the source and other users VALUE union_size = t.@target_edges + @source_edges - t.@intersection IF union_size <> 0 THEN { VALUE similarity = CAST(t.@intersection AS DOUBLE) / union_size // Store in top-k aggregator SET @topk_similarity += RECORD{ similarity: similarity, id: t.id } } } /* ====== STEP 4: RETURN RESULTS ====== */ // Output top 5 similar users FOR r IN @topk_similarity RETURN r.id AS user_id, ROUND(r.similarity, 5) AS jaccard_similarity } ''' ```` Return: ```` +---------+--------------------+ | user_id | jaccard_similarity | +---------+--------------------+ | 409 | 0.55556 | | 70 | 0.55556 | | 282 | 0.54545 | | 122 | 0.54545 | | 39 | 0.54545 | +---------+--------------------+ ````