Cypher Tutorial

Prefer a guided, video-based walkthrough? The ArcadeDB Academy offers a free course on Cypher with hands-on exercises and a certification at the end.

This tutorial introduces you to Cypher, a declarative graph query language. By the end of this tutorial, you’ll be able to create, query, and manipulate graph data in ArcadeDB using Cypher.

What is Cypher?

Cypher is a pattern-matching query language designed specifically for graphs. It uses ASCII-art syntax to represent graph patterns, making queries intuitive and readable. For example, (a)-[:KNOWS]→(b) visually represents a KNOWS relationship from node a to node b.

Setting Up

Before running Cypher queries, ensure you have:

  1. ArcadeDB server running

  2. A database created (we’ll use tutorial in examples)

Create vertex types for our tutorial:

CREATE VERTEX TYPE Person
CREATE VERTEX TYPE Movie
CREATE VERTEX TYPE Company
CREATE EDGE TYPE ACTED_IN
CREATE EDGE TYPE DIRECTED
CREATE EDGE TYPE WORKS_AT
CREATE EDGE TYPE KNOWS

Part 1: Creating Data

Creating Nodes

Use the CREATE clause to add nodes (vertices) to your graph.

Create a single node:

CREATE (p:Person {name: 'Alice', age: 30, city: 'New York'})

This creates a node with:

  • Label Person (the type)

  • Properties: name, age, and city

Create multiple nodes:

CREATE (a:Person {name: 'Bob', age: 35}),
       (b:Person {name: 'Charlie', age: 28}),
       (c:Person {name: 'Diana', age: 32})

Creating Relationships

Relationships (edges) connect nodes. Create them with the arrow syntax.

Create a relationship between new nodes:

CREATE (a:Person {name: 'Eve'})-[:KNOWS {since: 2020}]->(b:Person {name: 'Frank'})

Create a relationship between existing nodes:

MATCH (a:Person {name: 'Alice'}), (b:Person {name: 'Bob'})
CREATE (a)-[:KNOWS {since: 2019}]->(b)

Sample Dataset

Let’s create a sample movie database:

// Create people
CREATE (keanu:Person {name: 'Keanu Reeves', born: 1964})
CREATE (carrie:Person {name: 'Carrie-Anne Moss', born: 1967})
CREATE (laurence:Person {name: 'Laurence Fishburne', born: 1961})
CREATE (lana:Person {name: 'Lana Wachowski', born: 1965})
CREATE (lilly:Person {name: 'Lilly Wachowski', born: 1967})

// Create movies
CREATE (matrix:Movie {title: 'The Matrix', released: 1999})
CREATE (reloaded:Movie {title: 'The Matrix Reloaded', released: 2003})

// Create relationships
MATCH (keanu:Person {name: 'Keanu Reeves'}), (matrix:Movie {title: 'The Matrix'})
CREATE (keanu)-[:ACTED_IN {role: 'Neo'}]->(matrix)

MATCH (carrie:Person {name: 'Carrie-Anne Moss'}), (matrix:Movie {title: 'The Matrix'})
CREATE (carrie)-[:ACTED_IN {role: 'Trinity'}]->(matrix)

MATCH (laurence:Person {name: 'Laurence Fishburne'}), (matrix:Movie {title: 'The Matrix'})
CREATE (laurence)-[:ACTED_IN {role: 'Morpheus'}]->(matrix)

MATCH (lana:Person {name: 'Lana Wachowski'}), (matrix:Movie {title: 'The Matrix'})
CREATE (lana)-[:DIRECTED]->(matrix)

MATCH (lilly:Person {name: 'Lilly Wachowski'}), (matrix:Movie {title: 'The Matrix'})
CREATE (lilly)-[:DIRECTED]->(matrix)

Part 2: Reading Data

Basic MATCH Queries

The MATCH clause finds patterns in your graph.

Find all nodes of a type:

MATCH (p:Person)
RETURN p

Find nodes with specific properties:

MATCH (p:Person {name: 'Alice'})
RETURN p

Return specific properties:

MATCH (p:Person)
RETURN p.name, p.age

Filtering with WHERE

Use WHERE for complex conditions:

