Product co-purchase, customers also bought

Build an order-product-customer graph and surface the top items frequently bought with a target product

All recipes· graph· 5 minutesbeginnercypher

Product co-purchase, customers also bought

Objective

Replicate Amazon's "customers who bought this also bought" widget directly in SQL. With Cypher, the bipartite Customer→Order→Product graph collapses into a single pattern that finds co-purchased items and ranks them by support — useful for upsell, cross-sell, and merchandising rules.

Step 1: Create the graph

// Customers
MERGE (c1:Customer {name: "Sarah Chen"})
MERGE (c2:Customer {name: "Raj Patel"})
MERGE (c3:Customer {name: "Mia Rossi"})
MERGE (c4:Customer {name: "Leo Park"})
MERGE (c5:Customer {name: "Eli Tanaka"})

// Products
MERGE (p1:Product {sku: "ESP-01", name: "Espresso Beans 1kg", price: 24.00})
MERGE (p2:Product {sku: "GRD-02", name: "Burr Grinder",       price: 159.00})
MERGE (p3:Product {sku: "MIL-03", name: "Milk Frother",       price: 49.00})
MERGE (p4:Product {sku: "CUP-04", name: "Ceramic Cup Set",    price: 32.00})
MERGE (p5:Product {sku: "SCL-05", name: "Coffee Scale",       price: 39.00})
MERGE (p6:Product {sku: "FLT-06", name: "Paper Filters",      price: 8.00})

// Orders + line items (CONTAINS edges)
MERGE (o1:Order {id: 1001})-[:PLACED_BY]->(c1)
MERGE (o1)-[:CONTAINS]->(p1)
MERGE (o1)-[:CONTAINS]->(p2)
MERGE (o1)-[:CONTAINS]->(p4)

MERGE (o2:Order {id: 1002})-[:PLACED_BY]->(c2)
MERGE (o2)-[:CONTAINS]->(p1)
MERGE (o2)-[:CONTAINS]->(p2)
MERGE (o2)-[:CONTAINS]->(p5)

MERGE (o3:Order {id: 1003})-[:PLACED_BY]->(c3)
MERGE (o3)-[:CONTAINS]->(p1)
MERGE (o3)-[:CONTAINS]->(p3)

MERGE (o4:Order {id: 1004})-[:PLACED_BY]->(c4)
MERGE (o4)-[:CONTAINS]->(p1)
MERGE (o4)-[:CONTAINS]->(p2)

MERGE (o5:Order {id: 1005})-[:PLACED_BY]->(c5)
MERGE (o5)-[:CONTAINS]->(p1)
MERGE (o5)-[:CONTAINS]->(p6)
MERGE (o5)-[:CONTAINS]->(p5);

Step 2: Customers who bought espresso beans also bought…

// Co-occurrence: walk Order→Product to Order→other Product through the same order.
MATCH (target:Product {sku: "ESP-01"})<-[:CONTAINS]-(o:Order)-[:CONTAINS]->(other:Product)
WHERE other <> target
RETURN other.sku       AS sku,
       other.name      AS also_bought,
       count(o)        AS times_co_purchased
ORDER BY times_co_purchased DESC, sku;

What's happening

  • The bipartite hop (target)<-(o)->(other) finds every product sharing an order with the target.
  • count(o) produces market-basket support directly — no pivoting, no temp tables.
  • Cypher's pattern is symmetric: swap the SKU and you get a different recommender for free.
  • Adding more relationship types (e.g. :VIEWED, :WISHLISTED) extends the model without schema migrations — graph databases shine when you keep adding signals over time.
  • In SQL this is a self-join on order_items plus aggregation; with millions of rows it gets slow, while the graph version follows pointers and stays fast.

Try this next

MATCH (c:Customer)<-[:PLACED_BY]-(o:Order)-[:CONTAINS]->(p:Product)
RETURN c.name AS customer, sum(p.price) AS lifetime_value
ORDER BY lifetime_value DESC;
MATCH (p:Product)<-[:CONTAINS]-(o:Order)
RETURN p.name AS product, count(o) AS orders
ORDER BY orders DESC LIMIT 5;
MATCH (a:Product)<-[:CONTAINS]-(o:Order)-[:CONTAINS]->(b:Product)
WHERE a.sku < b.sku
RETURN a.name, b.name, count(o) AS support
ORDER BY support DESC LIMIT 10;

Tags

graphcypherecommercebeginner

Run this on your own machine

Install SynapCores Community Edition free, paste the SQL or Cypher above into the bundled web UI, and watch it run.

Download Free CE