Calculate 4 newest comments per post in query - neo4j

I have the following query to get posts with calculated number of likes and calculated user who created the post:
MATCH (post:Post)
WITH post
ORDER BY post.createdAt DESC
MATCH (user:User)-[:CREATED]->(post)
RETURN post, user.username AS createdBy,
size((post)<-[:LIKES]-(:User)) AS likes
SKIP {skip}
LIMIT {limit}
What I need is to calculate last 4 created comments for every post. If there is 0 comments, I would like to have returned empty array and there is also possibility that post has less then 4 comments, so will just show array of these comments.
I need smth like this (I added imaginary query):
MATCH (post:Post)
WITH post
ORDER BY post.createdAt DESC
MATCH (user:User)-[:CREATED]->(post)
[MATCH (comment:Comment)-[:BELONGS_TO]->(post:Post) ORDER BY comment.timestamp ASC]
RETURN post, user.username AS createdBy,
size((post)<-[:LIKES]-(:User)) AS likes,
comment AS comments
SKIP {skip}
LIMIT {limit}

This might do what you want:
MATCH (user:User)-[:CREATED]->(post:Post)
WITH user, post
ORDER BY post.createdAt DESC
OPTIONAL MATCH (post)<-[:BELONGS_TO]-(comment:Comment)
WITH user, post, comment
ORDER BY comment.timestamp DESC
RETURN post, user.username AS createdBy,
size((post)<-[:LIKES]-(:User)) AS likes,
COLLECT(comment)[0..4] AS latestComments
SKIP {skip}
LIMIT {limit}
It returns the posts (in descending order), and for each post it returns the creator's name, the number of likes, and a collection of (up to) the last 4 comments. It uses OPTIONAL MATCH to ensure that it returns posts that have no comments at all.

Related

How to limit overall union in cypher

I have these nodes:
user{user_id}: users
thread{thread_id, post_date} : posts
tag_id{tag_id}: the tag of the post
And these relationships:
(user) - [: FOLLOWED] -> (tag) // the user follows the tag
(thread) - [: BELONG_TO] -> (tag) // the post belongs to tag
(user) - [: READ{read_date}] -> (thread) // user reads the post
(user) - [: BEING_REPLIED{post_date}] -> (thread) // the user is given a reply by another user to his / her comment in a post
(user) - [: BEING_MENTIONED{post_date}] -> (thread) // the user is given a mention by another user comment in a post
I want to get 10 posts that the user is replied or mentioned by another user, then to the posts that belong to tag the user follows but the user has not read to display in each user's feed, I use multiple unions in the query but cannot limit to the total, the resulting form is limited to the last union
I wrote cypher as follows:
MATCH (u:User {user_id:3})-[rp:BEING_REPLIED]->(th:Thread)<-[r:READ]-(u:User {user_id:3})
WHERE rp.post_date> r.read_date
return u.user_id as user_id,th.thread_id as thread_id,
duration.inDays(datetime(),datetime(rp.post_date)).days*10 + 1000000 AS point
UNION ALL
MATCH (u:User {user_id:3})-[m:BEING_MENTIONED]->(th:Thread)<-[r:READ]-(u:User {user_id:3})
WHERE m.post_date> r.read_date
return u.user_id as user_id,th.thread_id as thread_id,
duration.inDays(datetime(),datetime(m.post_date)).days*10 + 1000000 AS point
UNION ALL
MATCH (u:User {user_id:3})-[m:BEING_MENTIONED]->(th:Thread)
WHERE NOT EXISTS ((u)-[:READ]->(th))
return u.user_id as user_id,th.thread_id as thread_id,
duration.inDays(datetime(),datetime(m.post_date)).days*10 + 1000000 AS point
MATCH (u:User)-[:FOLLOWED]->(t:Tag)<-[:BELONG_TO]->(th)
WHERE u.user_id = 3 AND NOT EXISTS((u)-[]->(th))
WITH u.user_id AS user_id, th.thread_id AS thread_id,
(0.5*th.like_count + 0.3*th.comment_count + 0.005*th.view_count
+ duration.inDays(datetime(),datetime(th.published_date)).days*100) AS point
ORDER BY point desc
RETURN DISTINCT user_id, thread_id, point
UNION
MATCH (u:User)-[:FOLLOWED]->(t:Tag)<-[:BELONG_TO]->(th)
WHERE u.user_id = 3 AND NOT EXISTS((u)-[]->(th))
AND NOT th.rating_total IS NULL
WITH u.user_id AS user_id, th.thread_id AS thread_id,
(duration.inDays(datetime(),datetime(th.published_date)).days*150 + 30*th.rating_total) AS point
ORDER BY point desc, th.published_date desc
RETURN DISTINCT user_id, thread_id, point
LIMIT 10
How can i set this query limit overall?
Thanks for your help!
You need subqueries for this, you should be using Neo4j 4.0.x or later, this allows you to perform post-UNION processing
Usage of UNION ALL in the subquery, with the LIMIT 10 outside of it, should allow you to get what you want.

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

