Graph query languages are specialized languages designed to express pattern matching and traversal operations on graph data. Unlike SQL, which operates on tables and joins, graph query languages work natively with nodes, relationships, and paths. This chapter explores the major graph query languages, with deep dives into Cypher and Gremlin, and examines the emerging GQL standard.
Graph databases needed specialized query languages because SQL, despite its power, wasn't designed for graph traversals. Early graph databases used imperative APIs that required developers to manually navigate relationships. Modern declarative query languages allow expressing complex patterns concisely.
Created by Neo4j, Cypher has become the most widely used graph query language. Its SQL-like syntax and ASCII-art pattern matching make it intuitive and readable.
// Anonymous node
()
// Node with label
(:Person)
// Node with label and property filter
(:Person {name: 'Alice'})
// Node bound to variable
(p:Person)
// Node with multiple labels
(p:Person:Employee)
// Node with variable and properties
(alice:Person {name: 'Alice', age: 30})
// Undirected relationship (rare in queries)
-[:KNOWS]-
// Directed relationship
-[:KNOWS]->
// Relationship with variable
-[k:KNOWS]->
// Relationship with properties
-[k:KNOWS {since: 2015}]->
// Multiple relationship types
-[:KNOWS|FRIEND_OF]->
// Variable length path
-[:KNOWS*1..3]->
// Any relationship type
-[*]->
// Simple pattern
(alice:Person)-[:KNOWS]->(bob:Person)
// Chain pattern
(alice)-[:KNOWS]->(bob)-[:KNOWS]->(charlie)
// Multiple relationships
(person)-[:LIVES_IN]->(city)<-[:LOCATED_IN]-(company)
// Variable length traversal
(alice)-[:KNOWS*2..4]->(friend)
// Find all people
MATCH (p:Person)
RETURN p
// Find friends of Alice
MATCH (alice:Person {name: 'Alice'})-[:KNOWS]->(friend)
RETURN friend.name
// Find friends of friends
MATCH (alice:Person {name: 'Alice'})-[:KNOWS*2]->(fof)
RETURN DISTINCT fof.name
// Multiple patterns
MATCH (p:Person)-[:WORKS_FOR]->(c:Company),
(p)-[:LIVES_IN]->(city:City)
WHERE c.name = 'Acme Corp'
RETURN p.name, city.name
// Simple filter
MATCH (p:Person)
WHERE p.age > 25
RETURN p.name
// Complex conditions
MATCH (p:Person)
WHERE p.age > 25 AND p.city = 'NYC' OR p.salary > 100000
RETURN p.name
// Pattern in WHERE
MATCH (p:Person)
WHERE (p)-[:KNOWS]->(:Person {name: 'Alice'})
RETURN p.name
// String operations
MATCH (p:Person)
WHERE p.email ENDS WITH '@example.com'
RETURN p.name
// Null checks
MATCH (p:Person)
WHERE p.phone IS NOT NULL
RETURN p.name
// List operations
MATCH (p:Person)
WHERE p.age IN [25, 30, 35]
RETURN p.name
// Create single node
CREATE (p:Person {name: 'Alice', age: 30})
// Create multiple nodes and relationship
CREATE (alice:Person {name: 'Alice'})-[:KNOWS]->(bob:Person {name: 'Bob'})
// Create pattern with existing nodes
MATCH (alice:Person {name: 'Alice'}), (bob:Person {name: 'Bob'})
CREATE (alice)-[:KNOWS {since: 2020}]->(bob)
// Create multiple relationships
CREATE (p:Person {name: 'Alice'})
CREATE (p)-[:LIVES_IN]->(:City {name: 'NYC'})
CREATE (p)-[:WORKS_FOR]->(:Company {name: 'Acme'})
// Create node if it doesn't exist
MERGE (p:Person {email: 'alice@example.com'})
ON CREATE SET p.name = 'Alice', p.createdAt = timestamp()
ON MATCH SET p.lastSeen = timestamp()
RETURN p
// Create relationship if it doesn't exist
MATCH (alice:Person {name: 'Alice'}), (bob:Person {name: 'Bob'})
MERGE (alice)-[k:KNOWS]->(bob)
ON CREATE SET k.since = date()
RETURN k
// Set single property
MATCH (p:Person {name: 'Alice'})
SET p.age = 31
RETURN p
// Set multiple properties
MATCH (p:Person {name: 'Alice'})
SET p.age = 31, p.city = 'NYC', p.updatedAt = timestamp()
// Replace all properties
MATCH (p:Person {name: 'Alice'})
SET p = {name: 'Alice', age: 31, email: 'alice@example.com'}
// Add properties
MATCH (p:Person {name: 'Alice'})
SET p += {phone: '555-1234', verified: true}
// Add label
MATCH (p:Person {name: 'Alice'})
SET p:Employee:Manager
// Delete node (must delete relationships first)
MATCH (p:Person {name: 'Alice'})
DETACH DELETE p
// Delete relationship
MATCH (alice:Person {name: 'Alice'})-[k:KNOWS]->(bob)
DELETE k
// Remove property
MATCH (p:Person {name: 'Alice'})
REMOVE p.temporaryField
// Remove label
MATCH (p:Person {name: 'Alice'})
REMOVE p:Temporary
// Return nodes
MATCH (p:Person)
RETURN p
// Return properties
MATCH (p:Person)
RETURN p.name, p.age
// Return with alias
MATCH (p:Person)
RETURN p.name AS personName, p.age AS personAge
// Return distinct
MATCH (p:Person)
RETURN DISTINCT p.city
// Return with limit
MATCH (p:Person)
RETURN p.name
ORDER BY p.age DESC
LIMIT 10
// Return with expressions
MATCH (p:Person)
RETURN p.name, p.age, p.age * 2 AS doubleAge
// Chain operations
MATCH (p:Person)
WITH p
ORDER BY p.age DESC
LIMIT 10
MATCH (p)-[:KNOWS]->(friend)
RETURN p.name, collect(friend.name) AS friends
// Aggregation in pipeline
MATCH (p:Person)-[:PURCHASED]->(product)
WITH p, count(product) AS purchaseCount
WHERE purchaseCount > 5
RETURN p.name, purchaseCount
// Count
MATCH (p:Person)
RETURN count(p)
// Collect
MATCH (alice:Person {name: 'Alice'})-[:KNOWS]->(friend)
RETURN alice.name, collect(friend.name) AS friends
// Sum, Avg, Min, Max
MATCH (p:Person)
RETURN avg(p.age), min(p.age), max(p.age), sum(p.age)
// Collect distinct
MATCH (p:Person)-[:LIVES_IN]->(city)
RETURN collect(DISTINCT city.name) AS cities
// LEFT JOIN equivalent
MATCH (p:Person)
OPTIONAL MATCH (p)-[:KNOWS]->(friend)
RETURN p.name, collect(friend.name) AS friends
// Find shortest path
MATCH path = shortestPath(
(alice:Person {name: 'Alice'})-[:KNOWS*]-(bob:Person {name: 'Bob'})
)
RETURN path, length(path)
// All shortest paths
MATCH paths = allShortestPaths(
(alice:Person {name: 'Alice'})-[:KNOWS*]-(bob:Person {name: 'Bob'})
)
RETURN paths
// Transform list
MATCH (p:Person)
RETURN [friend IN collect(p) | friend.name] AS names
// Filter list
MATCH (p:Person)
WITH collect(p) AS people
RETURN [person IN people WHERE person.age > 25 | person.name] AS adults
Gremlin, part of Apache TinkerPop, is a functional, data-flow language for graph traversal. Unlike Cypher's declarative approach, Gremlin is more imperative, describing the traversal steps explicitly.
// Get all vertices
g.V()
// Get specific vertex by ID
g.V(123)
// Get all edges
g.E()
// Get vertices by label
g.V().hasLabel('person')
// Get vertices by property
g.V().has('person', 'name', 'Alice')
// Out: traverse outgoing edges
g.V().has('person', 'name', 'Alice').out('knows')
// In: traverse incoming edges
g.V().has('person', 'name', 'Bob').in('knows')
// Both: traverse in both directions
g.V().has('person', 'name', 'Alice').both('knows')
// OutE: get outgoing edges
g.V().has('person', 'name', 'Alice').outE('knows')
// InV: get incoming vertices from edge
g.E().hasLabel('knows').inV()
// Has: filter by property
g.V().has('person', 'age', gt(25))
// Where: complex filters
g.V().hasLabel('person').where(__.values('age').is(gt(25)))
// Not: negation
g.V().hasLabel('person').not(__.out('knows'))
// And/Or
g.V().hasLabel('person').and(
__.has('age', gt(25)),
__.has('city', 'NYC')
)
// Values: extract property values
g.V().hasLabel('person').values('name')
// ValueMap: get all properties
g.V().hasLabel('person').valueMap()
// Project: create custom projections
g.V().hasLabel('person').project('name', 'friendCount')
.by('name')
.by(out('knows').count())
// Select: select from previous steps
g.V().hasLabel('person').as('p')
.out('knows').as('friend')
.select('p', 'friend')
.by('name')
// Count
g.V().hasLabel('person').count()
// Group
g.V().hasLabel('person').group().by('city').by(count())
// Fold: collect all results
g.V().hasLabel('person').values('name').fold()
// Group count
g.V().hasLabel('person').groupCount().by('city')
| Aspect | Cypher | Gremlin |
|---|---|---|
| Style | Declarative (what) | Imperative (how) |
| Syntax | SQL-like, ASCII art | Functional, method chaining |
| Learning Curve | Easier for SQL users | Easier for programmers |
| Database Support | Neo4j, some others | Multiple (TinkerPop-enabled) |
| Expressiveness | Excellent for patterns | Excellent for complex logic |
| Language Binding | Query string | Native in multiple languages |
Find friends of friends who like the same products:
Cypher:
MATCH (me:Person {name: 'Alice'})-[:KNOWS*2]->(fof:Person)
MATCH (me)-[:LIKES]->(product:Product)<-[:LIKES]-(fof)
WHERE NOT (me)-[:KNOWS]->(fof)
RETURN fof.name, collect(DISTINCT product.name) AS commonProducts
ORDER BY size(commonProducts) DESC
LIMIT 10
Gremlin:
g.V().has('person', 'name', 'Alice').as('me')
.out('knows').out('knows').as('fof')
.where(not(__.as('me').out('knows').as('fof')))
.where(
__.as('me').out('likes').as('product')
.in('likes').where(eq('fof'))
)
.select('fof', 'product')
.by('name')
.by('name')
.groupCount()
.order(local).by(values, desc)
.limit(10)
SPARQL is the W3C standard query language for RDF (Resource Description Framework) triple stores:
PREFIX foaf:
SELECT ?name ?email
WHERE {
?person foaf:name "Alice" .
?person foaf:knows ?friend .
?friend foaf:name ?name .
?friend foaf:mbox ?email .
}
GQL (Graph Query Language) is being standardized by ISO/IEC to provide a unified query language for property graphs:
// Bad: No labels, late filter
MATCH (a)-[:KNOWS*2]->(b)
WHERE a.name = 'Alice'
RETURN b
// Good: Labels, early binding
MATCH (alice:Person {name: 'Alice'})-[:KNOWS*2]->(b:Person)
RETURN b.name
LIMIT 100
Graph query languages like Cypher and Gremlin provide powerful tools for expressing complex graph operations. Cypher's declarative, pattern-based approach makes it intuitive for most users, while Gremlin's imperative style offers fine-grained control. Understanding both paradigms and choosing the right language for your use case and database platform is essential for effective graph database development.
Next, we'll explore graph algorithms and analytics, learning how to extract insights from connected data using pathfinding, centrality metrics, and community detection.
WIA-Official/wia-standards-public/tree/main/graph-database — open standard initiative providing source code for simulator, spec, API, and ebook assets cited throughout this volume; serves as the canonical verification record for all primary-source citations made by the WIA standard committee in this chapter. Canonical ENUM tokens used in this volume include NEO4J, NEPTUNE, DGRAPH, ARANGODB, JANUSGRAPH, TIGERGRAPH, MEMGRAPH, NEBULA_GRAPH, CYPHER, GREMLIN, SPARQL, GQL, PAGERANK, LOUVAIN, NODE2VEC, GRAPHSAGE, ISO_GQL_2024, W3C_RDF, W3C_SPARQL, LDBC_SNB, WIKIDATA, DBPEDIA, YAGO, CONCEPTNET.