how to find me and my followers posts in neo4j - neo4j

I have user, post and follows relations like below
user1-[:FOLLOWS]-user2-[:POSTED]-post
How can I get all posts which are made by my followers and myself in a cypher query?

Assuming you can uniquely identify yourself by an ID:
MATCH (me:User {Id: 1})<-[:FOLLOWS*0..1]-(follower)-[:POSTED]->(post)
RETURN post;
Rationale: in the case where the length of the :FOLLOWS relationship is 0, me == follower, so the query returns your posts as well.
You can find an example here: http://console.neo4j.org/?id=dexd4p

Related

Neo4j Cypher relationship existis and collection of IDs

I have a following Neo4j Cypher query that checks if relationship exists between User and entity and returns boolean result:
MATCH (u:User) WHERE u.id = {userId} MATCH (entity) WHERE id(entity) = {entityGraphId} RETURN EXISTS( (u)<-[:OWNED_BY]-(entity) )
Please help to rewrite this query in order to be able to accept a collection of {entityGraphIds} instead of a single {entityGraphId} and check if a relationship exists between User and any entities with these {entityGraphIds}.
For example, I have user1 and entity1, entity2. user1 has a relationship with entity2. I'll pass {user.id} like {userId} and {entity1.id, entity2.id} like {entityGraphIds} and this query should return true.
I believe you can simply use the IN operator. Considering these parameters:
:params {userId: 1, entityGraphIds : [2,3,4]}
Then, the query:
MATCH (u:User) WHERE u.id = {userId}
MATCH (entity) WHERE id(entity) IN ({entityGraphIds})
RETURN EXISTS( (u)<-[:OWNED_BY]-(entity) )
EDIT:
If you are trying to return true when :User is connected to at least 1 entity, then you can simplify your query to:
OPTIONAL MATCH (u:User)<-[:OWNED_BY]-(entity:Entity)
WHERE u.id = {userId} AND id(entity) IN ({entityGraphIds})
RETURN u IS NOT NULL

Check if user liked the post

I have the following query:
MATCH (user:User)-[:CREATED]->(post:Post)
WITH user, post
ORDER BY post.createdAt DESC
OPTIONAL MATCH (post)<-[:BELONGS_TO]-(comment:Comment)<-[:COMMENTED]-(:User)
WITH user, post, liked, comment
ORDER BY comment.timestamp DESC
WITH user, post, liked, COLLECT(comment)[0..4] AS comments
RETURN post,
{ username: user.username,
firstName: user.firstName,
lastName: user.lastName,
profilePicture: user.profilePicture
} AS createdBy,
size((post)<-[:LIKES]-(:User)) AS likes,
liked,
comments
SKIP {skip}
LIMIT {limit}
The query gets list of posts and calculates other things like:
get post comments, get user who created the post, get total number of likes.
I need to also calculate if I liked the post or not, which will result into: userLiked (true|false).
I was thinking of something like:
OPTIONAL MATCH (post)<-[userLiked:LIKES]-(:User {uuid: {userUUID}})
If you just need a true/false, then EXISTS() is a better option.
You can also improve performance by moving your SKIP and LIMIT from the end of your query to after your ORDER BY for the post creation.
If you're using Neo4j 3.1.x or higher, you can use map projection to make it a little easier to return the fields you need on the createdBy map.
Here's your query with all these things included:
MATCH (user:User)-[:CREATED]->(post:Post)
WITH user, post
ORDER BY post.createdAt DESC
OPTIONAL MATCH (post)<-[:BELONGS_TO]-(comment:Comment)<-[:COMMENTED]-(:User)
WITH user, post, comment
ORDER BY comment.timestamp DESC
WITH user, post, COLLECT(comment)[..4] AS comments
SKIP {skip} LIMIT {limit}
RETURN post,
user { .username, .firstName, .lastName, .profilePicture } AS createdBy,
size((post)<-[:LIKES]-(:User)) AS likes,
exists((post)<-[:LIKES]-(:User{uuid: {userUUID}})) AS userLiked,
comments

Rails: Finding users with at least 1 movie in common (user has_many movies through ratings)