MATCH (p:Person)
WHERE p.age > 30
RETURN p.name, p.age

Multiple conditions:

MATCH (p:Person)
WHERE p.age >= 25 AND p.age <= 35 AND p.city IS NOT NULL
RETURN p.name, p.age, p.city

String matching:

// Names starting with 'A'
MATCH (p:Person) WHERE p.name STARTS WITH 'A' RETURN p.name

// Names containing 'an'
MATCH (p:Person) WHERE p.name CONTAINS 'an' RETURN p.name

// Email pattern matching
MATCH (p:Person) WHERE p.email =~ '.*@gmail.com' RETURN p.name, p.email

IN operator:

MATCH (p:Person)
WHERE p.name IN ['Alice', 'Bob', 'Charlie']
RETURN p

Traversing Relationships

Find connected nodes by specifying relationship patterns:

Find direct connections:

MATCH (p:Person)-[:ACTED_IN]->(m:Movie)
RETURN p.name, m.title

Find connections with relationship properties:

MATCH (p:Person)-[r:ACTED_IN]->(m:Movie)
RETURN p.name, r.role, m.title

Find bidirectional connections (either direction):

MATCH (p:Person)-[:KNOWS]-(other:Person)
RETURN p.name, other.name

Multi-hop traversals:

MATCH (a:Person)-[:KNOWS]->(b:Person)-[:KNOWS]->(c:Person)
WHERE a.name = 'Alice'
RETURN a.name, b.name, c.name

Variable-Length Paths

Find paths of varying lengths:

// Find all people within 1 to 3 hops
MATCH (a:Person {name: 'Alice'})-[:KNOWS*1..3]->(b:Person)
RETURN b.name

Named paths:

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

OPTIONAL MATCH

Like a LEFT OUTER JOIN - returns NULL for missing matches:

MATCH (p:Person)
OPTIONAL MATCH (p)-[:ACTED_IN]->(m:Movie)
RETURN p.name, m.title

This returns all people, with m.title as NULL for those who haven’t acted in any movie.

Sorting and Pagination

ORDER BY:

MATCH (p:Person)
RETURN p.name, p.age
ORDER BY p.age DESC

Multiple sort keys:

MATCH (p:Person)
RETURN p.name, p.age
ORDER BY p.age DESC, p.name ASC

SKIP and LIMIT:

MATCH (p:Person)
RETURN p.name
ORDER BY p.name
SKIP 10
LIMIT 5

Part 3: Aggregations

Basic Aggregations

// Count all people
MATCH (p:Person)
RETURN count(p) AS totalPeople

// Count, sum, average
MATCH (p:Person)
RETURN count(p) AS total, sum(p.age) AS totalAge, avg(p.age) AS averageAge

// Min and max
MATCH (p:Person)
RETURN min(p.age) AS youngest, max(p.age) AS oldest

Grouping (Implicit GROUP BY)

Cypher automatically groups by non-aggregated expressions:

// Count people by city
MATCH (p:Person)
RETURN p.city, count(p) AS residents
ORDER BY residents DESC

Multiple grouping keys:

MATCH (p:Person)-[:WORKS_AT]->(c:Company)
RETURN c.name, p.department, count(p) AS employees
ORDER BY c.name, employees DESC

COLLECT

Collect values into a list:

// Collect all names
MATCH (p:Person)
RETURN collect(p.name) AS allNames

// Group and collect
MATCH (p:Person)-[:ACTED_IN]->(m:Movie)
RETURN m.title, collect(p.name) AS actors

Part 4: Data Modification

Updating with SET

// Update a single property
MATCH (p:Person {name: 'Alice'})
SET p.age = 31

// Update multiple properties
MATCH (p:Person {name: 'Alice'})
SET p.age = 31, p.city = 'Boston'

MERGE (Upsert)

MERGE finds or creates a pattern:

// Create if not exists
MERGE (p:Person {name: 'Grace'})

// With ON CREATE and ON MATCH actions
MERGE (p:Person {name: 'Grace'})
ON CREATE SET p.created = true, p.visits = 1
ON MATCH SET p.visits = p.visits + 1

MERGE relationships:

MATCH (a:Person {name: 'Alice'}), (b:Person {name: 'Bob'})
MERGE (a)-[r:KNOWS]->(b)
ON CREATE SET r.since = 2024
ON MATCH SET r.lastContact = 2024

DELETE

Delete a relationship:

MATCH (a:Person {name: 'Alice'})-[r:KNOWS]->(b:Person {name: 'Bob'})
DELETE r

Delete a node (must have no relationships):

MATCH (p:Person {name: 'TestPerson'})
DELETE p

DETACH DELETE (delete node and its relationships):

MATCH (p:Person {name: 'TestPerson'})
DETACH DELETE p

Part 5: Advanced Queries

WITH Clause

Use WITH to chain query parts:

// Filter and project before continuing
MATCH (p:Person)
WITH p.name AS name, p.age AS age
WHERE age > 30
RETURN name ORDER BY name

Aggregation with WITH:

MATCH (p:Person)-[:ACTED_IN]->(m:Movie)
WITH m.title AS movie, count(p) AS actorCount
WHERE actorCount > 2
RETURN movie, actorCount

UNWIND

Expand a list into rows:

// Unwind a literal list
UNWIND [1, 2, 3] AS x
RETURN x

// Unwind a property array
MATCH (p:Person {name: 'Alice'})
UNWIND p.hobbies AS hobby
RETURN p.name, hobby

// Create multiple nodes from a list
UNWIND ['Alice', 'Bob', 'Charlie'] AS name
CREATE (p:Person {name: name})

Pattern Predicates

Check if patterns exist:

// Find people who know someone
MATCH (p:Person)
WHERE (p)-[:KNOWS]->()
RETURN p.name

// Find people who don't know anyone
MATCH (p:Person)
WHERE NOT (p)-[:KNOWS]->()
RETURN p.name

// Find people who know a specific person
MATCH (a:Person {name: 'Alice'}), (p:Person)
WHERE (p)-[:KNOWS]->(a)
RETURN p.name AS knowsAlice

CASE Expressions

Conditional logic in queries:

MATCH (p:Person)
RETURN p.name,
       CASE
         WHEN p.age < 18 THEN 'minor'
         WHEN p.age < 65 THEN 'adult'
         ELSE 'senior'
       END AS ageGroup

Part 6: Using Functions

String Functions

MATCH (p:Person)
RETURN toUpper(p.name) AS upperName,
       toLower(p.name) AS lowerName,
       substring(p.name, 0, 3) AS initials

Math Functions

MATCH (p:Person)
RETURN p.name,
       abs(p.balance) AS absBalance,
       round(p.salary / 1000.0) AS salaryK

List Functions

MATCH (p:Person)-[:ACTED_IN]->(m:Movie)
WITH p.name AS actor, collect(m.title) AS movies
RETURN actor,
       size(movies) AS movieCount,
       head(movies) AS firstMovie,
       last(movies) AS lastMovie

Node/Relationship Functions

MATCH (p:Person)-[r:ACTED_IN]->(m:Movie)
RETURN id(p) AS personId,
       labels(p) AS personLabels,
       type(r) AS relationshipType,
       keys(m) AS movieProperties

Part 7: Best Practices

1. Use Labels

Always specify labels in MATCH patterns for better performance:

// Good - uses index on Person
MATCH (p:Person {name: 'Alice'}) RETURN p

// Avoid - scans all nodes
MATCH (n {name: 'Alice'}) RETURN n

2. Create Indexes

Create indexes on frequently queried properties:

CREATE INDEX ON Person (name)
CREATE INDEX ON Movie (title)

3. Use Parameters

For repeated queries, use parameters instead of string concatenation:

MATCH (p:Person)
WHERE p.name = $name AND p.age >= $minAge
RETURN p

4. Limit Results

Use LIMIT for exploratory queries:

MATCH (p:Person)-[:KNOWS*1..5]->(other)
RETURN p.name, other.name
LIMIT 100

5. Use WITH for Complex Queries

Break complex queries into steps with WITH:

MATCH (p:Person)-[:ACTED_IN]->(m:Movie)
WITH p, count(m) AS movieCount
WHERE movieCount > 5
MATCH (p)-[:DIRECTED]->(d:Movie)
RETURN p.name, movieCount, collect(d.title) AS directed