Pattern Matching in GQL

Pattern matching is the core of GQL queries. This reference covers all pattern types and their usage in Geode.

Overview

Patterns describe graph structures to match. They consist of:

  • Node patterns: Match nodes
  • Relationship patterns: Match relationships between nodes
  • Path patterns: Match sequences of nodes and relationships

Node Patterns

Basic Syntax

(variable:Label {property: value})
ComponentDescriptionRequired
variableVariable name for referencingNo
LabelNode label(s)No
{...}Property constraintsNo

Examples

-- Any node
(n)

-- Node with label
(p:Person)

-- Anonymous node (no variable)
(:Person)

-- Node with property constraint
(p:Person {name: 'Alice'})

-- Node with multiple properties
(p:Person {name: 'Alice', age: 30})

-- Node with multiple labels
(e:Person:Employee)

-- Just properties, no label
(n {status: 'active'})

Multiple Labels

Nodes can have multiple labels. Match nodes with specific label combinations:

-- Match nodes with both Person AND Employee labels
MATCH (e:Person:Employee)
RETURN e.name;

-- Match nodes with Person label (may have other labels too)
MATCH (p:Person)
RETURN p.name, labels(p);

Property Constraints

Property constraints filter nodes during matching:

-- Exact match
(p:Person {name: 'Alice'})

-- Multiple property constraints (AND)
(p:Person {name: 'Alice', city: 'Seattle'})

-- Dynamic values using parameters
(p:Person {id: $user_id})

Note: For complex filtering (comparisons, OR, etc.), use WHERE clause:

MATCH (p:Person)
WHERE p.age > 30 AND p.city IN ['Seattle', 'Portland']
RETURN p.name;

Relationship Patterns

Basic Syntax

-[variable:TYPE {property: value}]->
ComponentDescriptionRequired
- or <-Left connectionYes
[...]Relationship detailsNo
variableVariable nameNo
TYPERelationship typeNo
{...}Property constraintsNo
-> or -Right connectionYes

Direction

-- Outgoing relationship (left to right)
(a)-[:KNOWS]->(b)

-- Incoming relationship (right to left)
(a)<-[:KNOWS]-(b)

-- Undirected (either direction)
(a)-[:KNOWS]-(b)

Examples

-- Any relationship
(a)-[r]->(b)

-- Typed relationship
(a)-[:KNOWS]->(b)

-- Named relationship with type
(a)-[k:KNOWS]->(b)

-- Relationship with properties
(a)-[k:KNOWS {since: 2020}]->(b)

-- Anonymous relationship
(a)-[:KNOWS]->()

-- Multiple relationship types (OR)
(a)-[:KNOWS|:FOLLOWS]->(b)

-- Any relationship type
(a)-[r]->(b)

Multiple Relationship Types

Match any of several relationship types:

-- Either KNOWS or FOLLOWS
MATCH (a:Person)-[:KNOWS|:FOLLOWS]->(b:Person)
RETURN a.name, b.name;

-- Store type in variable
MATCH (a:Person)-[r:KNOWS|:FOLLOWS]->(b:Person)
RETURN a.name, type(r), b.name;

Relationship Properties

-- Match specific property value
MATCH (a)-[k:KNOWS {since: 2020}]->(b)
RETURN a.name, b.name;

-- Access relationship properties
MATCH (a)-[k:KNOWS]->(b)
RETURN a.name, b.name, k.since, k.strength;

-- Filter relationship properties in WHERE
MATCH (a)-[k:KNOWS]->(b)
WHERE k.since > 2020
RETURN a.name, b.name;

Path Patterns

Complete Patterns

A path pattern connects multiple node and relationship patterns:

-- Two-hop path
(a)-[:KNOWS]->(b)-[:WORKS_FOR]->(c)

-- Named path
path = (a)-[:KNOWS]->(b)-[:WORKS_FOR]->(c)

Path Examples

-- Find mutual friends
MATCH (alice:Person {name: 'Alice'})-[:KNOWS]->(friend)-[:KNOWS]->(mutual)
WHERE mutual <> alice
RETURN mutual.name;

-- Three-hop pattern
MATCH (a:Person)-[:WORKS_FOR]->(c:Company)-[:LOCATED_IN]->(city:City)
RETURN a.name, c.name, city.name;

