Match compute statement¶
The match compute statement performs iterative computations on temporary graphs. 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 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.
Data has been imported. For more information, see IMPORT.
Syntax¶
- 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 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.
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, 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<INT> = 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, 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<INT> = 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 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
- Variable manipulations
- Control flow statements
- EXPORT statements
- Logging statements
For more information, see Procedure body.
The following statements are not supported inside PER NODE and PER PATH blocks:
- Query-level clauses such as
RETURN,LIMIT,ORDER BY, andGROUP BY - General query statements such as
MATCH, subqueries, and localTABLEdefinitions
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 byactive_set_variablewith the set of nodes specified bynode_variable. - The
|=mode adds the set of nodes specified bynode_variableto the current set of nodes held byactive_set_variable.
active_set_variable¶
An active set variable that holds a set of nodes. For more information, see 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<INT> = 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 <per path clause> are visible here
}
'''
Node-attached aggregator value¶
- Aggregations (
SET node.@agg += ...) inPER PATHbecome visible after the path clause completes.
- Both assignment (
=) and aggregation (+=) operations inPER NODEare 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<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 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<INT64> = 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<INT64> = 0 // Movies liked per user
NODE VALUE intersection SumAgg<INT64> = 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 |
+---------+--------------------+