← Back to Amazon Dataset

Neo4j Implementation

Deployment and Query logic for Neo4j Community 5.26.0.

Schema Diagram and Graph Statistics

Amazon Neo4j Schema

Node Statistics

Node Label Count
Product 23,643,222
Category 2,487,388
Review 230,139,802
Image 17,081,240
ReviewImage 6,538,802
Reviewer 43,249,276

Edge Statistics

Relationship Count
(:Review)-[:REVIEWS]->(:Product) 230,139,802
(:Product)-[:HAS_PRODUCT_IMAGE]->(:Image) 22,531,444
(:Product)-[:ALSO_BUY]->(:Product) 97,123,542
(:Reviewer)-[:WROTE]->(:Review) 230,139,802
(:Review)-[:HAS_REVIEW_IMAGE]->(:Image) 6,840,462
(:Product)-[:IN_CATEGORY]->(:Category) 62,832,310
(:Product)-[:ALSO_VIEW]->(:Product) 92,658,319

Disk space: 173.09 GiB

Total import time: 56,061.585 Seconds

Data Loading

Overview

Data import is handled in two stages: node creation followed by sequential edge ingestion. We put all the JSON files under the data import directory of Neo4j. This directory can be set in the neo4j.conf file using the parameter dbms.directories.import.

For the implementation of Amazon Open Dataset on Neo4j, we used the original files downloaded from their website. We will read the JSON files unpacked from the downloaded file to complete node and edge imports.

Create Indices

To speed up the import process and future queries, we create B+-tree indices for some node labels.

cypher
CREATE CONSTRAINT IF NOT EXISTS FOR (p:Product) REQUIRE p.asin IS UNIQUE;
CREATE CONSTRAINT IF NOT EXISTS FOR (i:Image) REQUIRE i.url IS UNIQUE;
CREATE CONSTRAINT IF NOT EXISTS FOR (u:Reviewer) REQUIRE u.reviewerID IS UNIQUE;
CREATE CONSTRAINT IF NOT EXISTS FOR (c:Category) REQUIRE c.name IS UNIQUE;
CREATE CONSTRAINT IF NOT EXISTS FOR (ri:ReviewImage) REQUIRE ri.url IS UNIQUE;

Define Product Related Nodes and Edges (16,365.674 seconds)

First we define the Product nodes, Image nodes, Category nodes, HAS_PRODUCT_IMAGE edges, ALSO_BUY edges, ALSO_VIEW edges, and IN_CATEGORY edges.

Note that in the following code block, the file path in the second line is a placeholder. You should replace "path_to_folder" to a specific Amazon product category being listed in the original dataset, and "meta_file" with the actual metadata json file name. The file name will start with "meta".

cypher
CALL apoc.periodic.iterate(
  'CALL apoc.load.json("file:///amazon/amazon_data/path_to_folder/meta_file") YIELD value RETURN value',
  '
  MERGE (p:Product {asin: value.asin})
  // Parse price range
  WITH p, value, split(REPLACE(value.price, "$", ""), " - ") AS priceRange
  SET p.title       = coalesce(value.title, ""),
      p.brand       = coalesce(value.brand, ""),
      p.description = apoc.text.join(value.description, ", "),
      p.price_min   = toFloat(priceRange[0]),
      p.price_max   = CASE
                        WHEN size(priceRange) > 1 THEN toFloat(priceRange[1])
                        ELSE toFloat(priceRange[0])
                      END
  // Handle image URLs
  WITH p, value
  FOREACH (_ IN CASE WHEN value.imageURL IS NOT NULL AND value.imageURLHighRes IS NOT NULL AND size(value.imageURL) = size(value.imageURLHighRes) THEN [1] ELSE [] END |
    FOREACH (pair IN apoc.coll.zip(value.imageURL, value.imageURLHighRes) |
      MERGE (img:Image {url: pair[0]})
      SET img.highRes = pair[1]
      MERGE (p)-[:HAS_PRODUCT_IMAGE]->(img)
    )
  )
  // ALSO_BUY edges
  FOREACH (related_asin IN coalesce(value.also_buy, []) |
    MERGE (r:Product {asin: related_asin})
    MERGE (p)-[:ALSO_BUY]->(r)
  )
  // ALSO_VIEW edges
  FOREACH (view_asin IN coalesce(value.also_view, []) |
    MERGE (v:Product {asin: view_asin})
    MERGE (p)-[:ALSO_VIEW]->(v)
  )
// CATEGORY nodes and IN_CATEGORY edges

  FOREACH (cat IN coalesce(value.category, []) |

  MERGE (c:Category {name: apoc.text.replace(cat, "&", "&")})

  MERGE (p)-[:IN_CATEGORY]->(c)
)
  ',
  {batchSize: 5000, iterateList: true, parallel: false}
)

Product-related node and edge load time by category