Having some difficulty with an elegant way to solve this.
Users can rate movies 1 or 0. A user has many movies through ratings. I am trying to write an instance method to return a list of users who have at least one positive match in common (both rated movie 1).
e.g.
some_user.potential_matches => returns list of users who rated at least one of some_user.movie_ratings.where(rating: 1) as a 1
Hope that makes sense.
I think this one liner might do the job for you (it may need some tweaking to match your class names etc)
User.joins(:movies).where(movies: { id: Movie.joins(:users).where(users: { id: u }).joins(:ratings).where(ratings: { rating: 1 })})
When you're trying a query that is a bit more complicated I find it helpful to start at the end and work your way back
1) Find all movies with ratings == 1
Movie.joins(:ratings).where(ratings: { rating: 1 })
2) Replace all movies with only movies where user u has provided a rating. In your case since you're looking at an instance method for class User this will be self
Movie.joins(:users).where(users: { id: u }).[INSERT QUERY FROM STEP 1 HERE]
3) The last step is to find all users that have provided ratings for the movies returned by the query above
User.joins(:movies).where(movies: { id: [INSERT QUERY FROM STEP 2 HERE] })
[EDIT]
You can add .uniq at the end to return a unique set of users.
Assuming you have movie_id and user_id in your MovieRating model when you built the association, please try the method here:
def potential_matches
# find movie ids the user rated 1
movie_ids = self.movie_ratings.where(rating: 1).map(&:movie_id)
# find other users who rated 1 in those movies and get user_ids
other_user_ids = MovieRating.where(rating: 1).where(movie_id: movie_ids).where.not(user_id: self.id).map(&:user_id).uniq
# find those users with id
other_users = User.where(id: other_user_ids)
return other_users
end
In your user class you can add this instance variable:
def users_with_rated_movies_in_common
User.joins(:movies).where(movies: { id: movies }).where.not(id: self)
end
This basically grabs all users who have rated a movie, and the rated movies is a movie that the current user rated. Finally, we don't want to include the actual user.
You should be able to chain additional conditions onto the query.

Graph database - how to reference Nodes from within Relationship

I'm preparing a graph database (using neo4j) to handle the kind of social network scenario:
Users can Post to their walls, sharing the Posts with either specific users
Users can send Messages to others
A Message can either be a text or "link" to the Post
So I came up with the following idea:
Users and Posts are the Nodes of the graph. When the user A creates a post P sharing it with both B and C, the following relationships are created: A-[:posted]->p and p-[:shared_with]->B and p-[:shared_with]->C. The Post data (id, text, date) are stored as properties of the :posted relation.
For messages it's similar: A-[:messaged]->C for example.
Now, if I want to share the post in a message, I include the post_id in :messaged properties. It allows me to pull all the messages (together with the posts linked) with a single Cypher query:
match (a:User) match (b) where a.username = "C" match a-[m:messaged]-b
optional match (x:User)-[p:posted]->(post:Post)
where post.id = m.post_id
return distinct
m.post_id,
startnode(m).username as from,
endnode(m).username as to ,
x.username as PostAuthor,
case x when null then "Message" else "Pulled Post" end as Type,
case x when null then m.text else post.text end as Text,
m.date
order by m.date asc
It doesn't look right to me though - since on the graph there's no visual connection between the Post and the message. But, I can't set a relation between Node and Relation, right? How should I do it in order to have it designed properly?
In a model where post and message are both a node, your query would look like this:
match (a:User)<-[:FROM]-(m:Message)-[:TO]->(b:User)
where a.username = "C"
match (m)<-[:COMMENT]-(post:Post)<-[:POSTED]-(x:User)
return distinct
m.id,a as from, b as to,
x.username as PostAuthor,
case x when null then "Message" else "Pulled Post" end as Type,
case x when null then m.text else post.text end as Text,
m.date
order by m.date asc

Newsfeed implementation in Neo4j

I have a graph with two kinds of nodes: User and Activity. Users may be friends with each other. I want to fetch the newsfeed of a user. Following is my query:
MATCH (me:User) WHERE me.id = '#{opts[:user_id]}'
MATCH (me)-[:FRIEND*0..1]->(user)-[:CREATE_ACTIVITY]->(activity)
RETURN activity.id AS id,
activity.created_at AS created_at,
activity.activity_type AS activity_type,
{
id: user.id,
name: user.name,
avatar_name: user.avatar_name
} AS user
ORDER BY activity.created_at
LIMIT 10
However, this query is not efficient, since it seems not to apply indexes and will scan ALL data. In addition, after I added index on Activity:created_at, nothing changed.
Could anyone provide an efficient method to fetch newsfeed?
Update:
I am using Ruby gem.
I have index on id field of User and Activity, and also on created_at of Activity. I was expecting the ORDER BY will use the index on created_at, but it does not.
Re-writing the query a bit and using params:
MATCH (me:User)-[:FRIEND*0..1]->(user)
WHERE me.id = {user_id}
WITH user LIMIT 1000
MATCH user-[:CREATE_ACTIVITY]->(activity)
RETURN activity.id AS id,
activity.created_at AS created_at,
activity.activity_type AS activity_type,
{
id: user.id,
name: user.name,
avatar_name: user.avatar_name
} AS user
ORDER BY activity.created_at
LIMIT 10
Params can help speed things up, but I doubt that's the problem. How many friends does the user have? Is there an index on the id property on user? What about created_at? Also note that WHERE me.id = is NOT the same as WHERE ID(me) =. The first checks the id property, the second checks the internal neo ID
make sure you have the indexes you speak of (i.e. they show up in profile),
you can try to change the query to:
MATCH (me:User) WHERE me.id = '#{opts[:user_id]}'
MATCH (me)-[:FRIEND*0..1]->(user)
WITh distinct user
MATCH (user)-[:CREATE_ACTIVITY]->(activity)
RETURN activity.id AS id,
activity.created_at AS created_at,
activity.activity_type AS activity_type,
{
id: user.id,
name: user.name,
avatar_name: user.avatar_name
} AS user
ORDER BY activity.created_at
LIMIT 10

Resources