Skip to content

Instantly share code, notes, and snippets.

@voutilad
Last active November 7, 2019 01:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save voutilad/d0f42ae61ea15617491f2defe1642835 to your computer and use it in GitHub Desktop.
Save voutilad/d0f42ae61ea15617491f2defe1642835 to your computer and use it in GitHub Desktop.
Measuring Journeys in Neo4j
// Run this first on an empty Neo4j database.
// Note: the "type" properties are only added to make JSON
// results read better and aren't needed. The "at" property
// is used to create unique Views.
MERGE (u1:User {name:'dave'})
MERGE (u2:User {name:'not dave'})
MERGE (p1:Page {page:'/page1.html'})
MERGE (p2:Page {page:'/page2.html'})
MERGE (p3:Page {page:'/page3.html'})
MERGE (p4:Page {page:'/page4.html'})
MERGE (p5:Page {page:'/page5.html'})
MERGE (p6:Page {page:'/page6.html'})
MERGE (p7:Page {page:'/page7.html'})
MERGE (u1)-[:HAS {type:'has'}]->(v1:View {type:'view', at:rand()})-[:OF {type:'of'}]->(p1)
MERGE (u1)-[:HAS {type:'has'}]->(v2:View {type:'view', at:rand()})-[:OF {type:'of'}]->(p2)
MERGE (v1)-[:NEXT {type:'next'}]->(v2)
MERGE (u1)-[:HAS {type:'has'}]->(v3:View {type:'view', at:rand()})-[:OF {type:'of'}]->(p3)
MERGE (v2)-[:NEXT {type:'next'}]->(v3)
MERGE (u1)-[:HAS {type:'has'}]->(v4:View {type:'view', at:rand()})-[:OF {type:'of'}]->(p6)
MERGE (v3)-[:NEXT {type:'next'}]->(v4)
MERGE (u2)-[:HAS {type:'has'}]->(v5:View {type:'view', at:rand()})-[:OF {type:'of'}]->(p1)
MERGE (u2)-[:HAS {type:'has'}]->(v6:View {type:'view', at:rand()})-[:OF {type:'of'}]->(p4)
MERGE (v5)-[:NEXT {type:'next'}]->(v6)
MERGE (u2)-[:HAS {type:'has'}]->(v7:View {type:'view', at:rand()})-[:OF {type:'of'}]->(p3)
MERGE (v6)-[:NEXT {type:'next'}]->(v7)
MERGE (u2)-[:HAS {type:'has'}]->(v8:View {type:'view', at:rand()})-[:OF {type:'of'}]->(p6)
MERGE (v7)-[:NEXT {type:'next'}]->(v8)
// duplicate some page views by nav'ing to them again
MERGE (u2)-[:HAS {type:'has'}]->(v9:View {type:'view', at:rand()})-[:OF {type:'of'}]->(p3)
MERGE (v8)-[:NEXT {type:'next'}]->(v9)
// Find the "journeys" in terms of Views. To increase journey length, add
// additional requirements to the [:NEXT] relationship like [:NEXT*n] for a
// length of n+1
MATCH path=(begin:View)-[:NEXT]->(finish:View)
// Lookup all Pages in the Journey associated with the Views, using the
// "path" from the previous MATCH
WITH path
UNWIND nodes(path) AS view
MATCH (view)-[:OF]->(p:Page)
// Aggregate the list of Pages along the identified "path"
// If using a path of length n > 2, add additional MATCH
// statements and WHERE clauses
WITH path, collect(p.page) as pageView
MATCH (p1:Page), (p2:Page)
WHERE p1.page = pageView[0]
    AND p2.page = pageView[1]
// Count occurrences of ordered path arrays. Adjust if
// using a path of length n > 2 by updating array literals
// to be [p1, p2, ..., pn]
WITH [p1, p2] AS pathList, count([p1, p2]) AS cnt
// Upsert a Journey node with the latest computed count, constructing
// a Journey "id" based on the constituent order parts of a path. This could
// be rewritten to programmatically join all elements of PATH with the delimiter
MERGE (j:Journey {id: pathList[0].page + '-' + pathList[1].page, count: cnt})
// Upsert the relationship between the Journey and the Pages
WITH j, pathList[0] as p1, pathList[1] as p2
MERGE (j)-[:CONTAINS]->(p1)
MERGE (j)-[:CONTAINS]->(p2)
// Return journey, pages, and the journey's count making sure to
// include both the Page nodes (p1, p2), Journey (j), and the aggregations
RETURN j as Journey, p1 as Page1, p2 as Page2, j.count as Count
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment