GQL Tutorial
Welcome to the Graph Query Language (GQL) tutorial. This guide will take you from GQL basics to writing advanced graph queries through hands-on examples and exercises.
What You’ll Learn
By the end of this tutorial, you will be able to:
- Create and query nodes and relationships
- Use pattern matching to find graph structures
- Filter, sort, and paginate results
- Aggregate data with COUNT, SUM, AVG, and more
- Modify graph data with CREATE, SET, and DELETE
- Write complex queries with multiple clauses
- Optimize queries for performance
Prerequisites
Before starting:
- Geode installed and running - See Installation Guide
- Access to the Geode shell -
geode shell - No prior GQL experience required - SQL knowledge helpful but not necessary
Part 1: Getting Started
Connecting to Geode
Start the Geode interactive shell:
geode shell
# Output:
# Geode Shell v0.1.3
# Connected to localhost:3141
# Type \help for commands
# geode>
Creating a Sample Graph
Let’s create a simple social network:
-- Create the graph
CREATE GRAPH SocialNetwork;
USE SocialNetwork;
-- Create some people
CREATE (:Person {name: 'Alice', age: 30, city: 'Seattle'});
CREATE (:Person {name: 'Bob', age: 28, city: 'Portland'});
CREATE (:Person {name: 'Carol', age: 35, city: 'Seattle'});
CREATE (:Person {name: 'David', age: 32, city: 'Vancouver'});
CREATE (:Person {name: 'Eve', age: 27, city: 'Portland'});
What happened?
CREATE GRAPHcreates a new graph databaseUSEswitches to that graphCREATEwith(:Person {...})creates nodes with thePersonlabel and properties
Creating Relationships
Connect the people with KNOWS relationships:
-- Create friendships
MATCH (a:Person {name: 'Alice'}), (b:Person {name: 'Bob'})
CREATE (a)-[:KNOWS {since: 2020}]->(b);
MATCH (a:Person {name: 'Alice'}), (c:Person {name: 'Carol'})
CREATE (a)-[:KNOWS {since: 2018}]->(c);
MATCH (b:Person {name: 'Bob'}), (c:Person {name: 'Carol'})
CREATE (b)-[:KNOWS {since: 2021}]->(c);
MATCH (c:Person {name: 'Carol'}), (d:Person {name: 'David'})
CREATE (c)-[:KNOWS {since: 2019}]->(d);
MATCH (d:Person {name: 'David'}), (e:Person {name: 'Eve'})
CREATE (d)-[:KNOWS {since: 2022}]->(e);
Graph Structure Created:
Alice --KNOWS--> Bob --KNOWS--> Carol --KNOWS--> David --KNOWS--> Eve
| ^
+----------KNOWS---------------+
Part 2: Basic Queries
Query 1: Find All People
MATCH (p:Person)
RETURN p.name, p.age, p.city;
Expected Output:
| p.name | p.age | p.city |
|---------|-------|-----------|
| Alice | 30 | Seattle |
| Bob | 28 | Portland |
| Carol | 35 | Seattle |
| David | 32 | Vancouver |
| Eve | 27 | Portland |
Explanation:
MATCH (p:Person)finds all nodes with thePersonlabelpis a variable that refers to each matched nodeRETURN p.name, p.age, p.cityselects which properties to display
Query 2: Filter with WHERE
Find people over 30:
MATCH (p:Person)
WHERE p.age > 30
RETURN p.name, p.age;
Output:
| p.name | p.age |
|--------|-------|
| Carol | 35 |
| David | 32 |
Query 3: Multiple Conditions
Find people in Seattle under 35:
MATCH (p:Person)
WHERE p.city = 'Seattle' AND p.age < 35
RETURN p.name, p.age, p.city;
Output:
| p.name | p.age | p.city |
|--------|-------|---------|
| Alice | 30 | Seattle |
Query 4: Sorting Results
Order by age (descending):
MATCH (p:Person)
RETURN p.name, p.age
ORDER BY p.age DESC;
Output:
| p.name | p.age |
|--------|-------|
| Carol | 35 |
| David | 32 |
| Alice | 30 |
| Bob | 28 |
| Eve | 27 |
Query 5: Limiting Results
Get the 3 oldest people:
MATCH (p:Person)
RETURN p.name, p.age
ORDER BY p.age DESC
LIMIT 3;
Output:
| p.name | p.age |
|--------|-------|
| Carol | 35 |
| David | 32 |
| Alice | 30 |
Part 3: Relationship Queries
Query 6: Find Direct Connections
Find who Alice knows:
MATCH (alice:Person {name: 'Alice'})-[:KNOWS]->(friend:Person)
RETURN friend.name;
Output:
| friend.name |
|-------------|
| Bob |
| Carol |
Explanation:
(alice:Person {name: 'Alice'})matches the specific Alice node-[:KNOWS]->matches outgoing KNOWS relationships(friend:Person)captures the connected Person nodes
Query 7: Bidirectional Relationships
Find anyone connected to Bob (in or out):
MATCH (bob:Person {name: 'Bob'})-[:KNOWS]-(connected:Person)
RETURN connected.name;
Output:
| connected.name |
|----------------|
| Alice |
| Carol |
Note: -[:KNOWS]- (no arrow) matches both directions.
Query 8: Relationship Properties
Find when friendships were created:
MATCH (a:Person)-[k:KNOWS]->(b:Person)
RETURN a.name, b.name, k.since;
Output:
| a.name | b.name | k.since |
|--------|--------|---------|
| Alice | Bob | 2020 |
| Alice | Carol | 2018 |
| Bob | Carol | 2021 |
| Carol | David | 2019 |
| David | Eve | 2022 |
Query 9: Friends of Friends
Find Alice’s friends-of-friends:
MATCH (alice:Person {name: 'Alice'})-[:KNOWS*2]->(fof:Person)
WHERE fof <> alice
RETURN DISTINCT fof.name;
Output:
| fof.name |
|----------|
| Carol |
| David |
Explanation:
-[:KNOWS*2]->means exactly 2 hopsWHERE fof <> aliceexcludes Alice herselfDISTINCTremoves duplicates
Query 10: Variable-Length Paths
Find everyone reachable from Alice:
MATCH (alice:Person {name: 'Alice'})-[:KNOWS*1..4]->(reachable:Person)
RETURN DISTINCT reachable.name;
Output:
| reachable.name |
|----------------|
| Bob |
| Carol |
| David |
| Eve |
Explanation: -[:KNOWS*1..4]-> matches paths of 1 to 4 hops.
Part 4: Aggregation
Query 11: Count Nodes
Count all people:
MATCH (p:Person)
RETURN count(p) AS total_people;
Output:
| total_people |
|--------------|
| 5 |
Query 12: Group By and Count
Count people by city:
MATCH (p:Person)
RETURN p.city, count(p) AS population
ORDER BY population DESC;
Output:
| p.city | population |
|-----------|------------|
| Seattle | 2 |
| Portland | 2 |
| Vancouver | 1 |
Query 13: Multiple Aggregations
Calculate age statistics by city:
MATCH (p:Person)
RETURN p.city,
count(p) AS count,
avg(p.age) AS avg_age,
min(p.age) AS youngest,
max(p.age) AS oldest;
Output:
| p.city | count | avg_age | youngest | oldest |
|-----------|-------|---------|----------|--------|
| Seattle | 2 | 32.5 | 30 | 35 |
| Portland | 2 | 27.5 | 27 | 28 |
| Vancouver | 1 | 32.0 | 32 | 32 |
Query 14: Count Relationships
Count friendships per person:
MATCH (p:Person)-[:KNOWS]->(friend)
RETURN p.name, count(friend) AS friends
ORDER BY friends DESC;
Output:
| p.name | friends |
|--------|---------|
| Alice | 2 |
| Bob | 1 |
| Carol | 1 |
| David | 1 |
Query 15: COLLECT Aggregation
Collect friends into a list:
MATCH (p:Person)-[:KNOWS]->(friend)
RETURN p.name, collect(friend.name) AS friend_list;
Output:
| p.name | friend_list |
|--------|------------------|
| Alice | [Bob, Carol] |
| Bob | [Carol] |
| Carol | [David] |
| David | [Eve] |
Part 5: Data Modification
Query 16: Update Properties
Update Alice’s age:
MATCH (p:Person {name: 'Alice'})
SET p.age = 31
RETURN p.name, p.age;
Query 17: Add New Properties
Add email to all people:
MATCH (p:Person)
SET p.email = toLower(p.name) + '@example.com'
RETURN p.name, p.email;
Query 18: Remove Properties
Remove the email property:
MATCH (p:Person)
REMOVE p.email
RETURN p.name, p.email;
Query 19: Delete Relationships
Remove friendship between Alice and Bob:
MATCH (a:Person {name: 'Alice'})-[k:KNOWS]->(b:Person {name: 'Bob'})
DELETE k;
Query 20: Delete Nodes
Delete a person (must delete relationships first):
-- First, delete relationships
MATCH (p:Person {name: 'Eve'})-[r]-()
DELETE r;
-- Then delete the node
MATCH (p:Person {name: 'Eve'})
DELETE p;
-- Or use DETACH DELETE to do both
MATCH (p:Person {name: 'Eve'})
DETACH DELETE p;
Part 6: Advanced Queries
Query 21: WITH Clause
Use WITH for query pipelines:
MATCH (p:Person)-[:KNOWS]->(friend)
WITH p, count(friend) AS friend_count
WHERE friend_count >= 2
RETURN p.name, friend_count;
Explanation: WITH passes intermediate results to subsequent clauses.
Query 22: OPTIONAL MATCH
Find people and their friends (including those with no friends):
MATCH (p:Person)
OPTIONAL MATCH (p)-[:KNOWS]->(friend)
RETURN p.name, count(friend) AS friends;
Note: OPTIONAL MATCH is like a LEFT JOIN - it returns rows even when no match is found.
Query 23: CASE Expressions
Categorize people by age:
MATCH (p:Person)
RETURN p.name, p.age,
CASE
WHEN p.age < 30 THEN 'Young'
WHEN p.age < 35 THEN 'Middle'
ELSE 'Senior'
END AS category;
Query 24: COALESCE for Defaults
Handle missing properties:
MATCH (p:Person)
RETURN p.name, coalesce(p.nickname, p.name) AS display_name;
Query 25: Shortest Path
Find shortest path between two people:
MATCH path = shortestPath(
(alice:Person {name: 'Alice'})-[*]-(eve:Person {name: 'Eve'})
)
RETURN [n IN nodes(path) | n.name] AS path_names,
length(path) AS distance;
Practice Exercises
Exercise 1: Basic Query
Task: Find all people in Portland, sorted by age (youngest first).
Solution
MATCH (p:Person)
WHERE p.city = 'Portland'
RETURN p.name, p.age
ORDER BY p.age ASC;
Exercise 2: Relationship Query
Task: Find all pairs of people who know each other and both live in the same city.
Solution
MATCH (a:Person)-[:KNOWS]->(b:Person)
WHERE a.city = b.city
RETURN a.name, b.name, a.city;
Exercise 3: Aggregation
Task: Find the average age of people who have at least 1 friend.
Solution
MATCH (p:Person)-[:KNOWS]->()
WITH DISTINCT p
RETURN avg(p.age) AS avg_age;
Exercise 4: Path Query
Task: Find all paths of length 3 starting from Alice.
Solution
MATCH path = (alice:Person {name: 'Alice'})-[:KNOWS*3]->(end)
RETURN [n IN nodes(path) | n.name] AS path;
Exercise 5: Complex Query
Task: For each city, find the most connected person (person with the most outgoing KNOWS relationships).
Solution
MATCH (p:Person)-[:KNOWS]->(friend)
WITH p, count(friend) AS connections
ORDER BY connections DESC
WITH p.city AS city, collect({name: p.name, connections: connections})[0] AS top
RETURN city, top.name AS most_connected, top.connections;
Common Mistakes
Mistake 1: Missing Variable
-- Wrong: No variable to reference
MATCH (:Person)
RETURN name; -- Error: name is not bound
-- Right: Use a variable
MATCH (p:Person)
RETURN p.name;
Mistake 2: Forgetting DISTINCT
-- May have duplicates
MATCH (a)-[:KNOWS*1..3]->(b)
RETURN b.name;
-- Use DISTINCT to remove duplicates
MATCH (a)-[:KNOWS*1..3]->(b)
RETURN DISTINCT b.name;
Mistake 3: Deleting Nodes with Relationships
-- Wrong: Will fail if node has relationships
MATCH (p:Person {name: 'Alice'})
DELETE p;
-- Right: Use DETACH DELETE
MATCH (p:Person {name: 'Alice'})
DETACH DELETE p;
Mistake 4: Cartesian Product
-- Warning: This creates a cartesian product (every combination)
MATCH (a:Person), (b:Person)
WHERE a.city = b.city
RETURN a.name, b.name;
-- Better: Be explicit about the pattern
MATCH (a:Person)-[:LIVES_IN]->(c:City)<-[:LIVES_IN]-(b:Person)
RETURN a.name, b.name;
Next Steps
Congratulations on completing the GQL tutorial! Continue learning with:
- Advanced Patterns - Complex query techniques
- Pattern Matching Reference - Complete pattern syntax
- Aggregations Reference - All aggregation functions
- EXPLAIN and PROFILE - Query analysis and optimization
- Tutorials - More hands-on guides
- GQL Guide - Complete language reference
Quick Reference Card
-- Node pattern
(variable:Label {prop: value})
-- Relationship pattern
-[var:TYPE {prop: value}]->
-- Variable-length path
-[:TYPE*min..max]->
-- Query structure
MATCH pattern
WHERE condition
WITH projection
RETURN expression
ORDER BY column
LIMIT n
-- Aggregations
count(*), sum(n.x), avg(n.x), min(n.x), max(n.x), collect(n.x)
-- Modification
CREATE (n:Label {props})
SET n.prop = value
REMOVE n.prop
DELETE n
DETACH DELETE n
Last Updated: January 28, 2026 Geode Version: v0.1.3+ GQL Compliance: ISO/IEC 39075:2024