-- Diamond pattern
MATCH (a)-[:KNOWS]->(b), (a)-[:KNOWS]->(c), (b)-[:KNOWS]->(d), (c)-[:KNOWS]->(d)
RETURN a.name, b.name, c.name, d.name;

Named Paths

Capture entire path for analysis:

-- Capture path
MATCH p = (a:Person {name: 'Alice'})-[:KNOWS*]->(b:Person {name: 'Bob'})
RETURN p;

-- Path functions
MATCH p = (a)-[:KNOWS*1..3]->(b)
RETURN nodes(p),         -- List of nodes in path
       relationships(p), -- List of relationships
       length(p);        -- Number of relationships

Variable-Length Paths

Syntax

-[:TYPE*min..max]->
PatternMeaning
*Zero or more (any length)
*nExactly n hops
*..nUp to n hops (0 to n)
*n..n or more hops
*n..mBetween n and m hops

Examples

-- Exactly 2 hops
MATCH (a)-[:KNOWS*2]->(b)
RETURN a.name, b.name;

-- 1 to 3 hops
MATCH (a)-[:KNOWS*1..3]->(b)
RETURN DISTINCT b.name;

-- Up to 5 hops
MATCH (a)-[:KNOWS*..5]->(b)
RETURN b.name;

-- 2 or more hops
MATCH (a)-[:KNOWS*2..]->(b)
RETURN b.name;

-- Any length (use with caution!)
MATCH (a)-[:KNOWS*]->(b)
RETURN b.name;

Path Variables with Length

-- Capture path with variable length
MATCH path = (a:Person {name: 'Alice'})-[:KNOWS*1..4]->(b:Person)
RETURN [n IN nodes(path) | n.name] AS names,
       length(path) AS distance;

-- Filter by path length
MATCH path = (a)-[:KNOWS*]->(b)
WHERE length(path) <= 3
RETURN path;

Relationship Variable in Path

Access all relationships in variable-length path:

-- Check all relationship properties
MATCH path = (a)-[rels:KNOWS*1..3]->(b)
WHERE ALL(r IN rels WHERE r.strength > 0.5)
RETURN a.name, b.name;

-- Aggregate over relationships
MATCH (a)-[rels:TRANSACTION*1..5]->(b)
RETURN a.id, b.id,
       reduce(total = 0, r IN rels | total + r.amount) AS path_total;

Shortest Path

shortestPath

Find single shortest path:

MATCH path = shortestPath(
  (a:Person {name: 'Alice'})-[*]-(b:Person {name: 'Bob'})
)
RETURN path, length(path) AS distance;

allShortestPaths

Find all shortest paths (may be multiple with same length):

MATCH paths = allShortestPaths(
  (a:Person {name: 'Alice'})-[*]-(b:Person {name: 'Bob'})
)
RETURN paths;

Constraints on Shortest Paths

-- Limit path length
MATCH path = shortestPath(
  (a:Person)-[:KNOWS*..10]-(b:Person)
)
RETURN path;

-- Specific relationship types
MATCH path = shortestPath(
  (a:City)-[:ROAD|:HIGHWAY*]-(b:City)
)
RETURN [n IN nodes(path) | n.name] AS route;

-- With WHERE filter
MATCH path = shortestPath(
  (a:Person)-[*]-(b:Person)
)
WHERE ALL(n IN nodes(path) WHERE n.active = true)
RETURN path;

OPTIONAL MATCH

Match patterns that may not exist:

-- Include people with no friends
MATCH (p:Person)
OPTIONAL MATCH (p)-[:KNOWS]->(friend:Person)
RETURN p.name, collect(friend.name) AS friends;

-- Multiple optional patterns
MATCH (u:User)
OPTIONAL MATCH (u)-[:PLACED]->(o:Order)
OPTIONAL MATCH (u)-[:REVIEWED]->(r:Review)
RETURN u.name, count(o) AS orders, count(r) AS reviews;

OPTIONAL vs Regular MATCH

-- Regular MATCH: excludes people with no friends
MATCH (p:Person)-[:KNOWS]->(friend)
RETURN p.name, count(friend);

-- OPTIONAL MATCH: includes people with no friends (count = 0)
MATCH (p:Person)
OPTIONAL MATCH (p)-[:KNOWS]->(friend)
RETURN p.name, count(friend);

Pattern Predicates

Pattern in WHERE

-- Check pattern exists
MATCH (p:Person)
WHERE (p)-[:KNOWS]->(:Person {name: 'Alice'})
RETURN p.name;

