I have a User nodes and Intersets node. I want to be able given an array of interests to create/delete/change the relationship between the User and the Interests I also want in the same query to update some properties on the user node.
So far this is what i have menage to do:
MATCH (user:User {id: id})
OPTIONAL MATCH (user)-[oldRel:InterestedIn]->(:Interest)
DETACH DELETE oldRel
WITH user
UNWIND {interestsIds} as id
MATCH (interest:Interest {id: id})
MERGE (user)-[rel: InterestedIn]->(interest)
SET user.name = {user}.name, ..(more sets)
RETURN user, collect(interest) as interests
I think this one is working tho some time is looks like the interests are returned duplicated..
As well this query looks like a bit of an overkill. Any idea how to do that query with a better way?
Does this seem about right?
MATCH (user:User {id: id})
OPTIONAL MATCH (interest:Interest)
WHERE interest.id IN {interestsIds}
MERGE (user)-[:InterestedIn]->(interest)
WITH DISTINCT user
MATCH (user)-[rel:InterestedIn]->(interest:Interest)
WHERE NOT(interest.id IN {interestsIds})
DELETE rel
WITH DISTINCT user
MATCH (user)-[:InterestedIn]->(interest:Interest)
RETURN user, collect(interest)
Related
So this is a very basic question. I am trying to make a cypher query that creates a node and connects it to multiple nodes.
As an example, let's say I have a database with towns and cars. I want to create a query that:
creates people, and
connects them with the town they live in and any cars they may own.
So here goes:
Here's one way I tried this query (I have WHERE clauses that specify which town and which cars, but to simplify):
MATCH (t: Town)
OPTIONAL MATCH (c: Car)
MERGE a = ((c) <-[:OWNS_CAR]- (p:Person {name: "John"}) -[:LIVES_IN]-> (t))
RETURN a
But this returns multiple people named John - one for each car he owns!
In two queries:
MATCH (t:Town)
MERGE a = ((p:Person {name: "John"}) -[:LIVES_IN]-> (t))
MATCH (p:Person {name: "John"})
OPTIONAL MATCH (c:Car)
MERGE a = ((p) -[:OWNS_CAR]-> (c))
This gives me the result I want, but I was wondering if I could do this in 1 query. I don't like the idea that I have to find John again! Any suggestions?
It took me a bit to wrap my head around why MERGE sometimes creates duplicate nodes when I didn't intend that. This article helped me.
The basic insight is that it would be best to merge the Person node first before you match the towns and cars. That way you won't get a new Person node for each relationship pattern.
If Person nodes are uniquely identified by their name properties, a unique constraint would prevent you from creating duplicates even if you run a mistaken query.
If a person can have multiple cars and residences in multiple towns, you also want to avoid a cartesian product of cars and towns in your result set before you do the merge. Try using the table output in Neo4j Browser to see how many rows are getting returned before you do the MERGE to create relationships.
Here's how I would approach your query.
MERGE (p:Person {name:"John"})
WITH p
OPTIONAL MATCH (c:Car)
WHERE c.licensePlate in ["xyz123", "999aaa"]
WITH p, COLLECT(c) as cars
OPTIONAL MATCH (t:Town)
WHERE t.name in ["Lexington", "Concord"]
WITH p, cars, COLLECT(t) as towns
FOREACH(car in cars | MERGE (p)-[:OWNS]->(car))
FOREACH(town in towns | MERGE (p)-[:LIVES_IN]->(town))
RETURN p, towns, cars
Relationship between two users can be found out using below query.
RETURN EXISTS( (:User{_id:'User/123'})-[:Link]-(:User{_id:'User/567'}) )
What if it needs to be run for the list of queries like for below query i want to check if relationship exists
MATCH p=(u:User{_id:'User/8199'})-[r:Link]-(u1:User)
WHERE u1._id in ['12317291','User/09563','User/392942','User/24974','User/720']
RETURN p
You could match the first use and then iterate through the lest and test for existence for each other user in the list.
MATCH (u:User {_id: 'User/8199'})
WITH u
UNWIND ['12317291','User/09563','User/392942','User/24974','User/720'] | u1] AS other_user
RETURN other_user, exists((u)-[:Link]->(:Test {name: other_user}))
Or you could do something like this if you wanted the actual users matched afterwards. Match the first user, test for a realtioship to a list of users and return a collection of matches.
MATCH (u:User {_id: 'User/8199'})
RETURN [(u)-[:Link]->(u1:User)
WHERE u1._id IN
['12317291','User/09563','User/392942','User/24974','User/720'] | u1] AS matches
So I'm trying to create a set of queries which will do the following given a list of nodes:
If there is a node which has relationships to all the nodes in the list, return that node
If there is not such a node, create a node and relationships to each node in the list
My first attempt was with these two queries:
MATCH (u:User) WHERE u.id IN {userIds}
WITH collect(u) as users
MATCH (conversation:Conversation)
WHERE ALL(u in users WHERE (u)-->(conversation))
RETURN conversation
MATCH (user:User) WHERE user.id IN {userIds}
MERGE (conversation:Conversation {id: {conversationId}})
WITH conversation
MERGE (user)-[:In]->(conversation)
RETURN conversation
But the result is that one conversation node is created for each user node, and what I want is one conversation node which is connected to all user nodes.
How can I achieve this?
In your WITH clause, you need to pass user:
MATCH (user:User) WHERE user.id IN {userIds}
MERGE (conversation:Conversation {id: {conversationId}})
WITH conversation, user
MERGE (user)-[:In]->(conversation)
RETURN conversation
A user can be friends with another user. A user can also follow a users profile page. I want to query on all friends and followed_users (users who own the followed profile page)
This return the followed_users
match (user:User {id: SOME_ID})
-[:FOLLOWED_PAGE]->(Page)<-[:PROFILE_PAGE]-(followed_user:User)
return followed_user
This return the friend
match (u:User {uuid: SOME_ID})-[:CONNECTED_USER]->(friend:User)
return friend
This will union both
match (user:User {id: SOME_ID})
-[:FOLLOWED_PAGE]->(Page)<-[:PROFILE_PAGE]-(followed_user:User)
return followed_user
UNION ALL match (u:User {uuid: SOME_ID})-[:CONNECTED_USER]->(friend:User)
return friend
The problem is, I want to keep querying. Union will return.
This is what I WANT to do, but it doesnt work (the second match overwrites the first one)
match (user:User {id: SOME_ID})
-[:FOLLOWED_PAGE]->(Page)<-[:PROFILE_PAGE]-(friend:User)
match (user)-[:CONNECTED_USER]->(friend:User)
WHERE (page:Page)<-[:COMMENTED_ON]-(friend)
return page
How can I do something like this?
EDIT: Stefan Armbruster
Initial setup query:
create (PP:Page {title: "Jim"}), (Jim:User {name: "Jim"}), (You:User {name: "You"}), (Frank:User {name: "Frank"}), (Jimpost:Post {title: "Jim posted this, You follow his page"}), (Frankpost:Post {title: "Frank posted this, You are his friend (connected_to)"}),
(You)-[:CONNECTED_USER]->(Frank),
(Frank)-[:CONNECTED_USER]->(You),
(You)-[:BOOKMARKED_PAGE]->(PP),
(PP)<-[:PROFILE_PAGE]-(Jim),
(Jimpost)<-[:POSTED]-(Jim),
(Frankpost)<-[:POSTED]-(Frank)
You are "You". Frank is your "Friend" (CONNECTED_USER). You bookmarked "Jim"'s ProfilePage (Page). So I would like to be able to query using both "Friends" and "Followed" (Users whos profile pages a user has bookmarked) users. In this instance they have both posted a page, but the use case is not unique to just posted pages
You need to use the comma , to create composite patterns:
match (user:User {id: SOME_ID})-[:FOLLOWED_PAGE]->(Page)<-[:PROFILE_PAGE]-(friend:User),
(user)-[:CONNECTED_USER]->(friend:User),
(page:Page)<-[:COMMENTED_ON]-(friend)
return page
The comma basically build up one single pattern. The separate parts are connected by commonly used identifiers (here user, page, friend).
I want to add a "created by" relationship on nodes in my database. Any node should be able of having this relationship but there can never be more than one.
Right now my query looks something like this:
MATCH (u:User {email: 'my#mail.com'})
MERGE (n:Node {name: 'Node name'})
ON CREATE SET n.name='Node name', n.attribute='value'
CREATE UNIQUE (n)-[:CREATED_BY {date: '2015-02-23'}]->(u)
RETURN n
As I have understood Cypher there is no way to achieve what I want, the current query will only make sure there are no unique relationships based on TWO nodes, not ONE. So, this will create more CREATED_BY relationships when run for another User and I want to limit the outgoing CREATED_BY relationship to just one for all nodes.
Is there a way to achieve this without running multiple queries involving program logic?
Thanks.
Update
I tried to simplyfy the query by removing implementation details, if it helps here's the updated query based on cybersams response.
MERGE (c:Company {name: 'Test Company'})
ON CREATE SET c.uuid='db764628-5695-40ee-92a7-6b750854ebfa', c.created_at='2015-02-23 23:08:15', c.updated_at='2015-02-23 23:08:15'
WITH c
OPTIONAL MATCH (c)
WHERE NOT (c)-[:CREATED_BY]-()
CREATE (c)-[:CREATED_BY {date: '2015-02-23 23:08:15'}]->(u:User {token: '32ba9d2a2367131cecc53c310cfcdd62413bf18e8048c496ea69257822c0ee53'})
RETURN c
Still not working as expected.
Update #2
I ended up splitting this into two queries.
The problem I found was that there was two possible outcomes as I noticed.
The CREATED_BY relationship was created and (n) was returned using OPTIONAL MATCH, this relationship would always be created if it didn't already exist between (n) and (u), so when changing the email attribute it would re-create the relationship.
The Node (n) was not found (because of not using OPTIONAL MATCH and the WHERE NOT (c)-[:CREATED_BY]-() clause), resulting in no relationship created (yay!) but without getting the (n) back the MERGE query looses all it's meaning I think.
My Solution was the following two queries:
MERGE (n:Node {name: 'Name'})
ON CREATE SET
SET n.attribute='value'
WITH n
OPTIONAL MATCH (n)-[r:CREATED_BY]-()
RETURN c, r
Then I had program logic check the value of r, if there was no relationship I would run the second query.
MATCH (n:Node {name: 'Name'})
MATCH (u:User {email: 'my#email.com'})
CREATE UNIQUE (n)-[:CREATED_BY {date: '2015-02-23'}]->(u)
RETURN n
Unfortunately I couldn't find any real solution to combining this in one single query with Cypher. Sam, thanks! I've selected your answer even though it didn't quite solve my problem, but it was very close.
This should work for you:
MERGE (n:Node {name: 'Node name'})
ON CREATE SET n.attribute='value'
WITH n
OPTIONAL MATCH (n)
WHERE NOT (n)-[:CREATED_BY]->()
CREATE UNIQUE (n)-[:CREATED_BY {date: '2015-02-23'}]->(:User {email: 'my#mail.com'})
RETURN n;
I've removed the starting MATCH clause (because I presume you want to create a CREATED_BY relationship even when that User does not yet exist in the DB), and simplified the ON CREATE to remove the redundant setting of the name property.
I have also added an OPTIONAL MATCH that will only match an n node that does not already have an outgoing CREATED_BY relationship, followed by a CREATE UNIQUE clause that fully specifies the User node.