Skip to content
Developer Edition runs on one host and does not require a product license file. Need multi-host deployment or performance testing at scale? Explore Enterprise Edition

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
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 }

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:

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, 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.

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 += ...) 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<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            |
+---------+--------------------+