2
votes

I have a simple social-networking like graph w/ users, friends, comments, likes etc. Users can "own" items, comment on "items", like "items". I am trying to write a cypher query that returns "items" along w/ extra information to display them in my stream.

I have tried using optional match and collect and stuff, but there is always some part of the result that doesn't work.

Specifically, for a given user(say user1), I want to return "items" that:

  1. a specific user + his friends own
  2. show number of likes,
  3. also show number of comments,
  4. Know if the item is already owned by me (so I can hide "own" button in the UI)
  5. If the item is owned by friends, I want to show name, image of up to 2 friends (but not more than 2 friends if, say, 5 friends own that item)

You can copy-paste below to get the graph

// 3 users
CREATE (u1:USER{name:"USER1", image: "image1"})
CREATE (u2:USER{name:"USER2", image: "image2"})
CREATE (u3:USER{name:"USER3", image: "image3"})

//3 items
CREATE (i1:ITEM{name:"ITEM1"})
CREATE (i2:ITEM{name:"ITEM2"})
CREATE (i3:ITEM{name:"ITEM3"})


// OWNERSHIP ..

//user1 owns 2 items
CREATE (u1)-[:OWNS]->(i1)
CREATE (u1)-[:OWNS]->(i2)


// user2 owns i2 and i3 
CREATE (u2)-[:OWNS]->(i2)
CREATE (u2)-[:OWNS]->(i3)

// user3 also owns i2 and i3 (so i2 is owned by u1, u2 and u3; and i3 is owned by u2 and u3)
CREATE (u3)-[:OWNS]->(i2)
CREATE (u3)-[:OWNS]->(i3)     

// FRIENDSHIP..

// user1 is friend of both user2 and user3
CREATE (u1)-[:FRIEND_OF]->(u2)
CREATE (u1)-[:FRIEND_OF]->(u3)


// COMMENTS ..

//user1 has commented on all those items he owns
CREATE (u1i1:COMMENT{text:"user1 comment on item1"})
CREATE (u1)-[:COMMENTED]->(u1i1)-[:COMMENT_FOR]->(i1)

CREATE (u1i2:COMMENT{text:"user1 comment on item2"})
CREATE (u1)-[:COMMENTED]->(u1i2)-[:COMMENT_FOR]->(i2)

//user 2 has also commented on all those he owns
CREATE (u2i2:COMMENT{text:"user2 comment on item2"})
CREATE (u2)-[:COMMENTED]->(u2i2)-[:COMMENT_FOR]->(i2)

CREATE (u2i3:COMMENT{text:"user2 comment on item3"})
CREATE (u2)-[:COMMENTED]->(u2i3)-[:COMMENT_FOR]->(i3)


// LIKES ..

//user1 has liked user2's and user3's items
CREATE (u1)-[:LIKED]->(i2)
CREATE (u1)-[:LIKED]->(i3)

//user2 has liked user1's items
CREATE (u2)-[:LIKED]->(i1)
2

2 Answers

4
votes

Let's build your query up step by step:

Specifically, for a given user(say user1), I want to return "items" that: a specific user + his friends own

MATCH (u:USER {name: "USER1"})-[:FRIEND_OF*0..1]-(friend:USER)-[:OWNS]-(i:ITEM)
WITH u,i, 
     // Know if the item is already owned by me (so I can hide "own" button in the UI) 
     sum(size((u)-[:OWNS]->(i))) > 0 as user_owns,
     // If the item is owned by friends, I want to show name, image of up to 2 friends 
     collect({name:friend.name, image:friend.image})[0..2] as friends

RETURN u,i, user_owns, friends
// show number of likes,
       sum(size(()-[:LIKED]->(i))) as like,
// also show number of comments,
       sum(size(()-[:COMMENT_FOR]->(i))) as comments

Actually because it is such a good question, I sat down and created a GraphGist documenting each step here.

3
votes

Fairly easy. First you need to have a variable path length match from 0..1 on FRIEND_OF returning either yourself. Follow to all items being owned by those.

Use OPTIONAL MATCH for likes and comments since there might or might not exist any.

Since there are potentially multiple paths to a single item, you need to count the distinct likes and comments.

To check if you already own the item, check the endpoint of the variable path match from above if its name is yours.

For getting up to two images of the friends owning the item filter the list for your friends and return the image property. Last step is to slice the collection for the first two elements using subscript operator.

MATCH (:USER { name:'USER1' })-[:FRIEND_OF*0..1]->(me_or_friend)-[:OWNS]->(item)
OPTIONAL MATCH (item)<-[l:LIKED]-()
OPTIONAL MATCH (item)<-[c:COMMENT_FOR]-()
WITH item, count(distinct l) AS likes, count(distinct c) AS comments, 
    collect(DISTINCT me_or_friend) AS me_or_friends
RETURN item, likes, comments, 
    ANY (x IN me_or_friends WHERE x.name='USER1') AS i_already_own,
    [x IN me_or_friends  WHERE x.name<>'USER1' | x.image][0..2] as friendImages

Final comment:

On SO we appreciate if you show in your question what you've already tried yourself to solve the problem. Question like "solve that problem for me" are not that much welcome.