Introduction
You created CONTAINS relationships and completed the Customer→Order→Product path. Now you’ll see the full power of graph traversals.
In this lesson, you will learn how to write multi-hop queries that would be nightmares in SQL but are natural in Cypher.
Understanding the complete path
You now have this structure:
%%{init: {
"theme": "base",
"themeVariables": {
"primaryColor": "#eef6f9",
"primaryBorderColor": "#c7e0ec",
"lineColor": "#94a3b8",
"fontFamily": "Public Sans, Arial, Helvetica, sans-serif"
}
}}%%
graph LR
Customer((Customer)):::primary -->|PLACED| Order((Order)):::highlight
Order -->|ORDERS| Product((Product)):::forest
classDef primary fill:#eef6f9,stroke:#c7e0ec,stroke-width:1.25px,color:#0b5c7a
classDef highlight fill:#f4f5ff,stroke:#c7d2fe,stroke-width:1.25px,color:#3730a3
classDef forest fill:#edf6e8,stroke:#b7df9c,stroke-width:1.25px,color:#2f5d1e
linkStyle default stroke:#94a3b8,stroke-width:1.25pxThis enables multi-hop traversals:
-
Customer → Order → Product (what did they buy?)
-
Product ← Order ← Customer (who bought this?)
-
Customer → Order → Product ← Order ← Customer (who bought similar products?)
Finding what customers bought
The fundamental query - traverse two hops:
MATCH (c:Customer {id: 'ALFKI'})-[:PLACED]->(:Order)-[:CONTAINS]->(p:Product) // (1)
RETURN DISTINCT p.name // (2)
ORDER BY p.name // (3)-
Two-hop pattern - Customer through Order to Product (anonymous Order node)
-
Distinct - Eliminate duplicates (customer may order same product multiple times)
-
Sort - Alphabetical order
Click Run to see all products ALFKI has ever purchased.
SQL equivalent (3 JOINs)
The same query in SQL requires three table joins:
SELECT DISTINCT p.productName
FROM customers c
JOIN orders o ON c.customerID = o.customerID
JOIN order_details od ON o.orderID = od.orderID
JOIN products p ON od.productID = p.productID
WHERE c.customerID = 'ALFKI'
ORDER BY p.productName;In Cypher, you follow relationships directly. In SQL, you must explicitly join every table along the path.
Finding who bought products
Traverse backward from product to customers:
MATCH (c:Customer)-[:PLACED]->(:Order)-[:CONTAINS]->(p:Product {name: 'Chai'}) // (1)
RETURN DISTINCT c.name, c.city, c.country // (2)
ORDER BY c.name // (3)-
Reverse pattern - Start at Product, traverse back through orders to customers
-
Customer info - Return company details
-
Sort - By company name
Click Run to see all customers who have purchased Chai.
Analyzing customer favorites
Aggregate relationship properties across the path:
MATCH (c:Customer {id: 'ALFKI'})-[:PLACED]->(:Order)-[r:CONTAINS]->(p:Product)
RETURN p.name,
sum(r.quantity) AS totalQuantity,
count(DISTINCT r) AS numberOfOrders,
avg(r.unitPrice) AS avgPrice
ORDER BY totalQuantity DESC
LIMIT 10Click Run to see ALFKI’s favorite products by volume.
Understanding the aggregation
MATCH (c:Customer {id: 'ALFKI'})-[:PLACED]->(:Order)-[r:CONTAINS]->(p:Product) // (1)
RETURN p.name, // (2)
sum(r.quantity) AS totalQuantity, // (3)
count(DISTINCT r) AS numberOfOrders // (4)-
Capture relationship - Variable
raccesses CONTAINS properties -
Group by product - Implicitly groups by product name
-
Sum quantities - Total units ordered across all orders
-
Count orders - How many times this product was ordered
Measuring product popularity
Find the most popular products across all customers:
MATCH (:Customer)-[:PLACED]->(:Order)-[r:CONTAINS]->(p:Product)
RETURN p.name,
sum(r.quantity) AS totalQuantity,
count(DISTINCT r) AS timesOrdered
ORDER BY totalQuantity DESC
LIMIT 10Click Run to see your bestsellers.
Understanding product popularity query
MATCH (:Customer)-[:PLACED]->(:Order)-[r:CONTAINS]->(p:Product) // (1)
RETURN p.name, // (2)
sum(r.quantity) AS totalQuantity, // (3)
count(DISTINCT r) AS timesOrdered // (4)-
All customers - No filter, traverse entire graph
-
Group by product - Aggregate per product
-
Total quantity - Sum across all orders from all customers
-
Order count - How many individual orders included this product
Calculating customer spending
Calculate total spending using relationship properties:
MATCH (c:Customer)-[:PLACED]->(:Order)-[r:CONTAINS]->(:Product)
RETURN c.name,
sum(r.quantity * r.unitPrice) AS totalSpent,
count(DISTINCT r) AS itemsOrdered
ORDER BY totalSpent DESC
LIMIT 10Click Run to see your top customers by revenue.
Understanding customer spending query
MATCH (c:Customer)-[:PLACED]->(:Order)-[r:CONTAINS]->(:Product) // (1)
RETURN c.name, // (2)
sum(r.quantity * r.unitPrice) AS totalSpent, // (3)
count(DISTINCT r) AS itemsOrdered // (4)-
Traverse path - Customer through orders to products
-
Group by customer - Aggregate per customer
-
Calculate spending - Multiply quantity × unitPrice, sum total
-
Count items - Total distinct products ordered
Building the recommendation pattern
All these queries use the same fundamental pattern that powers recommendations:
%%{init: {
"theme": "base",
"themeVariables": {
"primaryColor": "#eef6f9",
"primaryBorderColor": "#c7e0ec",
"lineColor": "#94a3b8",
"fontFamily": "Public Sans, Arial, Helvetica, sans-serif"
}
}}%%
graph LR
Customer((Customer)):::primary -->|PLACED| Order((Order)):::highlight
Order -->|CONTAINS| Product((Product)):::forest
classDef primary fill:#eef6f9,stroke:#c7e0ec,stroke-width:1.25px,color:#0b5c7a
classDef highlight fill:#f4f5ff,stroke:#c7d2fe,stroke-width:1.25px,color:#3730a3
classDef forest fill:#edf6e8,stroke:#b7df9c,stroke-width:1.25px,color:#2f5d1e
linkStyle default stroke:#94a3b8,stroke-width:1.25pxTo recommend products:
-
Find products I bought:
(me:Customer)-[:PLACED]→(:Order)-[:CONTAINS]→(myProduct) -
Find customers who bought those products:
(myProduct)←[:CONTAINS]-(:Order)←[:PLACED]-(other:Customer) -
Find what THEY bought that I haven’t:
(other)-[:PLACED]→(:Order)-[:CONTAINS]→(rec:Product)
You’re one step away from the complete recommendation query!
SQL complexity: 3 JOINs required
Each of these queries requires 3 JOINs in SQL:
SELECT c.companyName,
SUM(od.quantity * od.unitPrice) AS totalSpent,
COUNT(DISTINCT od.orderDetailID) AS itemsOrdered
FROM customers c
JOIN orders o ON c.customerID = o.customerID
JOIN order_details od ON o.orderID = od.orderID
GROUP BY c.companyName
ORDER BY totalSpent DESC
LIMIT 10;Cypher simplicity: Direct traversal
The same query in Cypher:
MATCH (c:Customer)-[:PLACED]->(:Order)-[r:CONTAINS]->(:Product)
RETURN c.name,
sum(r.quantity * r.unitPrice) AS totalSpent,
count(DISTINCT r) AS itemsOrdered
ORDER BY totalSpent DESC
LIMIT 10The Cypher reads like the question. The SQL is machinery.
Summary
In this lesson, you learned about multi-hop traversals:
-
Two-hop patterns - Customer→Order→Product traverses the complete path
-
Bidirectional - Can traverse forward or backward through relationships
-
Relationship properties - Aggregate quantities, prices, and other connection data
-
Anonymous nodes - Use
(:Order)when you don’t need to reference the node -
Complex analytics - Product popularity, customer spending, favorite products
-
Foundation for recommendations - Same pattern powers the recommendation query
-
SQL comparison - Cypher is readable and performant vs complex JOINs
In the next lesson (optional), you can practice writing multi-hop queries.