Category Load Time (seconds)
Gift Cards 4.931
Magazine Subscriptions 5.392
Prime Pantry 13.717
Luxury Beauty 20.051
All Beauty 20.657
Digital Music 21.487
Software 15.768
Amazon Fashion 99.480
Video Games 106.527
Movies and TV 207.394
Kindle Store 543.526
Grocery and Gourmet Food 281.312
Industrial and Scientific 125.058
CDs and Vinyl 331.992
Appliances 34.394
Arts, Crafts and Sewing 281.256
Musical Instruments 108.023
Pet Supplies 227.203
Cell Phones and Accessories 438.375
Books 2,665.430
Patio, Lawn and Garden 243.615
Office Products 374.614
Toys and Games 698.900
Clothing, Shoes and Jewelry 3,592.958
Electronics 1,504.732
Tools and Home Improvement 866.487
Home and Kitchen 1,402.899
Automotive 915.697
Sports and Outdoors 1,213.799

Define Review Related Nodes and Edges (39,695.911 seconds)

Then we define the Review nodes, ReviewImage nodes, Reviewer nodes, HAS_REVIEW_IMAGE edges, REVIEWS edges, and WROTE edges.

Note that in the following code block, the file path in the second line is a placeholder. You should replace "path_to_folder" to a specific Amazon product category being listed in the original dataset, and "review_file" with the actual review json file name. The file name will simply be "[CATEGORY].json".

cypher
CALL apoc.periodic.iterate(
  '
  CALL apoc.load.json("file:///amazon/amazon_data/path_to_folder/review_file") YIELD value
  RETURN value
  ',
  '
  CREATE (r:Review {
    reviewerID: value.reviewerID,
    asin: value.asin,
    unixReviewTime: value.unixReviewTime
  })

  SET r.reviewDatetime = datetime({epochMillis: apoc.date.parse(value.reviewTime, "ms", "MM dd, yyyy")}),
      r.summary = coalesce(value.summary, ""),
      r.reviewText = coalesce(value.reviewText, ""),
      r.verified = value.verified,
      r.vote = toInteger(value.vote),
      r.overall = toInteger(value.overall)

  WITH r, value

  MERGE (p:Product {asin: value.asin})
  MERGE (r)-[:REVIEWS]->(p)

  WITH r, value

  MERGE (u:Reviewer {reviewerID: value.reviewerID})
  SET u.name = coalesce(value.reviewerName, "")
  MERGE (u)-[:WROTE]->(r)

  WITH r, value

  FOREACH (img_url IN coalesce(value.image, []) |
    MERGE (img:ReviewImage {url: img_url})
    MERGE (r)-[:HAS_REVIEW_IMAGE]->(img)
  )
  ',
  {batchSize: 1000, iterateList: true, parallel: false}
)

Review-related node and edge load time by category

Category Load Time (seconds)
Magazine Subscriptions 6.614
Gift Cards 10.147
All Beauty 24.967
Prime Pantry 31.382
Appliances 42.769
Luxury Beauty 39.892
Software 34.250
Amazon Fashion 62.413
Digital Music 115.677
Industrial and Scientific 143.358
Musical Instruments 115.777
Arts, Crafts and Sewing 230.586
Video Games 197.080
Grocery and Gourmet Food 339.453
Patio, Lawn and Garden 372.680
Office Products 477.029
Pet Supplies 513.814
Automotive 717.608
CDs and Vinyl 392.255
Toys and Games 889.196
Kindle Store 561.933
Tools and Home Improvement 957.439
Cell Phones and Accessories 1,074.606
Movies and TV 1,045.598
Sports and Outdoors 1,590.900
Home and Kitchen 3,165.101
Electronics 4,249.834
Clothing, Shoes and Jewelry 7,167.608
Books 15,125.945

Queries

Select a query below to view its logic and description.

1. Neighborhood
2. Path
3. Pattern
4. Aggregation
5. Analytics
6. Hybrid
7. Others
No queries in this category.

Q1. People Who Bought This Also Bought (Seed Expansion)

Subcategory: T1.1 One-Hop Neighbors

This query expands one hop from the seed products through ALSO_BUY edges, excludes the seeds themselves, and ranks neighboring products by how many seed products link to them.

cypher
MATCH (seed:SeedProduct)-[:ALSO_BUY]->(other:Product)
WHERE NOT other:SeedProduct   // exclude seeds themselves
RETURN other.asin AS ASIN,
    coalesce(other.title, other.asin) AS CoBuyProduct,
    count(DISTINCT seed) AS LinkedSeeds
ORDER BY LinkedSeeds DESC
LIMIT 200;

Equivalent query in GQL standard:

gql
MATCH (seed:SeedProduct)-[:ALSO_BUY]->(other:Product)
WHERE NOT other:SeedProduct
RETURN other.asin AS ASIN,
    coalesce(other.title, other.asin) AS CoBuyProduct,
    count(DISTINCT seed) AS LinkedSeeds
ORDER BY LinkedSeeds DESC
LIMIT 200;

Q2. Two-Hop Co-Buy Recommendations

