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.
Related
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
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.
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
I want to order the COUNT(Movie.title) in descending order.
But it gives an error.
This is the query.
MATCH (Movie {genre:"Action"})<-[:ACTS_IN]-(Person)
"RETURN Person.name, Movie.genre, COUNT(Movie.title)"
"ORDER BY COUNT(Movie.title) DESC"
"LIMIT 100";
Thanks!
You can use this query:
MATCH (movie:Movie {genre:"Action"})<-[:ACTS_IN]-(person:Person)
RETURN person.name, movie.genre, COUNT(distinct movie.title) AS cnt
ORDER BY cnt DESC
LIMIT 100
The error is returned because you cannot order by an aggregate immediately in Cypher. To order by any aggregate you need to use the WITH operator.
So your query should be (assumes that you want to list the titles per actor per genre):
MATCH (Movie {genre:"Action"})<-[:ACTS_IN]-(Person)
RETURN Person.name, Movie.genre, COUNT(Movie.title)
WITH Person.name AS name, Movie.genre AS genre, COLLECT(Movie.title) AS titles
RETURN name, genre, titles
ORDER BY LENGTH(titles) DESC
LIMIT 100
The limit 100 has now changed its behaviour so you probably want to move it up into the query:
MATCH (Movie {genre:"Action"})<-[:ACTS_IN]-(Person)
RETURN Person.name, Movie.genre, COUNT(Movie.title)
WITH Person, Movie
LIMIT 100
WITH Person.name AS name, Movie.genre AS genre, COLLECT(Movie.title) AS titles
RETURN name, genre, titles
ORDER BY LENGTH(titles) DESC
Aside: to make your queries perform well you should have an Index on the Movie.genre property and you should introduce labels for Movie and Person.
For example, lets say my query is:
match (u:User) Where u.LivesIn:'Los Angeles' OR u.From:'Miami' OR
u.Status:'Single' OR u.Job:'Artist'}) return u
How would I change my query so I can display a column that counts how many attributes matched my query.
For my query above, lets say I returned the following Users:
> User1, Los Angeles, Miami, Single, Artist, (4 attributes matched query
> so show a 4 in column)
>
> User2, Los Angeles, Miami, Married, Artist, (3 attributes matched
> query so display 3 in column)
> User3, Los Angeles, New York, Married, Dancer, (1 attributes matched
> query so display 1 in column)
Im using this to build a sort of ranking system
Im trying to get This:
u.UserID u.MatchingAttributes
User1 4
User2 3
User3 1
Also as a bonus if you can please show how to do this with relationships also. Thanks.
You could use a bunch of CASE statements:
MATCH (u:User)
WITH u.UserID AS User, CASE WHEN u.LivesIn = 'Los Angeles' THEN 1 ELSE 0 END AS c1,
CASE WHEN u.From = 'Miami' THEN 1 ELSE 0 END AS c2,
CASE WHEN u.Status = 'Single' THEN 1 ELSE 0 END AS c3,
CASE WHEN u.Job = 'Artist' THEN 1 ELSE 0 END AS c4
RETURN User, c1 + c2 + c3 + c4 AS Matching
Suppose you graph is modelled as
Now you can name the relationships to something suitable. The sole purpose of the graph model i pasted is to just demostrate the matching strategy thats why i have skipped naming the relationships suitably and adding node labels.
Now what you can do is
match (u:User)-[r]-(m) where m.name in ['LA','Miami','Single','Artist'] return u,count(m) as count
{Assuming above the m other nodes (other than the User labelled nodes) have name attribs in them }