Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Leveraging the Power of Graph Databases in PHP

902 views

Published on

Graph database presentation to Nashville PHP in November, 2014.

Published in: Technology
  • Be the first to comment

Leveraging the Power of Graph Databases in PHP

  1. 1. Leveraging the Power of Graph Databases in PHP Jeremy Kendall Nashville PHP November 2014
  2. 2. Obligatory Intro Slide
  3. 3. Also - New Father
  4. 4. What Kind of Database?
  5. 5. Graphs != Charts https://www.flickr.com/photos/markgroves/3065192499/
  6. 6. Graphs != Charts http://stephenwildish.tumblr.com/post/101408321763/friday-project-witch-moral-compass
  7. 7. Graph Databases
  8. 8. Graph Databases • Data Model! • Nodes with properties • Typed relationships
  9. 9. Graph Databases • Data Model! • Nodes with properties • Typed relationships • Strengths! • Highly connected data • ACID
  10. 10. Graph Databases • Data Model! • Nodes with properties • Typed relationships • Strengths! • Highly connected data • ACID • Weaknesses! • Paradigm shift
  11. 11. Graph Databases • Data Model! • Nodes with properties • Typed relationships • Strengths! • Highly connected data • ACID • Weaknesses! • Paradigm shift • Examples! • Neo4j, Titan, OrientDB
  12. 12. Why Care?
  13. 13. Why Care? • All the NoSQL Joy • Schema-less • Semi-structured data
  14. 14. Why Care? • All the NoSQL Joy • Schema-less • Semi-structured data • Escape from JOIN Hell
  15. 15. Why Care? • All the NoSQL Joy • Schema-less • Semi-structured data • Escape from JOIN Hell • Speed
  16. 16. Why Care?
  17. 17. Why Care? • Relationships have 1st class status
  18. 18. Why Care? • Relationships have 1st class status • Just as important as the objects connecting them
  19. 19. Why Care? • Relationships have 1st class status • Just as important as the objects connecting them • You can have properties & labels
  20. 20. Why Care? • Relationships have 1st class status • Just as important as the objects connecting them • You can have properties & labels • Multiple relationships
  21. 21. Why Care?
  22. 22. Speed Depth MySQL Query Time Neo4j Query Time Records Returned 2 0.028 (28 MS) 0.04 ~900 3 0.213 0.06 ~999 4 10.273 0.07 ~999 5 92.613 0.07 ~999 1,000 people with an average 50 friends each
  23. 23. Crazy Speed Depth MySQL Query Time Neo4j Query Time Records Returned 2 0.016 (16 MS) 0.01 ~2500 3 30.27 0.168 ~125,000 4 1543.505 1.359 ~600,000 5 Stopped after 1 hour 2.132 ~800,000 1,000,000 people with an average 50 friends each
  24. 24. Neo4j + Cypher
  25. 25. Cypher • Neo4j’s declarative query language • Easy to pick up • Some clauses and concepts familiar from SQL
  26. 26. Simple Example
  27. 27. Goal
  28. 28. Create Some Nodes CREATE (jk:Person { name: "Jeremy Kendall" })! CREATE (gs:Company { name: "Graph Story" })! ! CREATE (tn:State { name: "Tennessee" })! CREATE (memphis:City { name: "Memphis" })! CREATE (nashville:City { name: "Nashville" })! ! CREATE (hotchicken:Food { name: "Hot Chicken" })! CREATE (bbq:Food { name: "Barbecue" })! CREATE (photography:Hobby { name: "Photography" })! CREATE (language:Language { name: "PHP" })! ! // . . . snip . . .!
  29. 29. Create Some Relationships // . . . snip . . .! ! CREATE (jk)-[:WORKS_AT { title: {"CTO"}]->(gs),! (jk)-[:LIVES_IN]->(memphis)-[:LIVED_IN]->(nashville),! (hotchicken)-[:ONLY_IN]->(nashville),! (bbq)-[:ONLY_IN]->(memphis),! (jk)-[:LOVES]->(hotchicken),! ! // . . . snip . . .!
  30. 30. Create Some Relationships // . . . snip . . .! ! CREATE (jk)-[:WORKS_AT { title: {"CTO"}]->(gs),! (jk)-[:LIVES_IN]->(memphis)-[:LIVED_IN]->(nashville),! (hotchicken)-[:ONLY_IN]->(nashville),! (bbq)-[:ONLY_IN]->(memphis),! (jk)-[:LOVES]->(hotchicken),! ! // . . . snip . . .!
  31. 31. Create Some Relationships // . . . snip . . .! ! CREATE (jk)-[:WORKS_AT { title: {"CTO"}]->(gs),! (jk)-[:LIVES_IN]->(memphis)-[:LIVED_IN]->(nashville),! (hotchicken)-[:ONLY_IN]->(nashville),! (bbq)-[:ONLY_IN]->(memphis),! (jk)-[:LOVES]->(hotchicken),! ! // . . . snip . . .!
  32. 32. Create Some Relationships // . . . snip . . .! ! CREATE (jk)-[:WORKS_AT { title: {"CTO"}]->(gs),! (jk)-[:LIVES_IN]->(memphis)-[:LIVED_IN]->(nashville),! (hotchicken)-[:ONLY_IN]->(nashville),! (bbq)-[:ONLY_IN]->(memphis),! (jk)-[:LOVES]->(hotchicken),! ! // . . . snip . . .!
  33. 33. Example Cypher Query MATCH (p:Person { name: "Jeremy Kendall" })-[:LOVES]->(l)! WITH p, l! MATCH (p)-[:WORKS_AT]->(j)! WITH p, l, j! MATCH (p)-[:LIVES_IN]->(c:City)-[:LIVED_IN*0..]->(o:City)! RETURN p, l, j, o
  34. 34. Example Cypher Query MATCH (p:Person { name: "Jeremy Kendall" })-[:LOVES]->(l)! WITH p, l! MATCH (p)-[:WORKS_AT]->(j)! WITH p, l, j! MATCH (p)-[:LIVES_IN]->(c:City)-[:LIVED_IN*0..]->(o:City)! RETURN p, l, j, o
  35. 35. Example Cypher Query MATCH (p:Person { name: "Jeremy Kendall" })-[:LOVES]->(l)! WITH p, l! MATCH (p)-[:WORKS_AT]->(j)! WITH p, l, j! MATCH (p)-[:LIVES_IN]->(c:City)-[:LIVED_IN*0..]->(o:City)! RETURN p, l, j, o
  36. 36. Example Cypher Query MATCH (p:Person { name: "Jeremy Kendall" })-[:LOVES]->(l)! WITH p, l! MATCH (p)-[:WORKS_AT]->(j)! WITH p, l, j! MATCH (p)-[:LIVES_IN]->(c:City)-[:LIVED_IN*0..]->(o:City)! RETURN p, l, j, o
  37. 37. Example Cypher Query MATCH (p:Person { name: "Jeremy Kendall" })-[:LOVES]->(l)! WITH p, l! MATCH (p)-[:WORKS_AT]->(j)! WITH p, l, j! MATCH (p)-[:LIVES_IN]->(c:City)-[:LIVED_IN*0..]->(o:City)! RETURN p, l, j, o
  38. 38. Example Cypher Query MATCH (p:Person { name: "Jeremy Kendall" })-[:LOVES]->(l)! WITH p, l! MATCH (p)-[:WORKS_AT]->(j)! WITH p, l, j! MATCH (p)-[:LIVES_IN]->(c:City)-[:LIVED_IN*0..]->(o:City)! RETURN p, l, j, o
  39. 39. Example Cypher Query MATCH (p:Person { name: "Jeremy Kendall" })-[:LOVES]->(l)! WITH p, l! MATCH (p)-[:WORKS_AT]->(j)! WITH p, l, j! MATCH (p)-[:LIVES_IN]->(c:City)-[:LIVED_IN*0..]->(o:City)! RETURN p, l, j, o
  40. 40. Query Result
  41. 41. Neo4j + PHP
  42. 42. Neo4jPHP • PHP wrapper for the Neo4j REST API • Installable via Composer • Used internally at Graph Story • Used in this presentation • Well tested • https://packagist.org/packages/everyman/ neo4jphp
  43. 43. Also see: NeoClient • Written by Neoxygen • Alternative PHP wrapper for the Neo4j REST API • Installable via Composer • Under review for internal use at Graph Story • Well tested • https://packagist.org/packages/neoxygen/neoclient
  44. 44. Connecting $neo4jClient = new EverymanNeo4jClient(! ‘yourgraph.example.com’, ! 7473! );! ! $neo4jClient->getTransport()! ->setAuth('username', 'password')! ->getTransport()->useHttps();
  45. 45. Creating a Node and Label $node = new Node($neo4jClient);! ! $label = $neo4jClient->makeLabel('Person');! ! $node->setProperty('name', ‘Jeremy Kendall');! ! $node->save()->addLabels(array($label));
  46. 46. Searching // Searching for a label by property! $label = $neo4jClient->makeLabel('Person');! $nodes = $label->getNodes('name', $name);
  47. 47. Querying (Cypher) $queryString = ! 'MATCH (p:Person { name: { name }}) RETURN p';! ! $query = new EverymanNeo4jCypherQuery(! $neo4jClient,! $queryString,! ['name' => ‘Jeremy Kendall']! );! ! $result = $query->getResultSet();
  48. 48. Named Parameters
  49. 49. Named Parameters $queryString = ! 'MATCH (p:Person { name: { name }}) RETURN p';! ! $query = new EverymanNeo4jCypherQuery(! $neo4jClient,! $queryString,! ['name' => ‘Jeremy Kendall']! );! ! $result = $query->getResultSet();
  50. 50. Named Parameters $queryString = ! 'MATCH (p:Person { name: { name }}) RETURN p';! ! $query = new EverymanNeo4jCypherQuery(! $neo4jClient,! $queryString,! ['name' => ‘Jeremy Kendall']! );! ! $result = $query->getResultSet();
  51. 51. Content Modeling: News Feeds
  52. 52. News Feed • Modeled as a list of posts • Newest post first • All subsequent posts follow • Relationships: LASTPOST and NEXTPOST
  53. 53. LASTPOST
  54. 54. NEXTPOST
  55. 55. The Content Model class Content! {! public $node;! public $nodeId;! public $contentId;! public $title;! public $url;! public $tagstr;! public $timestamp;! public $userNameForPost;! public $owner = false;! }
  56. 56. Adding Content public static function add($username, Content $content)! {! $queryString =<<<CYPHER! MATCH (user { username: {u}})! OPTIONAL MATCH (user)-[r:LASTPOST]->(lastpost)! DELETE r! CREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url:{url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId: {contentId} })! WITH p, collect(lastpost) as lastposts! FOREACH (x IN lastposts | CREATE p-[:NEXTPOST]->x)! RETURN p, {u} as username, true as owner! CYPHER;! ! $query = new Query(! Neo4jClient::client(),! $queryString,! array(! 'u' => $username,! 'title' => $content->title,! 'url' => $content->url,! 'tagstr' => $content->tagstr,! 'timestamp' => time(),! 'contentId' => uniqid()! )! );! $result = $query->getResultSet();! ! return self::returnMappedContent($result);! }
  57. 57. Adding Content public static function add($username, Content $content)! {! $queryString =<<<CYPHER! MATCH (user { username: {u}})! OPTIONAL MATCH (user)-[r:LASTPOST]->(lastpost)! DELETE r! CREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url:{url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId: {contentId} })! WITH p, collect(lastpost) as lastposts! FOREACH (x IN lastposts | CREATE p-[:NEXTPOST]->x)! RETURN p, {u} as username, true as owner! CYPHER;! ! $query = new Query(! Neo4jClient::client(),! $queryString,! array(! 'u' => $username,! 'title' => $content->title,! 'url' => $content->url,! 'tagstr' => $content->tagstr,! 'timestamp' => time(),! 'contentId' => uniqid()! )! );! $result = $query->getResultSet();! ! return self::returnMappedContent($result);! }
  58. 58. Adding Content public static function add($username, Content $content)! {! $queryString =<<<CYPHER! MATCH (user { username: {u}})! OPTIONAL MATCH (user)-[r:LASTPOST]->(lastpost)! DELETE r! CREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url:{url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId: {contentId} })! WITH p, collect(lastpost) as lastposts! FOREACH (x IN lastposts | CREATE p-[:NEXTPOST]->x)! RETURN p, {u} as username, true as owner! CYPHER;! ! $query = new Query(! Neo4jClient::client(),! $queryString,! array(! 'u' => $username,! 'title' => $content->title,! 'url' => $content->url,! 'tagstr' => $content->tagstr,! 'timestamp' => time(),! 'contentId' => uniqid()! )! );! $result = $query->getResultSet();! ! return self::returnMappedContent($result);! }
  59. 59. Adding Content MATCH (user { username: {u}})! OPTIONAL MATCH (user)-[r:LASTPOST]->(lastpost)! DELETE r! CREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url: {url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId: {contentId} })! WITH p, collect(lastpost) as lastposts! FOREACH (x IN lastposts | CREATE p-[:NEXTPOST]->x)! RETURN p, {u} as username, true as owner
  60. 60. Adding Content MATCH (user { username: {u}})! OPTIONAL MATCH (user)-[r:LASTPOST]->(lastpost)! DELETE r! CREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url: {url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId: {contentId} })! WITH p, collect(lastpost) as lastposts! FOREACH (x IN lastposts | CREATE p-[:NEXTPOST]->x)! RETURN p, {u} as username, true as owner
  61. 61. Adding Content MATCH (user { username: {u}})! OPTIONAL MATCH (user)-[r:LASTPOST]->(lastpost)! DELETE r! CREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url: {url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId: {contentId} })! WITH p, collect(lastpost) as lastposts! FOREACH (x IN lastposts | CREATE p-[:NEXTPOST]->x)! RETURN p, {u} as username, true as owner
  62. 62. Adding Content MATCH (user { username: {u}})! OPTIONAL MATCH (user)-[r:LASTPOST]->(lastpost)! DELETE r! CREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url: {url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId: {contentId} })! WITH p, collect(lastpost) as lastposts! FOREACH (x IN lastposts | CREATE p-[:NEXTPOST]->x)! RETURN p, {u} as username, true as owner
  63. 63. Adding Content MATCH (user { username: {u}})! OPTIONAL MATCH (user)-[r:LASTPOST]->(lastpost)! DELETE r! CREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url: {url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId: {contentId} })! WITH p, collect(lastpost) as lastposts! FOREACH (x IN lastposts | CREATE p-[:NEXTPOST]->x)! RETURN p, {u} as username, true as owner
  64. 64. Adding Content MATCH (user { username: {u}})! OPTIONAL MATCH (user)-[r:LASTPOST]->(lastpost)! DELETE r! CREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url: {url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId: {contentId} })! WITH p, collect(lastpost) as lastposts! FOREACH (x IN lastposts | CREATE p-[:NEXTPOST]->x)! RETURN p, {u} as username, true as owner
  65. 65. Adding Content $query = new Query(! $neo4jClient,! $queryString,! array(! 'u' => $username,! 'title' => $content->title,! 'url' => $content->url,! 'tagstr' => $content->tagstr,! 'timestamp' => time(),! 'contentId' => uniqid()! )! );! ! $result = $query->getResultSet();
  66. 66. Retrieving Content public static function getContent($username, $skip)! {! $queryString = <<<CYPHER! MATCH (u:User { username: { u }})-[:FOLLOWS*0..1]->f! WITH DISTINCT f, u! MATCH f-[:LASTPOST]-lp-[:NEXTPOST*0..]-p! RETURN p, f.username as username, f = u as owner! ORDER BY p.timestamp desc SKIP { skip } LIMIT 4! CYPHER;! ! $query = new Query(! Neo4jClient::client(),! $queryString,! array(! 'u' => $username,! 'skip' => $skip,! )! );! ! $result = $query->getResultSet();! ! return self::returnMappedContent($result);! }
  67. 67. Retrieving Content MATCH (u:User { username: { u }})-[:FOLLOWS*0..1]->f! WITH DISTINCT f, u! MATCH f-[:LASTPOST]-lp-[:NEXTPOST*0..]-p! RETURN p, f.username as username, f = u as owner! ORDER BY p.timestamp desc SKIP { skip } LIMIT 4
  68. 68. Retrieving Content MATCH (u:User { username: { u }})-[:FOLLOWS*0..1]->f! WITH DISTINCT f, u! MATCH f-[:LASTPOST]-lp-[:NEXTPOST*0..]-p! RETURN p, f.username as username, f = u as owner! ORDER BY p.timestamp desc SKIP { skip } LIMIT 4
  69. 69. Retrieving Content MATCH (u:User { username: { u }})-[:FOLLOWS*0..1]->f! WITH DISTINCT f, u! MATCH f-[:LASTPOST]-lp-[:NEXTPOST*0..]-p! RETURN p, f.username as username, f = u as owner! ORDER BY p.timestamp desc SKIP { skip } LIMIT 4
  70. 70. Retrieving Content MATCH (u:User { username: { u }})-[:FOLLOWS*0..1]->f! WITH DISTINCT f, u! MATCH f-[:LASTPOST]-lp-[:NEXTPOST*0..]-p! RETURN p, f.username as username, f = u as owner! ORDER BY p.timestamp desc SKIP { skip } LIMIT 4
  71. 71. Retrieving Content MATCH (u:User { username: { u }})-[:FOLLOWS*0..1]->f! WITH DISTINCT f, u! MATCH f-[:LASTPOST]-lp-[:NEXTPOST*0..]-p! RETURN p, f.username as username, f = u as owner! ORDER BY p.timestamp desc SKIP { skip } LIMIT 4
  72. 72. Retrieving Content MATCH (u:User { username: { u }})-[:FOLLOWS*0..1]->f! WITH DISTINCT f, u! MATCH f-[:LASTPOST]-lp-[:NEXTPOST*0..]-p! RETURN p, f.username as username, f = u as owner! ORDER BY p.timestamp desc SKIP { skip } LIMIT 4
  73. 73. Editing Content public static function edit(Content $content)! {! $updatedAt = time();! ! $node = $content->node;! $node->setProperty('title', $content->title);! $node->setProperty('url', $content->url);! $node->setProperty('tagstr', $content->tagstr);! $node->setProperty('updated', $updatedAt);! $node->save();! ! $content->updated = $updatedAt;! ! return $content;! }
  74. 74. Editing Content public static function edit(Content $content)! {! $updatedAt = time();! ! $node = $content->node;! $node->setProperty('title', $content->title);! $node->setProperty('url', $content->url);! $node->setProperty('tagstr', $content->tagstr);! $node->setProperty('updated', $updatedAt);! $node->save();! ! $content->updated = $updatedAt;! ! return $content;! }
  75. 75. Editing Content public static function edit(Content $content)! {! $updatedAt = time();! ! $node = $content->node;! $node->setProperty('title', $content->title);! $node->setProperty('url', $content->url);! $node->setProperty('tagstr', $content->tagstr);! $node->setProperty('updated', $updatedAt);! $node->save();! ! $content->updated = $updatedAt;! ! return $content;! }
  76. 76. Editing Content public static function edit(Content $content)! {! $updatedAt = time();! ! $node = $content->node;! $node->setProperty('title', $content->title);! $node->setProperty('url', $content->url);! $node->setProperty('tagstr', $content->tagstr);! $node->setProperty('updated', $updatedAt);! $node->save();! ! $content->updated = $updatedAt;! ! return $content;! }
  77. 77. Editing Content public static function edit(Content $content)! {! $updatedAt = time();! ! $node = $content->node;! $node->setProperty('title', $content->title);! $node->setProperty('url', $content->url);! $node->setProperty('tagstr', $content->tagstr);! $node->setProperty('updated', $updatedAt);! $node->save();! ! $content->updated = $updatedAt;! ! return $content;! }
  78. 78. Deleting Content public static function delete($username, $contentId)! {! $queryString = self::getDeleteQueryString(! $username, ! $contentId! );! ! $params = array(! 'username' => $username,! 'contentId' => $contentId,! );! ! $query = new Query(! $neo4jClient,! $queryString, ! $params! );! $query->getResultSet();! }
  79. 79. Deleting Content public static function delete($username, $contentId)! {! $queryString = self::getDeleteQueryString(! $username, ! $contentId! );! ! $params = array(! 'username' => $username,! 'contentId' => $contentId,! );! ! $query = new Query(! $neo4jClient,! $queryString, ! $params! );! $query->getResultSet();! }
  80. 80. Deleting Content public static function delete($username, $contentId)! {! $queryString = self::getDeleteQueryString(! $username, ! $contentId! );! ! $params = array(! 'username' => $username,! 'contentId' => $contentId,! );! ! $query = new Query(! $neo4jClient,! $queryString, ! $params! );! $query->getResultSet();! }
  81. 81. Deleting Content public static function delete($username, $contentId)! {! $queryString = self::getDeleteQueryString(! $username, ! $contentId! );! ! $params = array(! 'username' => $username,! 'contentId' => $contentId,! );! ! $query = new Query(! $neo4jClient,! $queryString, ! $params! );! $query->getResultSet();! }
  82. 82. Deleting Content: Leaf // If leaf! MATCH (u:User { username: { username }})-[:LASTPOST| NEXTPOST*0..]->(c:Content { contentId: { contentId }})! WITH c! MATCH (c)-[r]-()! DELETE c, r
  83. 83. Deleting Content: Leaf // If leaf! MATCH (u:User { username: { username }})-[:LASTPOST| NEXTPOST*0..]->(c:Content { contentId: { contentId }})! WITH c! MATCH (c)-[r]-()! DELETE c, r
  84. 84. Deleting Content: Leaf // If leaf! MATCH (u:User { username: { username }})-[:LASTPOST| NEXTPOST*0..]->(c:Content { contentId: { contentId }})! WITH c! MATCH (c)-[r]-()! DELETE c, r
  85. 85. Deleting Content: Leaf // If leaf! MATCH (u:User { username: { username }})-[:LASTPOST| NEXTPOST*0..]->(c:Content { contentId: { contentId }})! WITH c! MATCH (c)-[r]-()! DELETE c, r
  86. 86. Deleting Content: Leaf // If leaf! MATCH (u:User { username: { username }})-[:LASTPOST| NEXTPOST*0..]->(c:Content { contentId: { contentId }})! WITH c! MATCH (c)-[r]-()! DELETE c, r
  87. 87. Deleting Content: LASTPOST // If last! MATCH (u:User { username: { username }})-[lp:LASTPOST]- >(del:Content { contentId: { contentId }})-[np:NEXTPOST]- >(nextPost)! CREATE UNIQUE (u)-[:LASTPOST]->(nextPost)! DELETE lp, del, np
  88. 88. Deleting Content: LASTPOST // If last! MATCH (u:User { username: { username }})-[lp:LASTPOST]- >(del:Content { contentId: { contentId }})-[np:NEXTPOST]- >(nextPost)! CREATE UNIQUE (u)-[:LASTPOST]->(nextPost)! DELETE lp, del, np
  89. 89. Deleting Content: LASTPOST // If last! MATCH (u:User { username: { username }})-[lp:LASTPOST]- >(del:Content { contentId: { contentId }})-[np:NEXTPOST]- >(nextPost)! CREATE UNIQUE (u)-[:LASTPOST]->(nextPost)! DELETE lp, del, np
  90. 90. Deleting Content: LASTPOST // If last! MATCH (u:User { username: { username }})-[lp:LASTPOST]- >(del:Content { contentId: { contentId }})-[np:NEXTPOST]- >(nextPost)! CREATE UNIQUE (u)-[:LASTPOST]->(nextPost)! DELETE lp, del, np
  91. 91. Deleting Content: Other // All other! MATCH (u:User { username: { username }})-[:LASTPOST| NEXTPOST*0..]->(before),! (before)-[delBefore]->(del:Content { contentId: { contentId }})-[delAfter]->(after)! CREATE UNIQUE (before)-[:NEXTPOST]->(after)! DELETE del, delBefore, delAfter
  92. 92. Deleting Content: Other // All other! MATCH (u:User { username: { username }})-[:LASTPOST| NEXTPOST*0..]->(before),! (before)-[delBefore]->(del:Content { contentId: { contentId }})-[delAfter]->(after)! CREATE UNIQUE (before)-[:NEXTPOST]->(after)! DELETE del, delBefore, delAfter
  93. 93. Deleting Content: Other // All other! MATCH (u:User { username: { username }})-[:LASTPOST| NEXTPOST*0..]->(before),! (before)-[delBefore]->(del:Content { contentId: { contentId }})-[delAfter]->(after)! CREATE UNIQUE (before)-[:NEXTPOST]->(after)! DELETE del, delBefore, delAfter
  94. 94. Deleting Content: Other // All other! MATCH (u:User { username: { username }})-[:LASTPOST| NEXTPOST*0..]->(before),! (before)-[delBefore]->(del:Content { contentId: { contentId }})-[delAfter]->(after)! CREATE UNIQUE (before)-[:NEXTPOST]->(after)! DELETE del, delBefore, delAfter
  95. 95. Deleting Content: Other // All other! MATCH (u:User { username: { username }})-[:LASTPOST| NEXTPOST*0..]->(before),! (before)-[delBefore]->(del:Content { contentId: { contentId }})-[delAfter]->(after)! CREATE UNIQUE (before)-[:NEXTPOST]->(after)! DELETE del, delBefore, delAfter
  96. 96. Questions?
  97. 97. Thanks! ! jeremy.kendall@graphstory.com @JeremyKendall http://www.graphstory.com

×