Newsfeed implementation in Neo4j - 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

Related

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

Getting the latest 10 registered users - Spring security Grails

I need to get the last 10 registered users (normal users) in my application for statistics. The application has two roles: normal user and administrator user.
In my User class (Spring security), I have the dateCreated field and I can obtain the last 10 registered users in my controller with this query:
User.listOrderByDateCreated(max: 10, order: 'desc')
But I just want to get it between normal users, excluding administrator. With this query, I can obtain all normal users:
UserRole.findAllByRole(role).user
What query have I to run? Thanks.
Try this
> User.executeQuery( "from User user where user.id in (select userRole.user.id from UserRole userRole where userRole.role.id =:roleId) order by dateCreated desc", [roleId: role.id], [max: 10])
Other way is
UserRole.executeQuery( "select ur.user from UserRole ur where ur.role.id =:roleId) order by ur.user.dateCreated desc", [roleId: role.id], [max: 10])
Let me know if it works for you .. :)
Surprisingly this is a tricky one, because User doesn't have a direct handle on Role, so the GORM helpers don't help as much. Using straight Groovy list manipulation we can get what you want.
def users = User.list().findAll { it.authorities.contains role }
.sort { it.dateCreated }
.reverse()
.take(10)
//or…
def users = UserRole.findAllByRole(role).user
.sort { it.dateCreated }
.reverse()
.take(10)
However, if you have a large number of users this would be an inefficient way to get 10 of them. A better option may be to use Hibernate criteria:
def users = UserRole.createCriteria().list(max: 2) {
eq "role", role
user {
order 'dateCreated', 'desc'
}
projections { property 'user' }
}
Or if you want, you can query using HQL via executeQuery():
def users = User.executeQuery("from User where id in (select user.id from UserRole where role.id = :roleId) order by dateCreated desc", [roleId: role.id], [max: 10])
Try this:
User.findAllByRole(role,
[max: 10, sort: "dateCreated", order: "desc"])

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.

how to find me and my followers posts in 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

Why does User.last(2) and User.first(2) return the same users?

I have a User model in which
default_scope :order => 'created at desc'
I currently have fifty records, ids 1 through 50.
User.first returns User id: 50.
User.first(2) returns User id: 50 and User id: 49
User.last returns User id: 1
This all makes sense. However,
User.last(2) returns User id: 49 and User id: 50, in that order. Why is that? And how do I return User id: 1 and User id: 2?
This is a common mistake. User.last(1) and User.last() are not exactly same. User.last(1) would give you an array of a single record while User.last() would return that record object.
Again, both these methods would behave entirely differently if you have a default_scope in your User model.
User.last just works on your default scope, reversing its order. So the SQL query it fires is:
SELECT * FROM users ORDER BY created_at ASC LIMIT 1
On the other hand, User.last(1) is similar to writing User.order('id desc').limit(1). And with your default_scope in action, the order by id desc would come second to the default one. So the SQL it fires would be:
SELECT * FROM users ORDER BY created_at desc, id desc LIMIT 1
So what you really need to do here is remove the default scope using User.unscoped as Kien has mentioned.
Personally, I avoid using default scope ordering and use explicit scoping instead.
Try unscoped method:
User.unscoped.first(2) # Get User id=1 and id=2
You can check Remove all scoping.
Rubydocs give you information about first and last methods.
If you last(2) is not working you can try
User.find(:order => 'created_at asc', :limit =>2)
corrected
User.find(:all,:order => 'created_at asc', :limit =>2)
User.unscoped.order('created_at asc').limit(2)
You can pass there many arguments to get what you want.

Resources