Subcategory: T1.2 k-Hop Neighborhood

This query expands from a selected product through two outgoing ALSO_BUY hops and returns distinct second-hop products.

cypher
MATCH (:Product {asin: $asin})-[:ALSO_BUY]->(:Product)-[:ALSO_BUY]->(rec:Product)
WHERE rec.asin <> $asin
RETURN DISTINCT rec.asin AS ASIN, coalesce(rec.title, rec.asin) AS Product
ORDER BY Product
LIMIT $limit;

Equivalent query in GQL standard:

gql
MATCH (:Product {asin: $asin})-[:ALSO_BUY]->(:Product)-[:ALSO_BUY]->(rec:Product)
WHERE rec.asin <> $asin
RETURN DISTINCT rec.asin AS ASIN, coalesce(rec.title, rec.asin) AS Product
ORDER BY Product
LIMIT $limit;

Q3. Products Exactly Two Co-Buy Hops Away

Subcategory: T1.3 Exact kth-Hop Neighbors

This query returns products exactly two ALSO_BUY hops from a selected product, excluding direct one-hop recommendations.

cypher
MATCH (seed:Product {asin: $asin})-[:ALSO_BUY]->(:Product)-[:ALSO_BUY]->(second:Product)
WHERE second <> seed
  AND NOT (seed)-[:ALSO_BUY]->(second)
RETURN DISTINCT second.asin AS ASIN, coalesce(second.title, second.asin) AS Product
ORDER BY Product
LIMIT $limit;

Equivalent query in GQL standard:

gql
MATCH (seed:Product {asin: $asin})-[:ALSO_BUY]->(:Product)-[:ALSO_BUY]->(second:Product)
WHERE second <> seed
  AND NOT (seed)-[:ALSO_BUY]->(second)
RETURN DISTINCT second.asin AS ASIN, coalesce(second.title, second.asin) AS Product
ORDER BY Product
LIMIT $limit;

Q4. Highly Rated Co-Buy Neighbors

Subcategory: T1.4 Neighborhood with Property Filter

This query filters one-hop co-buy neighbors to products whose review average meets a minimum rating threshold.

cypher
MATCH (:Product {asin: $asin})-[:ALSO_BUY]->(p:Product)<-[:REVIEWS]-(r:Review)
WHERE r.overall IS NOT NULL
WITH p, avg(r.overall) AS AvgRating, count(r) AS ReviewCount
WHERE AvgRating >= $minRating
RETURN p.asin AS ASIN, coalesce(p.title, p.asin) AS Product, round(AvgRating, 2) AS AvgRating, ReviewCount
ORDER BY AvgRating DESC, ReviewCount DESC
LIMIT $limit;

Equivalent query in GQL standard:

gql
MATCH (:Product {asin: $asin})-[:ALSO_BUY]->(p:Product)<-[:REVIEWS]-(r:Review)
WHERE r.overall IS NOT NULL
WITH p, avg(r.overall) AS AvgRating, count(r) AS ReviewCount
WHERE AvgRating >= $minRating
RETURN p.asin AS ASIN, coalesce(p.title, p.asin) AS Product, round(AvgRating, 2) AS AvgRating, ReviewCount
ORDER BY AvgRating DESC, ReviewCount DESC
LIMIT $limit;

Q5. High-Rating Helpful Review Records

Subcategory: T2.2 Edge Filtering

Reviews are modeled as nodes between reviewers and products, so this query filters review-edge records by rating and helpful-vote properties.

cypher
MATCH (u:Reviewer)-[:WROTE]->(r:Review)-[:REVIEWS]->(p:Product)
WHERE r.overall >= $minRating
  AND coalesce(r.vote, 0) >= $minHelpfulVotes
RETURN u.reviewerID AS ReviewerID, p.asin AS ASIN, r.overall AS Rating, r.vote AS HelpfulVotes
ORDER BY HelpfulVotes DESC, Rating DESC
LIMIT $limit;

Equivalent query in GQL standard:

gql
MATCH (u:Reviewer)-[:WROTE]->(r:Review)-[:REVIEWS]->(p:Product)
WHERE r.overall >= $minRating
  AND coalesce(r.vote, 0) >= $minHelpfulVotes
RETURN u.reviewerID AS ReviewerID, p.asin AS ASIN, r.overall AS Rating, r.vote AS HelpfulVotes
ORDER BY HelpfulVotes DESC, Rating DESC
LIMIT $limit;

Q6. Reachability Between Products in the Co-Buy Graph

Subcategory: T2.3 Reachability between Two Nodes

This query tests whether one product can reach another product through up to four ALSO_BUY hops.

cypher
MATCH p = (:Product {asin: $sourceAsin})-[:ALSO_BUY*1..4]->(:Product {asin: $targetAsin})
RETURN count(p) > 0 AS reachable
LIMIT 1;

Equivalent query in GQL standard:

gql
MATCH p = ANY (:Product {asin: $sourceAsin})-[:ALSO_BUY]->{1,4}(:Product {asin: $targetAsin})
RETURN true AS reachable;

Q7. Shortest Co-Buy Path Between Products

Subcategory: T2.4 Shortest Path

This query finds the shortest directed co-buy path between two products within a bounded depth.

cypher
MATCH p = shortestPath((src:Product {asin: $sourceAsin})-[:ALSO_BUY*1..6]->(dst:Product {asin: $targetAsin}))
RETURN [n IN nodes(p) | n.asin] AS ProductPath, length(p) AS HopCount;

Equivalent query in GQL standard:

gql
MATCH p = SHORTEST (:Product {asin: $sourceAsin})-[:ALSO_BUY]->{1,6}(:Product {asin: $targetAsin})
RETURN p;

Q8. Simple Co-Buy Paths up to Three Hops

Subcategory: T2.5 All Simple Paths up to Length k

This query enumerates acyclic co-buy paths between two products up to three hops.

cypher
MATCH p = (src:Product {asin: $sourceAsin})-[:ALSO_BUY*1..3]->(dst:Product {asin: $targetAsin})
WHERE all(n IN nodes(p) WHERE single(m IN nodes(p) WHERE m = n))
RETURN [n IN nodes(p) | n.asin] AS ProductPath, length(p) AS HopCount
ORDER BY HopCount
LIMIT $limit;

Equivalent query in GQL standard:

gql
MATCH p = (src:Product {asin: $sourceAsin})-[:ALSO_BUY]->{1,3}(dst:Product {asin: $targetAsin})
RETURN p
LIMIT $limit;

Q9. Top Weighted Co-Buy Paths by Product Price

Subcategory: T2.6 top-K Weighted Paths

This query ranks bounded co-buy paths by the sum of product prices along each path.

cypher
MATCH p = (:Product {asin: $sourceAsin})-[:ALSO_BUY*1..3]->(:Product {asin: $targetAsin})
WITH p, reduce(total = 0.0, n IN nodes(p) | total + coalesce(n.price, 0.0)) AS PathPrice
RETURN [n IN nodes(p) | n.asin] AS ProductPath, length(p) AS HopCount, PathPrice
ORDER BY PathPrice DESC
LIMIT $k;

Equivalent query in GQL standard:

gql
MATCH p = (:Product {asin: $sourceAsin})-[:ALSO_BUY]->{1,3}(:Product {asin: $targetAsin})
RETURN p
LIMIT $k;

Q10. Co-Buy Product Triangles

Subcategory: T3.1 Triangle Detection

This query finds directed triangles in the product co-buy graph.

cypher
MATCH (p1:Product)-[:ALSO_BUY]->(p2:Product)-[:ALSO_BUY]->(p3:Product)-[:ALSO_BUY]->(p1)
WHERE p1.asin < p2.asin AND p2.asin < p3.asin
RETURN p1.asin AS Product1, p2.asin AS Product2, p3.asin AS Product3
LIMIT $limit;

Equivalent query in GQL standard:

gql
MATCH (p1:Product)-[:ALSO_BUY]->(p2:Product)-[:ALSO_BUY]->(p3:Product)-[:ALSO_BUY]->(p1)
WHERE p1.asin < p2.asin AND p2.asin < p3.asin
RETURN p1.asin AS Product1, p2.asin AS Product2, p3.asin AS Product3
LIMIT $limit;

Q11. Four-Product Co-Buy Cycles

Subcategory: T3.2 Square Detection

This query searches for four-product cycles formed by directed ALSO_BUY relationships, where the second and fourth products must be different.

cypher
MATCH (p1:Product)-[:ALSO_BUY]->(p2:Product)-[:ALSO_BUY]->(p3:Product)-[:ALSO_BUY]->(p4:Product)-[:ALSO_BUY]->(p1)
WHERE p1.asin < p2.asin AND p1.asin < p3.asin AND p1.asin < p4.asin AND p2 <> p4
RETURN p1.asin AS Product1, p2.asin AS Product2, p3.asin AS Product3, p4.asin AS Product4
LIMIT $limit;

Equivalent query in GQL standard:

gql
MATCH (p1:Product)-[:ALSO_BUY]->(p2:Product)-[:ALSO_BUY]->(p3:Product)-[:ALSO_BUY]->(p4:Product)-[:ALSO_BUY]->(p1)
WHERE p1.asin < p2.asin AND p1.asin < p3.asin AND p1.asin < p4.asin AND p2 <> p4
RETURN p1.asin AS Product1, p2.asin AS Product2, p3.asin AS Product3, p4.asin AS Product4
LIMIT $limit;

Q12. Products with Many Co-Buy Recommendations

Subcategory: T3.3 Star Pattern Query

This query finds star-like products that point to many co-buy recommendation neighbors.

cypher
MATCH (center:Product)-[:ALSO_BUY]->(neighbor:Product)
WITH center, count(DISTINCT neighbor) AS RecommendationCount
WHERE RecommendationCount >= $minDegree
RETURN center.asin AS ASIN, coalesce(center.title, center.asin) AS Product, RecommendationCount
ORDER BY RecommendationCount DESC
LIMIT $limit;

Equivalent query in GQL standard:

gql
MATCH (center:Product)-[:ALSO_BUY]->(neighbor:Product)
WITH center, count(DISTINCT neighbor) AS RecommendationCount
WHERE RecommendationCount >= $minDegree
RETURN center.asin AS ASIN, coalesce(center.title, center.asin) AS Product, RecommendationCount
ORDER BY RecommendationCount DESC
LIMIT $limit;

Q13. Four-Product Co-Buy Cliques

Subcategory: T3.4 4-Clique

This query treats co-buy links as undirected and finds four products where every pair is connected by an ALSO_BUY relationship.

cypher
MATCH (p1:Product)-[:ALSO_BUY]-(p2:Product),
      (p1)-[:ALSO_BUY]-(p3:Product),
      (p1)-[:ALSO_BUY]-(p4:Product),
      (p2)-[:ALSO_BUY]-(p3),
      (p2)-[:ALSO_BUY]-(p4),
      (p3)-[:ALSO_BUY]-(p4)
WHERE p1.asin < p2.asin AND p2.asin < p3.asin AND p3.asin < p4.asin
RETURN p1.asin AS Product1, p2.asin AS Product2, p3.asin AS Product3, p4.asin AS Product4
LIMIT $limit;

Equivalent query in GQL standard:

gql
MATCH (p1:Product)-[:ALSO_BUY]-(p2:Product),
      (p1)-[:ALSO_BUY]-(p3:Product),
      (p1)-[:ALSO_BUY]-(p4:Product),
      (p2)-[:ALSO_BUY]-(p3),
      (p2)-[:ALSO_BUY]-(p4),
      (p3)-[:ALSO_BUY]-(p4)
WHERE p1.asin < p2.asin AND p2.asin < p3.asin AND p3.asin < p4.asin
RETURN p1.asin AS Product1, p2.asin AS Product2, p3.asin AS Product3, p4.asin AS Product4
LIMIT $limit;

Q14. Reviewers Who Reviewed the Same Product

Subcategory: T3.5 Two-hop Join Pattern

This query joins reviewers through products they have both reviewed.

cypher
MATCH (u1:Reviewer)-[:WROTE]->(:Review)-[:REVIEWS]->(p:Product)<-[:REVIEWS]-(:Review)<-[:WROTE]-(u2:Reviewer)
WHERE u1.reviewerID < u2.reviewerID
RETURN u1.reviewerID AS Reviewer1, u2.reviewerID AS Reviewer2, count(DISTINCT p) AS SharedProductCount
ORDER BY SharedProductCount DESC
LIMIT $limit;

Equivalent query in GQL standard:

gql
MATCH (u1:Reviewer)-[:WROTE]->(:Review)-[:REVIEWS]->(p:Product)<-[:REVIEWS]-(:Review)<-[:WROTE]-(u2:Reviewer)
WHERE u1.reviewerID < u2.reviewerID
RETURN u1.reviewerID AS Reviewer1, u2.reviewerID AS Reviewer2, count(DISTINCT p) AS SharedProductCount
ORDER BY SharedProductCount DESC
LIMIT $limit;

Q15. Reviewers Who Write the Longest Reviews

Subcategory: T4.1 Ungrouped Node Degree Statistics by Label

This query ranks all reviewers by the average length of their review text, returning reviewer identity, review count, and average review length.

cypher
MATCH (u:Reviewer)-[:WROTE]->(r:Review)
RETURN u.reviewerID AS ReviewerID,
    coalesce(u.name, u.reviewerID) AS ReviewerName,
    count(r) AS ReviewCount,
    avg(size(r.reviewText)) AS AvgReviewLength
ORDER BY AvgReviewLength DESC
LIMIT 200;

Equivalent query in GQL standard:

gql
MATCH (u:Reviewer)-[:WROTE]->(r:Review)
RETURN u.reviewerID AS ReviewerID,
    coalesce(u.name, u.reviewerID) AS ReviewerName,
    count(r) AS ReviewCount,
    avg(size(r.reviewText)) AS AvgReviewLength
ORDER BY AvgReviewLength DESC
LIMIT 200;

Q16. Products with the Most Reviews

Subcategory: T4.2 Grouped Node Degree Statistics by Label

This query finds the 200 products with the highest number of reviews, marks them as seed products, and returns each product's identifier, display title, and review count.

cypher
MATCH (p:Product)<-[:REVIEWS]-(r:Review)
WITH p, count(r) AS ReviewCount
ORDER BY ReviewCount DESC
LIMIT 200
SET p:SeedProduct
RETURN p.asin AS ASIN, coalesce(p.title, p.asin) AS Product, ReviewCount;

Equivalent query in GQL standard:

gql
MATCH (p:Product)<-[:REVIEWS]-(r:Review)
WITH p, count(r) AS ReviewCount
ORDER BY ReviewCount DESC
LIMIT 200
SET p:SeedProduct
RETURN p.asin AS ASIN, coalesce(p.title, p.asin) AS Product, ReviewCount;

Q17. Highly Rated Products with Many Reviews

Subcategory: T4.2 Grouped Node Degree Statistics by Label

This query finds products with at least 50 reviews and an average rating of at least 4.0, returning the most-reviewed high-quality products first.

cypher
MATCH (p:Product)<-[:REVIEWS]-(r:Review)
WHERE r.overall IS NOT NULL
WITH p, avg(r.overall) AS AvgRating, count(r) AS ReviewCount
WHERE ReviewCount >= 50 AND AvgRating >= 4.0
RETURN p.asin AS ASIN,
    coalesce(p.title, p.asin) AS Product,
    round(AvgRating, 2) AS AvgRating,
    ReviewCount
ORDER BY ReviewCount DESC, AvgRating DESC
LIMIT 200;

Equivalent query in GQL standard:

gql
MATCH (p:Product)<-[:REVIEWS]-(r:Review)
WHERE r.overall IS NOT NULL
WITH p, avg(r.overall) AS AvgRating, count(r) AS ReviewCount
WHERE ReviewCount >= 50 AND AvgRating >= 4.0
RETURN p.asin AS ASIN,
    coalesce(p.title, p.asin) AS Product,
    round(AvgRating, 2) AS AvgRating,
    ReviewCount
ORDER BY ReviewCount DESC, AvgRating DESC
LIMIT 200;

Q18. Products with the Most Images

Subcategory: T4.2 Grouped Node Degree Statistics by Label

This query counts product image neighbors for each product and returns the products with the largest number of attached images.

cypher
MATCH (p:Product)-[:HAS_PRODUCT_IMAGE]->(i:Image)
WITH p.asin AS ASIN, coalesce(p.title, p.asin) AS Product, count(i) AS ImageCount
RETURN ASIN, Product, ImageCount
ORDER BY ImageCount DESC
LIMIT 200;

Equivalent query in GQL standard:

gql
MATCH (p:Product)-[:HAS_PRODUCT_IMAGE]->(i:Image)
WITH p.asin AS ASIN, coalesce(p.title, p.asin) AS Product, count(i) AS ImageCount
RETURN ASIN, Product, ImageCount
ORDER BY ImageCount DESC
LIMIT 200;

Q19. Review Edge Rating Summary

Subcategory: T4.3 Ungrouped Edge Count and Property Statistics

Reviews are modeled as nodes between reviewers and products, so this query summarizes review-edge records and their rating properties.

cypher
MATCH (:Reviewer)-[:WROTE]->(r:Review)-[:REVIEWS]->(:Product)
RETURN count(r) AS ReviewCount,
       avg(r.overall) AS AvgRating,
       min(r.overall) AS MinRating,
       max(r.overall) AS MaxRating;

Equivalent query in GQL standard:

gql
MATCH (:Reviewer)-[:WROTE]->(r:Review)-[:REVIEWS]->(:Product)
RETURN count(r) AS ReviewCount,
       avg(r.overall) AS AvgRating,
       min(r.overall) AS MinRating,
       max(r.overall) AS MaxRating;

Q20. Category-Level Review Rating Summary

Subcategory: T4.4 Grouped Edge Count and Property Statistics

This query groups review records by product category and summarizes ratings within each category.

cypher
MATCH (c:Category)<-[:IN_CATEGORY]-(p:Product)<-[:REVIEWS]-(r:Review)
RETURN c.name AS Category,
       count(r) AS ReviewCount,
       round(avg(r.overall), 2) AS AvgRating
ORDER BY ReviewCount DESC
LIMIT $limit;

Equivalent query in GQL standard:

gql
MATCH (c:Category)<-[:IN_CATEGORY]-(p:Product)<-[:REVIEWS]-(r:Review)
RETURN c.name AS Category,
       count(r) AS ReviewCount,
       round(avg(r.overall), 2) AS AvgRating
ORDER BY ReviewCount DESC
LIMIT $limit;

Q21. Co-Buy Path Counts by Length

Subcategory: T4.5 Path Counts by Length

This query counts bounded ALSO_BUY paths from a selected product and groups them by path length.

cypher
MATCH path = (:Product {asin: $asin})-[:ALSO_BUY*1..3]->(target:Product)
RETURN length(path) AS PathLength,
       count(path) AS PathCount,
       count(DISTINCT target) AS DistinctTargetCount
ORDER BY PathLength;

Equivalent query in GQL standard:

gql
MATCH path = (:Product {asin: $asin})-[:ALSO_BUY]->{1,3}(target:Product)
RETURN length(path) AS PathLength,
       count(path) AS PathCount,
       count(DISTINCT target) AS DistinctTargetCount
ORDER BY PathLength;