calculate number of likes for every post

I have following query to get one post with calculating number of likes:
MATCH (user:User)-[:CREATED]->(post:Post)
WHERE post.uuid = {postUUID}
RETURN post,
user.username AS `createdBy`,
size((post)<-[:LIKES]-(:User)) AS `likes`
What I need is to calculate number of likes for every post individually.
My current query looks like:
MATCH (user:User)-[:CREATED]->(posts:Post)
RETURN posts, user.username AS `createdBy`
ORDER BY posts.createdAt DESC
SKIP {skip}
LIMIT {limit}
Thanks!
You'll want to reorder your query so you get the relevant subset of posts first, and then perform the additional matching and counting operations on that subset. That saves you from performing operations on nodes that won't even be returned in the first place.
MATCH (post:Post)
WITH post
ORDER BY post.createdAt DESC
SKIP {skip}
LIMIT {limit}
MATCH (user:User)-[:CREATED]->(post)
RETURN post, user.username AS `createdBy`, size((post)<-[:LIKES]-(:User)) AS `likes`
If you want to return the results for a specific list of posts (passing the list in a postUUIDs parameter):
MATCH (user:User)-[:CREATED]->(post:Post)
WHERE post.uuid IN {postUUIDs}
RETURN
post,
user.username AS `createdBy`,
size((post)<-[:LIKES]-(:User)) AS `likes`
You can use simple aggregation:
MATCH (user:User)-[:CREATED]->(post:Post)<-[L:LIKES]-(:User)
WHERE post.uuid = {postUUID}
RETURN user.username AS `createdBy`,
post,
count(L) as countOfLikes

Getting replies of comments on posts in nested query

I am writing a query to get all the posts of your friends, the comments of each post and the replies of each comment:
MATCH (:User {email:{my_email}})-[:Friends]->(other_user:User)-[:Published]->(post:Post)
OPTIONAL MATCH (commenters:User)-[:Published]->(comments:Comment)-[:Comments]->post
OPTIONAL MATCH (repliers:User)-[:Published]->(reply:Reply)-[:Replied]->comments
WITH post, other_user, comments, reply
ORDER BY comments.timestamp DESC, reply.timestamp DESC
RETURN post AS posts, other_user AS other, Collect(comments) AS comments, Collect(reply) as replies
ORDER BY post.timestamp DESC LIMIT 20
I can get all the posts and the comments of the post but am unable to get the replies of the comments.
How do we execute such a query?
I figured out how to this query
Here is the right query:
MATCH (:User {email:{my_email}})-[:Friends]->(User)-[:Published]->(post:Post)
OPTIONAL MATCH (commenters:User)-[:Published]->(comment:Comment)-[:Commented]->post
OPTIONAL MATCH (repliers:User)-[:Published]->(reply:Reply)-[:Replied]->comment
WITH post, comment, reply
ORDER BY reply.timestamp ASC
WITH post, comment, Collect(reply) as replies
ORDER BY comment.timestamp ASC
WITH post, Collect({comment: comment, replies: replies}) as comments
ORDER BY post.timestamp DESC
RETURN Collect({post:post, comments:comments}) as posts
query explanation:
We get all our friends posts
Some Posts might not have comments yet so we use OPTIONAL MATCH on the Comment
Some Comments might not have replies so we use OPTIONAL MATCH on the Reply as well
ORDER BY the replies in ASC order.. from oldest to newest
Collect all those replies
Then ORDER BY the comments in ASC order too.. from oldest to newest
Then Collect each individual Comment and their associated replies
ORDER BY the post in DESC order.. from newest to oldest
Then Collect each individual Post and it's associated comments

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