7.11. Implementing newsfeeds in a graph

cypher-newsfeed-graph.svg

Implementation of newsfeed or timeline feature is a frequent requirement for social applications. The following exmaples are inspired by Newsfeed feature powered by Neo4j Graph Database. The query asked here is:

Starting at me, retrieve the time-ordered status feed of the status updates of me and and all friends that are connected via a CONFIRMED FRIEND relationship to me.

Query. 

START me=node:node_auto_index(name='Joe')
MATCH me-[rels:FRIEND*0..1]-myfriend
WHERE ALL(r in rels
WHERE r.status = 'CONFIRMED')
WITH myfriend
MATCH myfriend-[:STATUS]-latestupdate-[:NEXT*0..1]-statusupdates
RETURN myfriend.name as name, statusupdates.date as date, statusupdates.text as text
ORDER BY statusupdates.date DESC
LIMIT 3

To understand the strategy, let’s divide the query into five steps:

  1. First Get the list of all my friends (along with me) through FRIEND relationship (MATCH me-[rels:FRIEND*0..1]-myfriend). Also, the WHERE predicate can be added to check whether the friend request is pending or confirmed.
  2. Get the latest status update of my friends through Status relationship (MATCH myfriend-[:STATUS]-latestupdate).
  3. Get subsequent status updates (along with the latest one) of my friends through NEXT relationships (MATCH myfriend-[:STATUS]-latestupdate-[:NEXT*0..1]-statusupdates).
  4. Sort the status updates by posted date (ORDER BY statusupdates.date DESC).
  5. LIMIT the number of updates you need in every query (LIMIT x SKIP x*y).

Result

namedatetext
3 rows
0 ms

"Joe"

6

"Joe status2"

"Bob"

4

"bobs status2"

"Joe"

3

"Joe status1"


Try this query live. (1) {"name":"Bob"} (2) {"date":1,"name":"bob_s1","text":"bobs status1"} (3) {"date":4,"name":"bob_s2","text":"bobs status2"} (4) {"name":"Alice"} (5) {"date":2,"name":"alice_s1","text":"Alices status1"} (6) {"date":5,"name":"alice_s2","text":"Alices status2"} (7) {"name":"Joe"} (8) {"date":3,"name":"joe_s1","text":"Joe status1"} (9) {"date":6,"name":"joe_s2","text":"Joe status2"} (1)-[:STATUS]->(2) {} (1)-[:FRIEND]->(4) {"status":"CONFIRMED"} (2)-[:NEXT]->(3) {} (4)-[:STATUS]->(5) {} (4)-[:FRIEND]->(7) {"status":"PENDING"} (5)-[:NEXT]->(6) {} (7)-[:STATUS]->(8) {} (7)-[:FRIEND]->(1) {"status":"CONFIRMED"} (8)-[:NEXT]->(9) {} START me=node:node_auto_index(name='Joe') MATCH me-[rels:FRIEND*0..1]-myfriend WHERE ALL(r in rels WHERE r.status = 'CONFIRMED') WITH myfriend MATCH myfriend-[:STATUS]-latestupdate-[:NEXT*0..1]-statusupdates RETURN myfriend.name as name, statusupdates.date as date, statusupdates.text as text ORDER BY statusupdates.date DESC LIMIT 3

Here, the example shows how to add a new status update into the existing data for a user.

Query. 

START me=node:node_auto_index(name='Bob')
MATCH me-[r?:STATUS]-secondlatestupdate
DELETE r
WITH me, secondlatestupdate
CREATE me-[:STATUS]->(latest_update{text:'Status',date:123})
WITH latest_update,secondlatestupdate
CREATE latest_update-[:NEXT]-secondlatestupdate
WHERE secondlatestupdate <> null
RETURN latest_update.text as new_status

Dividing the query into steps, this query resembles adding new item in middle of a doubly linked list:

  1. Get the latest update (if it exists) of the user through the STATUS relationship (MATCH me-[r?:STATUS]-secondlatestupdate).
  2. Delete the STATUS relationship between user and secondlatestupdate (if it exists), as this would become the second latest update now and only the latest update would be added through a STATUS relationship, all earlier updates would be connected to their subsequent updates through a NEXT relationship. (DELETE r).
  3. Now, create the new statusupdate node (with text and date as properties) and connect this with the user through a STATUS relationship (CREATE me-[:STATUS]->(latest_update{text:'Status',date:123})).
  4. Now, create a NEXT relationship between the latest status update and the second latest status update (if it exists) (CREATE latest_update-[:NEXT]-secondlatestupdate WHERE secondlatestupdate <> null).

Result

new_status
1 row
Nodes created: 1
Relationships created: 2
Properties set: 2
Relationships deleted: 1
0 ms

"Status"


Try this query live. (1) {"name":"Bob"} (2) {"date":1,"name":"bob_s1","text":"bobs status1"} (3) {"date":4,"name":"bob_s2","text":"bobs status2"} (5) {"date":123,"text":"Status"} (1)-[:STATUS]->(5) {} (2)-[:NEXT]->(3) {} (5)-[:NEXT]->(2) {} START me=node:node_auto_index(name='Bob') MATCH me-[r?:STATUS]-secondlatestupdate DELETE r WITH me, secondlatestupdate CREATE me-[:STATUS]->(latest_update{text:'Status',date:123}) WITH latest_update,secondlatestupdate CREATE latest_update-[:NEXT]-secondlatestupdate WHERE secondlatestupdate <> null RETURN latest_update.text as new_status

cypher-insertstatusupdate-graph.svg