Q22. Co-Buy Path Counts by Length and Endpoint Category

Subcategory: T4.6 Grouped Path Counts by Length and Endpoint Type

This query groups co-buy paths by path length and by the category of the endpoint product.

cypher
MATCH path = (:Product {asin: $asin})-[:ALSO_BUY*1..3]->(target:Product)-[:IN_CATEGORY]->(c:Category)
RETURN length(path) AS PathLength,
       c.name AS EndpointCategory,
       count(path) AS PathCount,
       count(DISTINCT target) AS EndpointProductCount
ORDER BY PathLength, PathCount DESC
LIMIT $limit;

Equivalent query in GQL standard:

gql
MATCH path = (:Product {asin: $asin})-[:ALSO_BUY]->{1,3}(target:Product)-[:IN_CATEGORY]->(c:Category)
RETURN length(path) AS PathLength,
       c.name AS EndpointCategory,
       count(path) AS PathCount,
       count(DISTINCT target) AS EndpointProductCount
ORDER BY PathLength, PathCount DESC
LIMIT $limit;

Q23. Products with Both Many Reviews and Images

Subcategory: T4.7 Union and Intersection

This query intersects products with many reviews and products with multiple images to find items with both strong review and media signals.

cypher
MATCH (p:Product)<-[:REVIEWS]-(r:Review)
WITH p, count(r) AS ReviewCount
WHERE ReviewCount >= $minReviews
WITH collect(p) AS ReviewedProducts
MATCH (p:Product)-[:HAS_PRODUCT_IMAGE]->(i:Image)
WITH ReviewedProducts, p, count(i) AS ImageCount
WHERE p IN ReviewedProducts AND ImageCount >= $minImages
RETURN p.asin AS ASIN, coalesce(p.title, p.asin) AS Product, ImageCount
ORDER BY ImageCount DESC
LIMIT $limit;

Equivalent query in GQL standard:

gql
MATCH (p:Product)<-[:REVIEWS]-(r:Review)
WITH p, count(r) AS ReviewCount
WHERE ReviewCount >= $minReviews
WITH collect(p) AS ReviewedProducts
MATCH (p:Product)-[:HAS_PRODUCT_IMAGE]->(i:Image)
WITH ReviewedProducts, p, count(i) AS ImageCount
WHERE p IN ReviewedProducts AND ImageCount >= $minImages
RETURN p.asin AS ASIN, coalesce(p.title, p.asin) AS Product, ImageCount
ORDER BY ImageCount DESC
LIMIT $limit;

Q24. Top Products by Co-Buy Degree Centrality

Subcategory: T5.1 Degree Centrality

This query computes degree centrality directly by counting products connected through incoming or outgoing co-buy edges.

cypher
MATCH (p:Product)-[:ALSO_BUY]-(neighbor:Product)
RETURN p.asin AS ASIN,
       coalesce(p.title, p.asin) AS Product,
       count(DISTINCT neighbor) AS CoBuyDegree
ORDER BY CoBuyDegree DESC
LIMIT $limit;

Equivalent query in GQL standard:

gql
MATCH (p:Product)-[:ALSO_BUY]-(neighbor:Product)
RETURN p.asin AS ASIN,
       coalesce(p.title, p.asin) AS Product,
       count(DISTINCT neighbor) AS CoBuyDegree
ORDER BY CoBuyDegree DESC
LIMIT $limit;

Q25. PageRank on the Co-Buy Product Graph

Subcategory: T5.2 PageRank

This query projects the product co-buy graph and runs PageRank to rank influential products.

cypher
CALL gds.graph.project(
  'AmazonCoBuyPageRank',
  'Product',
  {ALSO_BUY: {type: 'ALSO_BUY', orientation: 'NATURAL'}}
);

CALL gds.pageRank.stream('AmazonCoBuyPageRank')
YIELD nodeId, score
RETURN gds.util.asNode(nodeId).asin AS ASIN,
       gds.util.asNode(nodeId).title AS Product,
       score
ORDER BY score DESC
LIMIT $limit;
Note: Cannot be expressed in standard GQL because Graph Data Science (GDS) procedures and PageRank are not supported by the current GQL standard.

Q26. Weakly Connected Components of the Co-Buy Graph

Subcategory: T5.3 Connected Components / Weakly Connected Components

This query treats co-buy links as undirected and reports the largest weakly connected components.

cypher
CALL gds.graph.project(
  'AmazonCoBuyWcc',
  'Product',
  {ALSO_BUY: {type: 'ALSO_BUY', orientation: 'UNDIRECTED'}}
);

CALL gds.wcc.stream('AmazonCoBuyWcc')
YIELD nodeId, componentId
RETURN componentId, count(*) AS ComponentSize
ORDER BY ComponentSize DESC
LIMIT $limit;
Note: Cannot be expressed in standard GQL because Graph Data Science (GDS) procedures and connected-components algorithms are not supported by the current GQL standard.

