Schema Diagram and Graph Statistics
Node Statistics
| Node Label | Count |
|---|---|
| Business | 150,346 |
| Category | 1,311 |
| Review | 6,990,247 |
| Tip | 908,915 |
| User | 1,987,897 |
Edge Statistics
| Relationship | Count |
|---|---|
| (:Tip)-[:FOR]-(:Business) | 908,915 |
| (:User)-[:FRIEND]-(:User) | 14,611,748 |
| (:Business)-[:IN_CATEGORY]-(:Category) | 668,592 |
| (:Review)-[:REVIEWS]-(:Business) | 6,990,247 |
| (:User)-[:TIPPED]-(:Tip) | 908,915 |
| (:User)-[:WROTE]-(:Review) | 6,990,247 |
Disk space: 10.64 GiB
Total import time: 1930.66 Seconds
Data Loading
Overview
Data import is handled in two stages: node creation followed by sequential edge ingestion. We put all the csv 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 Yelp 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.
// For Users CREATE INDEX user_user_id_index FOR (u:User) ON (u.user_id); // For Businesses CREATE INDEX business_id_index FOR (b:Business) ON (b.id);
Define Nodes
First we define the Business nodes.
CALL apoc.periodic.iterate( "CALL apoc.load.json('file:///yelp/yelp_academic_dataset_business.json') YIELD value", "CREATE (b:Business { id: value.business_id, name: value.name, address: value.address, city: value.city, state: value.state, postal_code: value.postal_code, latitude: value.latitude, longitude: value.longitude, is_open: value.is_open, categories: value.categories, rating: value.stars, review_count: value.review_count })", {batchSize: 1000, parallel: true} )
Then we define the User nodes.
CALL apoc.periodic.iterate( "CALL apoc.load.json('file:///yelp/yelp_academic_dataset_user.json') YIELD value", "CREATE (u:User { user_id: value.user_id, name: value.name, review_count: value.review_count, yelping_since: value.yelping_since, friends: value.friends, useful: value.useful, funny: value.funny, cool: value.cool, fans: value.fans, elite: value.elite, average_stars: value.average_stars, compliment_hot: value.compliment_hot, compliment_more: value.compliment_more, compliment_profile: value.compliment_profile, compliment_cute: value.compliment_cute, compliment_list: value.compliment_list, compliment_note: value.compliment_note, compliment_plain: value.compliment_plain, compliment_cool: value.compliment_cool, compliment_funny: value.compliment_funny, compliment_writer: value.compliment_writer, compliment_photos: value.compliment_photos })", {batchSize: 1000, parallel: true} )
Define Edges
Then we define the REVIEW and WROTE edges.
CALL apoc.periodic.iterate( "CALL apoc.load.json('file:///yelp/yelp_academic_dataset_review.json') YIELD value", " MATCH (u:User {user_id: value.user_id}) MATCH (b:Business {id: value.business_id}) CREATE (r:Review { id: value.review_id, stars: value.stars, date: value.date, text: value.text, useful: value.useful, funny: value.funny, cool: value.cool }) CREATE (u)-[:WROTE]->(r) CREATE (r)-[:REVIEWS]->(b) ", {batchSize: 1000, parallel: false} )
Then we define the TIP and FOR edges. We would also create Tip
nodes during the process.
CALL apoc.periodic.iterate( "CALL apoc.load.json('file:///yelp/yelp_academic_dataset_tip.json') YIELD value", " MATCH (u:User {user_id: value.user_id}) MATCH (b:Business {id: value.business_id}) CREATE (t:Tip { text: value.text, date: value.date, compliment_count: value.compliment_count }) CREATE (u)-[:TIPPED]->(t) CREATE (t)-[:FOR]->(b) ", {batchSize: 1000, parallel: false} )
Then we define the IN_CATEGORY edges. We would create Category nodes during
the process.
CALL apoc.periodic.iterate(
"MATCH (b:Business) WHERE b.categories IS NOT NULL RETURN b",
"
WITH b, apoc.text.split(b.categories, ',') AS categoryList
UNWIND categoryList AS rawCategory
WITH b, trim(rawCategory) AS category
MERGE (c:Category {name: category})
CREATE (b)-[:IN_CATEGORY]->(c)
",
{batchSize: 1000, parallel: false})
Then we define the FRIEND edges.
CALL apoc.periodic.iterate(
"MATCH (u:User) WHERE u.friends IS NOT NULL RETURN u",
"
WITH u, apoc.text.split(u.friends, ', ') AS friendIds
UNWIND friendIds AS friendId
MATCH (f:User {user_id: friendId})
CREATE(u)-[:FRIEND]->(f)
",
{batchSize: 10000, parallel: false})
Queries
Select a query below to view its logic and description.
Q1. Find the friends of all "John"s
This query retrieves the name and user ID of up to 20 friends of users named "John".
MATCH (:User {name: "John"})-[:FRIEND]->(friend) RETURN friend.name, friend.user_id LIMIT 20
Equivalent query in GQL standard:
MATCH (:User {name: "John"})-[:FRIEND]->(friend) RETURN friend.name, friend.user_id LIMIT 20
Q2. Find all users within two friend hops of a given user
This query finds all distinct users reachable within one or two FRIEND hops from a starting user.
MATCH (start:User {user_id: "Oi1qbcz2m2SnwUeztGYcnQ"})-[:FRIEND*1..2]-(neighbor:User) WHERE neighbor <> start RETURN DISTINCT neighbor.name AS name, neighbor.user_id AS user_id ORDER BY name, user_id LIMIT 100
Equivalent query in GQL standard:
MATCH p = (start:User {user_id: "Oi1qbcz2m2SnwUeztGYcnQ"})-[:FRIEND]-{1,2}(neighbor:User) WHERE neighbor != start RETURN DISTINCT neighbor.name AS name, neighbor.user_id AS user_id ORDER BY name, user_id LIMIT 100
Q3. Get 3-hop neighbors of a given user
This query identifies the unique names and user IDs of all neighbors exactly three friend-connections away from a given user.
MATCH (start:User {user_id: "Oi1qbcz2m2SnwUeztGYcnQ"})-[:FRIEND*3]-(neighbor) WHERE neighbor.user_id <> "Oi1qbcz2m2SnwUeztGYcnQ" RETURN DISTINCT neighbor.name, neighbor.user_id
Equivalent query in GQL standard:
MATCH (start:User {user_id: "Oi1qbcz2m2SnwUeztGYcnQ"})-[:FRIEND]-{3}(neighbor) WHERE neighbor.user_id != "Oi1qbcz2m2SnwUeztGYcnQ" RETURN DISTINCT neighbor.name, neighbor.user_id
Q4. Find high-review-count users within two friend hops
This query finds distinct users within one or two FRIEND hops of a starting user, filtered to users with at least 100 reviews and an average rating of 4.0 or higher.
MATCH (start:User {user_id: "Oi1qbcz2m2SnwUeztGYcnQ"})-[:FRIEND*1..2]-(neighbor:User) WHERE neighbor <> start AND neighbor.review_count >= 100 AND neighbor.average_stars >= 4.0 RETURN DISTINCT neighbor.name AS name, neighbor.user_id AS user_id, neighbor.review_count AS review_count, neighbor.average_stars AS average_stars ORDER BY review_count DESC, average_stars DESC LIMIT 100
Equivalent query in GQL standard:
MATCH p = (start:User {user_id: "Oi1qbcz2m2SnwUeztGYcnQ"})-[:FRIEND]-{1,2}(neighbor:User) WHERE neighbor != start AND neighbor.review_count >= 100 AND neighbor.average_stars >= 4.0 RETURN DISTINCT neighbor.name AS name, neighbor.user_id AS user_id, neighbor.review_count AS review_count, neighbor.average_stars AS average_stars ORDER BY review_count DESC, average_stars DESC LIMIT 100
Q5. Find open, highly rated businesses in Pennsylvania
This query filters individual Business nodes by state, open status, rating, and review count without traversing edges.
MATCH (b:Business) WHERE b.state = "PA" AND b.is_open = 1 AND b.rating >= 4.5 AND b.review_count >= 100 RETURN b.name, b.city, b.rating, b.review_count ORDER BY b.rating DESC, b.review_count DESC LIMIT 25
Equivalent query in GQL standard:
MATCH (b:Business) WHERE b.state = "PA" AND b.is_open = 1 AND b.rating >= 4.5 AND b.review_count >= 100 RETURN b.name, b.city, b.rating, b.review_count ORDER BY b.rating DESC, b.review_count DESC LIMIT 25
Q6. Find useful five-star review edges
The Yelp Neo4j schema stores review attributes on Review nodes that mediate User-to-Business edges, so this query filters those edge records by rating and usefulness.
MATCH (u:User)-[:WROTE]->(r:Review)-[:REVIEWS]->(b:Business) WHERE r.stars = 5 AND r.useful >= 10 RETURN u.name AS user_name, r.id AS review_id, r.stars AS stars, r.useful AS useful_votes, b.name AS business_name, b.city AS city ORDER BY useful_votes DESC LIMIT 20
Equivalent query in GQL standard:
MATCH (u:User)-[:WROTE]->(r:Review)-[:REVIEWS]->(b:Business) WHERE r.stars = 5 AND r.useful >= 10 RETURN u.name AS user_name, r.id AS review_id, r.stars AS stars, r.useful AS useful_votes, b.name AS business_name, b.city AS city ORDER BY useful_votes DESC LIMIT 20
Q7. Check whether Steven can reach Niki within four friend hops
This query tests whether a bounded friend path exists between two User nodes.
MATCH (source:User {user_id: "Oi1qbcz2m2SnwUeztGYcnQ"}), (target:User {user_id: "WwulXySQN8t2hwqH_yWurA"}) RETURN EXISTS { MATCH (source)-[:FRIEND*1..4]-(target) } AS reachable
Equivalent query in GQL standard:
MATCH p = ANY (source:User {user_id: "Oi1qbcz2m2SnwUeztGYcnQ"})-[:FRIEND]-{1,4}(target:User {user_id: "WwulXySQN8t2hwqH_yWurA"}) RETURN true AS reachable
Q8. Find the shortest friend path between Steven and Niki
This query returns the shortest bounded path connecting two users in the friend graph.
MATCH p = shortestPath( (:User {user_id: "Oi1qbcz2m2SnwUeztGYcnQ"})-[:FRIEND*1..6]-(:User {user_id: "WwulXySQN8t2hwqH_yWurA"}) ) RETURN [n IN nodes(p) | n.name] AS user_path, length(p) AS hop_count
Equivalent query in GQL standard:
MATCH p = SHORTEST (:User {user_id: "Oi1qbcz2m2SnwUeztGYcnQ"})-[:FRIEND]-{1,6}(:User {user_id: "WwulXySQN8t2hwqH_yWurA"}) RETURN p
Q9. Enumerate simple friend paths between Steven and Niki up to length three
This query materializes acyclic friend paths up to three hops between two users.
MATCH p = (:User {user_id: "Oi1qbcz2m2SnwUeztGYcnQ"})-[:FRIEND*1..3]-(:User {user_id: "WwulXySQN8t2hwqH_yWurA"}) WHERE all(i IN range(0, size(nodes(p)) - 1) WHERE single(n IN nodes(p) WHERE n = nodes(p)[i])) RETURN [n IN nodes(p) | n.user_id] AS user_ids, length(p) AS hop_count ORDER BY hop_count ASC LIMIT 20
Equivalent query in GQL standard:
MATCH p = ACYCLIC (:User {user_id: "Oi1qbcz2m2SnwUeztGYcnQ"})-[:FRIEND]-{1,3}(:User {user_id: "WwulXySQN8t2hwqH_yWurA"}) RETURN p LIMIT 20
Q10. Find friend triangles around Steven
This query finds three-user cycles in the friend graph anchored at Steven.
MATCH (a:User {user_id: "Oi1qbcz2m2SnwUeztGYcnQ"})-[:FRIEND]-(b:User), (b)-[:FRIEND]-(c:User), (c)-[:FRIEND]-(a) WHERE a <> b AND b <> c AND a <> c RETURN DISTINCT b.name AS friend_one, c.name AS friend_two LIMIT 25
Equivalent query in GQL standard:
MATCH (a:User {user_id: "Oi1qbcz2m2SnwUeztGYcnQ"})-[:FRIEND]-(b:User), (b)-[:FRIEND]-(c:User), (c)-[:FRIEND]-(a) WHERE a <> b AND b <> c AND a <> c RETURN DISTINCT b.name AS friend_one, c.name AS friend_two LIMIT 25
Q11. Find friend squares around Steven
This query searches for four-user cycles that include Steven.
MATCH (a:User {user_id: "Oi1qbcz2m2SnwUeztGYcnQ"})-[:FRIEND]-(b:User), (b)-[:FRIEND]-(c:User), (c)-[:FRIEND]-(d:User), (d)-[:FRIEND]-(a) WHERE a <> b AND a <> c AND a <> d AND b <> c AND b <> d AND c <> d RETURN DISTINCT b.name AS first_friend, c.name AS bridge_friend, d.name AS second_friend LIMIT 25
Equivalent query in GQL standard:
MATCH (a:User {user_id: "Oi1qbcz2m2SnwUeztGYcnQ"})-[:FRIEND]-(b:User), (b)-[:FRIEND]-(c:User), (c)-[:FRIEND]-(d:User), (d)-[:FRIEND]-(a) WHERE a <> b AND a <> c AND a <> d AND b <> c AND b <> d AND c <> d RETURN DISTINCT b.name AS first_friend, c.name AS bridge_friend, d.name AS second_friend LIMIT 25
Q12. Find high-review-count friend stars
This query finds users who are connected to many prolific reviewer friends.
MATCH (center:User)-[:FRIEND]->(friend:User) WHERE friend.review_count >= 100 RETURN center.name AS center_name, center.user_id AS center_id, count(friend) AS prolific_friend_count ORDER BY prolific_friend_count DESC LIMIT 25
Equivalent query in GQL standard:
MATCH (center:User)-[:FRIEND]->(friend:User) WHERE friend.review_count >= 100 RETURN center.name AS center_name, center.user_id AS center_id, count(friend) AS prolific_friend_count ORDER BY prolific_friend_count DESC LIMIT 25
Q13. Find four-user cliques around Steven
This query finds fully connected groups of four users containing Steven.
MATCH (a:User {user_id: "Oi1qbcz2m2SnwUeztGYcnQ"})-[:FRIEND]-(b:User), (a)-[:FRIEND]-(c:User), (a)-[:FRIEND]-(d:User), (b)-[:FRIEND]-(c), (b)-[:FRIEND]-(d), (c)-[:FRIEND]-(d) WHERE b.user_id < c.user_id AND c.user_id < d.user_id RETURN b.name AS user_b, c.name AS user_c, d.name AS user_d LIMIT 10
Equivalent query in GQL standard:
MATCH (a:User {user_id: "Oi1qbcz2m2SnwUeztGYcnQ"})-[:FRIEND]-(b:User), (a)-[:FRIEND]-(c:User), (a)-[:FRIEND]-(d:User), (b)-[:FRIEND]-(c), (b)-[:FRIEND]-(d), (c)-[:FRIEND]-(d) WHERE b.user_id < c.user_id AND c.user_id < d.user_id RETURN b.name AS user_b, c.name AS user_c, d.name AS user_d LIMIT 10
Q14. Find mutual friends between Steven and Niki
This query finds the names and user IDs of all mutual friends between two specific users, Steven and Niki.
MATCH (a:User {user_id: "Oi1qbcz2m2SnwUeztGYcnQ"})-[:FRIEND]->(mutual)<-[:FRIEND]-(b:User {user_id: "WwulXySQN8t2hwqH_yWurA"}) RETURN DISTINCT mutual.name, mutual.user_id
Equivalent query in GQL standard:
MATCH (a:User {user_id: "Oi1qbcz2m2SnwUeztGYcnQ"})-[:FRIEND]->(mutual)<-[:FRIEND]-(b:User {user_id: "WwulXySQN8t2hwqH_yWurA"}) RETURN DISTINCT mutual.name, mutual.user_id
Q15. For a given user's friends, find their reviewed businesses along with locations
This query lists the businesses, including their physical addresses and coordinates, that have been reviewed by a given user's friends.
MATCH (u:User {user_id: "Oi1qbcz2m2SnwUeztGYcnQ"})-[:FRIEND]->(friend:User)-[:WROTE]->(r:Review)-[:REVIEWS]->(b:Business) RETURN DISTINCT friend.name AS friend_name, b.name AS business_name, b.address, b.city, b.state, b.latitude, b.longitude ORDER BY friend_name, business_name
Equivalent query in GQL standard:
MATCH (u:User {user_id: "Oi1qbcz2m2SnwUeztGYcnQ"})-[:FRIEND]->(friend:User)-[:WROTE]->(r:Review)-[:REVIEWS]->(b:Business) RETURN DISTINCT friend.name AS friend_name, b.name AS business_name, b.address, b.city, b.state, b.latitude, b.longitude ORDER BY friend_name, business_name
Q16. Count the friends of all "John"s
This query calculates the total number of unique friends belonging to all users named "John".
MATCH (:User {name: "John"})-[:FRIEND]->(friend) RETURN count(DISTINCT friend) AS total_friends
Equivalent query in GQL standard:
MATCH (:User {name: "John"})-[:FRIEND]->(friend) RETURN count(DISTINCT friend) AS total_friends
Q17. Find businesses with only negative reviews
This query finds businesses where every review rating is less than 3 stars, ordered by the volume of reviews.
MATCH (b:Business)<-[:REVIEWS]-(r:Review) WITH b, collect(r.stars) AS stars WHERE ALL(star IN stars WHERE star < 3) RETURN b.name, stars ORDER BY size(stars) DESC
Equivalent query in GQL standard:
MATCH (b:Business)<-[:REVIEWS]-(r:Review) RETURN b.name, collect(r.stars) AS stars WHERE ALL(star IN stars WHERE star < 3) ORDER BY size(stars) DESC
Q18. Summarize review-edge counts and ratings
The Yelp schema stores review rating on Review nodes rather than relationship properties, so this query summarizes the review-mediated User-to-Business edges.
MATCH (:User)-[:WROTE]->(r:Review)-[:REVIEWS]->(:Business) RETURN count(r) AS review_edge_count, avg(r.stars) AS avg_review_stars, min(r.stars) AS min_review_stars, max(r.stars) AS max_review_stars
Equivalent query in GQL standard:
MATCH (:User)-[:WROTE]->(r:Review)-[:REVIEWS]->(:Business) RETURN count(r) AS review_edge_count, avg(r.stars) AS avg_review_stars, min(r.stars) AS min_review_stars, max(r.stars) AS max_review_stars
Q19. Summarize review ratings by business state
This query groups review-mediated User-to-Business edges by the destination business state and summarizes ratings.
MATCH (:User)-[:WROTE]->(r:Review)-[:REVIEWS]->(b:Business) RETURN b.state AS state, count(r) AS review_edge_count, avg(r.stars) AS avg_review_stars, min(r.stars) AS min_review_stars, max(r.stars) AS max_review_stars ORDER BY review_edge_count DESC LIMIT 25
Equivalent query in GQL standard:
MATCH (:User)-[:WROTE]->(r:Review)-[:REVIEWS]->(b:Business) RETURN b.state AS state, count(r) AS review_edge_count, avg(r.stars) AS avg_review_stars, min(r.stars) AS min_review_stars, max(r.stars) AS max_review_stars ORDER BY review_edge_count DESC LIMIT 25
Q20. Count Steven's friend paths by length
This query counts all bounded friend paths from Steven and groups them by hop length.
MATCH p = (:User {user_id: "Oi1qbcz2m2SnwUeztGYcnQ"})-[:FRIEND*1..3]-(neighbor:User) RETURN length(p) AS path_length, count(p) AS path_count, count(DISTINCT neighbor) AS distinct_neighbor_count ORDER BY path_length ASC
Equivalent query in GQL standard:
MATCH p = (:User {user_id: "Oi1qbcz2m2SnwUeztGYcnQ"})-[:FRIEND]-{1,3}(neighbor:User) RETURN length(p) AS path_length, count(p) AS path_count, count(DISTINCT neighbor) AS distinct_neighbor_count ORDER BY path_length ASC
Q21. Count Steven's friend paths by length and rating bucket
This query extends Q20 by grouping endpoint users into average-star buckets.
MATCH p = (:User {user_id: "Oi1qbcz2m2SnwUeztGYcnQ"})-[:FRIEND*1..3]-(neighbor:User) WITH p, neighbor, CASE WHEN neighbor.average_stars >= 4.0 THEN "high" WHEN neighbor.average_stars >= 3.0 THEN "medium" ELSE "low" END AS rating_bucket RETURN length(p) AS path_length, rating_bucket, count(p) AS path_count, count(DISTINCT neighbor) AS distinct_neighbor_count ORDER BY path_length ASC, path_count DESC LIMIT 25
Equivalent query in GQL standard:
MATCH p = (:User {user_id: "Oi1qbcz2m2SnwUeztGYcnQ"})-[:FRIEND]-{1,3}(neighbor:User) WITH p, neighbor, CASE WHEN neighbor.average_stars >= 4.0 THEN "high" WHEN neighbor.average_stars >= 3.0 THEN "medium" ELSE "low" END AS rating_bucket RETURN length(p) AS path_length, rating_bucket, count(p) AS path_count, count(DISTINCT neighbor) AS distinct_neighbor_count ORDER BY path_length ASC, path_count DESC LIMIT 25
Q22. Find the total number of reviews and tips for each business
This query calculates the combined count of reviews and tips for every business, sorting them in descending order of total user interactions.
MATCH (b:Business) OPTIONAL MATCH (b)<-[:REVIEWS]-(r:Review) WITH b, count(DISTINCT r) AS num_reviews OPTIONAL MATCH (b)<-[:FOR]-(t:Tip)<-[:TIPPED]-(:User) WITH b, num_reviews, count(DISTINCT t) AS num_tips RETURN b.name AS business_name, num_reviews, num_tips, num_reviews + num_tips AS total ORDER BY total DESC
Equivalent query in GQL standard:
MATCH (b:Business) OPTIONAL MATCH (b)<-[:REVIEWS]-(r:Review) OPTIONAL MATCH (b)<-[:TIPPED]-(t:Tip) RETURN b.name AS business_name, count(DISTINCT r) AS num_reviews, count(DISTINCT t) AS num_tips, (count(DISTINCT r) + count(DISTINCT t)) AS total ORDER BY total DESC
Q23. Find the businesses with both tips and reviews, and return their counts
This query finds businesses that have received both reviews and tips, returning the counts of each along with their combined total.
MATCH (b:Business)<-[:REVIEWS]-(r:Review) WITH b, count(DISTINCT r) AS num_reviews MATCH (b)<-[:FOR]-(t:Tip)<-[:TIPPED]-(:User) WITH b, num_reviews, count(DISTINCT t) AS num_tips RETURN b.name AS business_name, num_reviews, num_tips, num_reviews + num_tips AS total ORDER BY total DESC
Equivalent query in GQL standard:
MATCH (b:Business)<-[:REVIEWS]-(r:Review) MATCH (b)<-[:TIPPED]-(t:Tip) RETURN b.name AS business_name, count(DISTINCT r) AS num_reviews, count(DISTINCT t) AS num_tips, (count(DISTINCT r) + count(DISTINCT t)) AS total ORDER BY total DESC
Q24. Print all the text reviews and tips for a specific restaurant
This query compiles all text content from reviews and tips written for a specific restaurant named "Acme Oyster House".
MATCH (b:Business {name: "Acme Oyster House"}) MATCH (b)<-[:REVIEWS]-(r:Review) RETURN b.id AS business_id, r.text AS text, 'review' AS type UNION MATCH (b:Business {name: "Acme Oyster House"}) MATCH (b)<-[:FOR]-(t:Tip)<-[:TIPPED]-(:User) RETURN b.id AS business_id, t.text AS text, 'tip' AS type
Equivalent query in GQL standard:
MATCH (b:Business {name: "Acme Oyster House"}) MATCH (b)<-[:REVIEWS]-(r:Review) RETURN b.id AS business_id, r.text AS text, 'review' AS type UNION MATCH (b:Business {name: "Acme Oyster House"}) MATCH (b)<-[:TIPPED]-(t:Tip) RETURN b.id AS business_id, t.text AS text, 'tip' AS type
Q25. Run degree centrality on the in-memory friend-graph and write the results back to the database
This query runs the Degree Centrality algorithm on the friend graph to count each user's direct connections and stores the centrality score.
The queries in the Analytics category would require projecting a in-memory GDS graph first. Run the below query to do it.
CALL gds.graph.project( 'FriendGraph', 'User', { FRIEND: { type: 'FRIEND', orientation: 'UNDIRECTED' } } ) YIELD graphName, nodeCount, relationshipCount, projectMillis RETURN graphName, nodeCount, relationshipCount, projectMillis;
Then run the following analytic query.
CALL gds.degree.stream( 'FriendGraph' ) YIELD nodeId, score WITH gds.util.asNode(nodeId) AS u, score RETURN u.name AS name, u.user_id AS user_id, score AS degree ORDER BY degree DESC LIMIT 10;
Q26. Run page rank on the in-memory friend-graph and write the results back to the database
This query runs the PageRank algorithm on the friend graph to measure user influence and lists the top 10 most influential users.
The queries in the Analytics category would require projecting a in-memory GDS graph first. Run the below query to do it.
CALL gds.graph.project( 'FriendGraph', 'User', { FRIEND: { type: 'FRIEND', orientation: 'UNDIRECTED' } } ) YIELD graphName, nodeCount, relationshipCount, projectMillis RETURN graphName, nodeCount, relationshipCount, projectMillis;
Then run the following analytic query.
CALL gds.pageRank.stream('FriendGraph') YIELD nodeId, score WITH gds.util.asNode(nodeId) AS u, score RETURN u.name AS name, u.user_id AS user_id, score AS pagerank ORDER BY pagerank DESC LIMIT 10;
Q27. Find weakly connected components (WCC) and query the size of each WCC
This query groups users into weakly connected components based on mutual or directional friendships and lists the sizes of the largest components.
The queries in the Analytics category would require projecting a in-memory GDS graph first. Run the below query to do it.
CALL gds.graph.project( 'FriendGraph', 'User', { FRIEND: { type: 'FRIEND', orientation: 'UNDIRECTED' } } ) YIELD graphName, nodeCount, relationshipCount, projectMillis RETURN graphName, nodeCount, relationshipCount, projectMillis;
Then run the following analytic query.
CALL gds.wcc.stream('FriendGraph') YIELD nodeId, componentId WITH componentId, count(*) AS component_size RETURN componentId, component_size ORDER BY component_size DESC LIMIT 10;
Q28. Find strongly connected components in the directed friend graph
This query runs strongly connected components on the directed FRIEND graph and reports the largest components.
The queries in the Analytics category would require projecting a in-memory GDS graph first. Run the below query to do it.
CALL gds.graph.project( 'FriendGraph', 'User', { FRIEND: { type: 'FRIEND', orientation: 'UNDIRECTED' } } ) YIELD graphName, nodeCount, relationshipCount, projectMillis RETURN graphName, nodeCount, relationshipCount, projectMillis;
Then run the following analytic query.
CALL gds.scc.stream('FriendGraph') YIELD nodeId, componentId WITH componentId, count(*) AS component_size RETURN componentId, component_size ORDER BY component_size DESC LIMIT 20;
Q29. Find Louvain Community and query the size of each community
This query identifies community structures within the user friendship network using the Louvain modularity optimization algorithm and lists the largest communities.
The queries in the Analytics category would require projecting a in-memory GDS graph first. Run the below query to do it.
CALL gds.graph.project( 'FriendGraph', 'User', { FRIEND: { type: 'FRIEND', orientation: 'UNDIRECTED' } } ) YIELD graphName, nodeCount, relationshipCount, projectMillis RETURN graphName, nodeCount, relationshipCount, projectMillis;
Then run the following analytic query.
CALL gds.louvain.stream('FriendGraph') YIELD nodeId, communityId WITH communityId, count(*) AS size RETURN communityId, size ORDER BY size DESC LIMIT 10;
Q30. Count 3-hop neighbors of a given user
This query calculates the total number of unique users who are exactly three friend-connections away from a given user.
MATCH (start:User {user_id: "Oi1qbcz2m2SnwUeztGYcnQ"})-[:FRIEND*3]-(neighbor) WHERE neighbor.user_id <> "Oi1qbcz2m2SnwUeztGYcnQ" RETURN count(DISTINCT neighbor) AS three_hop_count
Equivalent query in GQL standard:
MATCH (start:User {user_id: "Oi1qbcz2m2SnwUeztGYcnQ"})-[:FRIEND]-{3}(neighbor) WHERE neighbor.user_id != "Oi1qbcz2m2SnwUeztGYcnQ" RETURN count(DISTINCT neighbor) AS three_hop_count
Q31. Who has the most mutual friends with a given user
This query finds the top 50 users who share the highest number of mutual friends with a given user.
MATCH (me:User {user_id: "Oi1qbcz2m2SnwUeztGYcnQ"})-[:FRIEND]->(f:User) MATCH (other:User)-[:FRIEND]->(f) WHERE other.user_id <> me.user_id RETURN other.user_id AS user_id, other.name AS name, count(DISTINCT f.user_id) AS mutual_count ORDER BY mutual_count DESC, user_id ASC LIMIT 50
Equivalent query in GQL standard:
MATCH (me:User {user_id: "Oi1qbcz2m2SnwUeztGYcnQ"})-[:FRIEND]->(f:User) MATCH (other:User)-[:FRIEND]->(f) WHERE other != me RETURN other.name, count(f) AS mutual_count ORDER BY mutual_count DESC LIMIT 50
Q32. For a given user's friends, count the friend-business pairs
This query counts the total number of distinct friend-business review relationships among the friends of a given user.
MATCH (:User {user_id: 'Oi1qbcz2m2SnwUeztGYcnQ'}) -[:FRIEND]->(friend:User) -[:WROTE]->(:Review) -[:REVIEWS]->(business:Business) WITH DISTINCT friend.user_id AS friend_id, business.id AS business_id RETURN count(*) AS total_results
Equivalent query in GQL standard:
CALL { MATCH (u:User {user_id: "Oi1qbcz2m2SnwUeztGYcnQ"})-[:FRIEND]->(friend:User)-[:WROTE]->(r:Review)-[:REVIEWS]->(b:Business) RETURN DISTINCT friend.name AS friend_name, b.name AS business_name, b.address AS address, b.city AS city, b.state AS state, b.latitude AS latitude, b.longitude AS longitude } RETURN count(*) AS total_results
Q33. Find all users who commented on the above businesses
This query lists all unique users who have written reviews for businesses situated within a 5-mile radius of a specific coordinate.
The following query requires a new attribute on Business nodes be set. Run this query to set
location on Business nodes.
MATCH (b:Business) WHERE b.latitude IS NOT NULL AND b.longitude IS NOT NULL SET b.location = point({latitude: b.latitude, longitude: b.longitude})
Then we build point index with the location values.
CREATE POINT INDEX business_location_index FOR (b:Business) ON (b.location)
WITH point({latitude: 39.877788, longitude: -75.69293535784999}) AS center MATCH (b:Business) WHERE b.location IS NOT NULL AND point.distance(b.location, center) <= 8046.72 MATCH (u:User)-[:WROTE]->(:Review)-[:REVIEWS]->(b) RETURN DISTINCT u.user_id, u.name ORDER BY u.name
Q34. Find the median of the above locations (from Q15)
This query calculates the median latitude and longitude of all businesses reviewed by the friends of a given user.
MATCH (u:User {user_id: "Oi1qbcz2m2SnwUeztGYcnQ"})-[:FRIEND]->(:User)-[:WROTE]->(:Review)-[:REVIEWS]->(b:Business) WHERE b.latitude IS NOT NULL AND b.longitude IS NOT NULL WITH DISTINCT b.id AS bid, b.latitude AS lat, b.longitude AS lon RETURN percentileCont(lat, 0.5) AS median_latitude, percentileCont(lon, 0.5) AS median_longitude
percentileCont
is a non-standard aggregate function not supported by standard GQL.
Q35. Count the number of users who commented on the above businesses
This query calculates the total number of unique users who have reviewed businesses within a 5-mile radius of a specific coordinate.
The following query requires a new attribute on Business nodes be set. Run this query to set
location on Business nodes.
MATCH (b:Business) WHERE b.latitude IS NOT NULL AND b.longitude IS NOT NULL SET b.location = point({latitude: b.latitude, longitude: b.longitude})
Then we build point index with the location values.
CREATE POINT INDEX business_location_index FOR (b:Business) ON (b.location)
WITH point({latitude: 39.877788, longitude: -75.69293535784999}) AS center MATCH (b:Business) WHERE b.location IS NOT NULL AND point.distance(b.location, center) <= 8046.72 MATCH (u:User)-[:WROTE]->(:Review)-[:REVIEWS]->(b) RETURN count(DISTINCT u) AS user_count
Q36. Find the most frequent names
This query counts the occurrences of each user name in the dataset and returns the top 30 most frequent names in descending order.
MATCH (u:User) RETURN u.name AS name, count(*) AS occurrences ORDER BY occurrences DESC LIMIT 30
Equivalent query in GQL standard:
MATCH (u:User) RETURN u.name AS name, count(*) AS occurrences ORDER BY occurrences DESC LIMIT 30
Q37. Find all businesses within 5 miles from a spicified location
This query retrieves all businesses located within a 5-mile radius of a specific geographical coordinate, sorted by proximity.
The following query requires a new attribute on Business nodes be set. Run this query to set
location on Business nodes.
MATCH (b:Business) WHERE b.latitude IS NOT NULL AND b.longitude IS NOT NULL SET b.location = point({latitude: b.latitude, longitude: b.longitude})
Then we build point index with the location values.
CREATE POINT INDEX business_location_index FOR (b:Business) ON (b.location)
WITH point({latitude: 39.877788, longitude: -75.69293535784999}) AS center MATCH (b:Business) WHERE b.location IS NOT NULL AND point.distance(b.location, center) <= 8046.72 // 5 miles in meters RETURN b.name, b.address, b.city, b.state, b.latitude, b.longitude, point.distance(b.location, center) AS dist_meters ORDER BY dist_meters ASC
Q38. Find the top-10 nearest business to the specific location
This query identifies the 10 nearest businesses to a specific geographical coordinate and ranks them in order of distance.
The following query requires a new attribute on Business nodes be set. Run this query to set
location on Business nodes.
MATCH (b:Business) WHERE b.latitude IS NOT NULL AND b.longitude IS NOT NULL SET b.location = point({latitude: b.latitude, longitude: b.longitude})
Then we build point index with the location values.
CREATE POINT INDEX business_location_index FOR (b:Business) ON (b.location)
WITH point({latitude: 39.877788, longitude: -75.69293535784999}) AS center MATCH (b:Business) WHERE b.location IS NOT NULL RETURN b.name, b.address, b.city, b.state, b.latitude, b.longitude, point.distance(b.location, center) AS dist_meters ORDER BY dist_meters ASC LIMIT 10
Q39. Find reviews containing “disgusting”
This query searches for and returns up to 100 review texts that contain the word "disgusting".
MATCH (r:Review) WHERE toLower(r.text) CONTAINS "disgusting" RETURN r.id, r.text LIMIT 100
Equivalent query in GQL standard:
MATCH (r:Review) WHERE toLower(r.text) CONTAINS "disgusting" RETURN r.id, r.text LIMIT 100
Q40. Find the most frequently used words in reviews
This query extracts, filters (length > 3), and ranks the top 20 most frequent individual words used across all review text.
MATCH (r:Review) WITH split(toLower(r.text), " ") AS words UNWIND words AS word WITH word, count(*) AS freq WHERE size(word) > 3 RETURN word, freq ORDER BY freq DESC LIMIT 20
Equivalent query in GQL standard:
MATCH (r:Review) UNWIND split(toLower(r.text), " ") AS word RETURN word, count(*) AS freq WHERE size(word) > 3 ORDER BY freq DESC LIMIT 20