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) which will give you the latest and one additional statusupdate, adjust 0..1 to whatever suits your case.
  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

"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

"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"} (1)-[:STATUS]->(2) {} (2)-[:NEXT]->(3) {} 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