Q27. Strongly Connected Components of the Co-Buy Graph

Subcategory: T5.4 Strongly Connected Components

This query runs strongly connected components on the directed co-buy product graph.

cypher
CALL gds.graph.project(
  'AmazonCoBuyScc',
  'Product',
  {ALSO_BUY: {type: 'ALSO_BUY', orientation: 'NATURAL'}}
);

CALL gds.scc.stream('AmazonCoBuyScc')
YIELD nodeId, componentId
RETURN componentId, count(*) AS ComponentSize
ORDER BY ComponentSize DESC
LIMIT $limit;
Note: Cannot be expressed in standard GQL because Graph Data Science (GDS) procedures and strongly connected component algorithms are not supported by the current GQL standard.

Q28. Louvain Communities in the Co-Buy Graph

Subcategory: T5.5 Community Detection

This query detects communities of products in the undirected co-buy graph using Louvain.

cypher
CALL gds.graph.project(
  'AmazonCoBuyLouvain',
  'Product',
  {ALSO_BUY: {type: 'ALSO_BUY', orientation: 'UNDIRECTED'}}
);

CALL gds.louvain.stream('AmazonCoBuyLouvain')
YIELD nodeId, communityId
RETURN communityId, count(*) AS CommunitySize
ORDER BY CommunitySize DESC
LIMIT $limit;
Note: Cannot be expressed in standard GQL because Graph Data Science (GDS) procedures and community detection algorithms are not supported by the current GQL standard.

Q29. Second-Hop Co-Buy Influence (Ripple Effect)

Subcategory: T2.1 Node Filtering (Single Node Paths); T3.6 Line Pattern; T4.1 Ungrouped Node Degree Statistics by Label

This query starts from the most-reviewed seed product, follows two ALSO_BUY hops, and ranks second-hop products by how many first-hop neighbors connect back to them.

cypher
// Step 1: Pick the single most reviewed SeedProduct
MATCH (seed:SeedProduct)
WITH seed, COUNT { (seed)<-[:REVIEWS]-() } AS ReviewCount
ORDER BY ReviewCount DESC
LIMIT 1

// Step 2: First-hop co-buy neighbors
MATCH (seed)-[:ALSO_BUY]->(firstHop:Product)
WHERE NOT firstHop:SeedProduct   // exclude seed set

// Step 3: Second-hop neighbors (co-buys of firstHop)
MATCH (firstHop)-[:ALSO_BUY]->(secondHop:Product)
WHERE secondHop <> seed
RETURN secondHop.asin AS ASIN,
    coalesce(secondHop.title, secondHop.asin) AS SecondHopProduct,
    count(DISTINCT firstHop) AS SharedNeighbors
ORDER BY SharedNeighbors DESC
LIMIT 200;

Equivalent query in GQL standard:

gql
MATCH (seed:SeedProduct)<-[:REVIEWS]-(review:Review)
WITH seed, count(review) AS ReviewCount
ORDER BY ReviewCount DESC
LIMIT 1
MATCH (seed)-[:ALSO_BUY]->(firstHop:Product)
WHERE NOT firstHop:SeedProduct
MATCH (firstHop)-[:ALSO_BUY]->(secondHop:Product)
WHERE secondHop <> seed
RETURN secondHop.asin AS ASIN,
    coalesce(secondHop.title, secondHop.asin) AS SecondHopProduct,
    count(DISTINCT firstHop) AS SharedNeighbors
ORDER BY SharedNeighbors DESC
LIMIT 200;

Q30. Are Reviews with More Images More Helpful?

Subcategory: T2.1 Node Filtering (Single Node Paths); T4.2 Grouped Node Degree Statistics by Label

This query finds highly helpful reviews, counts any images attached to each review, and returns reviewer, rating, helpful-vote, and image-count fields for comparison.

cypher
MATCH (r:Review)
WHERE r.vote IS NOT NULL
OPTIONAL MATCH (r)-[:HAS_IMAGE]->(ri:ReviewImage)
WITH r.reviewerID AS ReviewerID,
    coalesce(r.reviewerID, "Unknown") AS ReviewerName,
    r.overall AS Rating,
    r.vote AS HelpfulVotes,
    count(ri) AS ImageCount
RETURN ReviewerID, ReviewerName, Rating, HelpfulVotes, ImageCount
ORDER BY HelpfulVotes DESC
LIMIT 200;

Equivalent query in GQL standard:

gql
MATCH (r:Review)
WHERE r.vote IS NOT NULL
OPTIONAL MATCH (r)-[:HAS_IMAGE]->(ri:ReviewImage)
WITH r.reviewerID AS ReviewerID,
    coalesce(r.reviewerID, "Unknown") AS ReviewerName,
    r.overall AS Rating,
    r.vote AS HelpfulVotes,
    count(ri) AS ImageCount
RETURN ReviewerID, ReviewerName, Rating, HelpfulVotes, ImageCount
ORDER BY HelpfulVotes DESC
LIMIT 200;