-- Check pattern does not exist
MATCH (p:Person)
WHERE NOT (p)-[:BLOCKED]->()
RETURN p.name;

EXISTS Subquery

-- Pattern exists
MATCH (p:Person)
WHERE EXISTS {
  MATCH (p)-[:PURCHASED]->(prod:Product)
  WHERE prod.price > 100
}
RETURN p.name;

Pattern Expressions

-- Count patterns
MATCH (p:Person)
RETURN p.name,
       size((p)-[:KNOWS]->()) AS outgoing_knows,
       size((p)<-[:KNOWS]-()) AS incoming_knows;

Advanced Patterns

Disconnected Patterns

Match independent patterns (creates cartesian product):

-- Two separate patterns
MATCH (a:Person {city: 'Seattle'}), (b:Company {industry: 'Tech'})
RETURN a.name, b.name;

Self-Relationships

-- Node related to itself
MATCH (n)-[r:LINKS]->(n)
RETURN n.name, r.type;

Mutual Relationships

-- Bidirectional friendship
MATCH (a:Person)-[:KNOWS]->(b:Person)-[:KNOWS]->(a)
RETURN a.name, b.name;

Negative Patterns

-- Not friends
MATCH (a:Person), (b:Person)
WHERE a <> b AND NOT (a)-[:KNOWS]-(b)
RETURN a.name, b.name;

Pattern Matching Performance

Best Practices

  1. Start with selective patterns

    -- Good: Start with specific node
    MATCH (a:Person {email: 'alice@example.com'})-[:KNOWS]->(b)
    
    -- Avoid: Start with broad scan
    MATCH (a)-[:KNOWS]->(b:Person {email: 'alice@example.com'})
    
  2. Use labels

    -- Good: Labels enable index usage
    MATCH (p:Person)-[:KNOWS]->(f:Person)
    
    -- Avoid: No labels means full scan
    MATCH (p)-[:KNOWS]->(f)
    
  3. Limit variable-length paths

    -- Good: Bounded length
    MATCH (a)-[:KNOWS*1..5]->(b)
    
    -- Dangerous: Unbounded may be slow
    MATCH (a)-[:KNOWS*]->(b)
    
  4. Filter early

    -- Good: Filter in pattern
    MATCH (p:Person {status: 'active'})-[:KNOWS]->(f)
    
    -- Also good: Filter immediately after
    MATCH (p:Person)-[:KNOWS]->(f)
    WHERE p.status = 'active'
    

Use EXPLAIN

Analyze pattern matching performance:

EXPLAIN MATCH (a:Person)-[:KNOWS*1..3]->(b:Person)
WHERE a.name = 'Alice'
RETURN b.name;

Common Pattern Examples

Social Network

-- Friends of friends (not direct friends)
MATCH (me:Person {name: 'Alice'})-[:KNOWS]->()-[:KNOWS]->(fof)
WHERE NOT (me)-[:KNOWS]->(fof) AND me <> fof
RETURN DISTINCT fof.name;

-- Influence chain
MATCH path = (influencer:Person)-[:FOLLOWS*1..3]->(follower:Person)
WHERE influencer.verified = true
RETURN influencer.name, follower.name, length(path);

E-Commerce

-- Products frequently bought together
MATCH (p1:Product)<-[:CONTAINS]-(o:Order)-[:CONTAINS]->(p2:Product)
WHERE p1.id < p2.id  -- Avoid duplicates
RETURN p1.name, p2.name, count(o) AS co_purchase_count
ORDER BY co_purchase_count DESC;

-- Customer journey
MATCH path = (c:Customer)-[:VIEWED*1..5]->(p:Product)-[:PURCHASED]->(p)
RETURN c.id, [n IN nodes(path) | n.name];

Knowledge Graph

-- Concept hierarchy
MATCH path = (specific:Concept)-[:IS_A*1..5]->(general:Concept)
WHERE specific.name = 'German Shepherd'
RETURN [n IN nodes(path) | n.name] AS taxonomy;

-- Entity connections
MATCH (e1:Entity)-[r*1..2]-(e2:Entity)
WHERE e1.name = 'Albert Einstein'
RETURN e2.name, [rel IN r | type(rel)] AS connection_types;

Last Updated: January 28, 2026 Geode Version: v0.1.3+ GQL Compliance: ISO/IEC 39075:2024