Neo4j match nodes that are in relationship with one OR another node - neo4j

I'm trying to wrap my head around one query. For example I have this pattern (photo:Photo)-[:AUTHOR]->(user:User). User can have friends (user:User)-[:FRIEND]->(friend:User). So how can i make a query in which i will find all :Photos made by me or my friends and sort them by date if there is any?
MATCH (user:User {id: 'me'}), (user)-[:FRIEND]-(friend:User)
//other pattern matches that I need to do
OPTIONAL MATCH (photo:Photo)-[:AUTHOR]-(user | friend)
RETURN photo
ORDER BY photo.date
LIMIT 42
But as far as I know this costruct (user | friend) is invalid. So what's correct way to do that?

If you only look for a single relationship to a defined User node, a simple way would be to use a variable length relationship with length 0 to 1. This collects all nodes with a distance of 0 (which is you start node) and all nodes with a distance of 1.
MATCH (user:User {id: 'me'})-[:FRIEND*0..1]-(me_and_friend:User)
OPTIONAL MATCH (photo:Photo)-[:AUTHOR]-(me_and_friend)
RETURN photo
ORDER BY photo.date
LIMIT 42
A more generic solution would be to collect different nodes into arrays, combine these arrays and then use UNWIND to MATCH again:
MATCH (user:User {id: 'me'}), (user)-[:FRIEND]-(friend:User)
WITH collect(user)+collect(friend) AS me_and_friends
UNWIND me_and_friends AS allusers
OPTIONAL MATCH (photo:Photo)-[:AUTHOR]-(allusers)
RETURN photo
ORDER BY photo.date
LIMIT 42
This could be useful if you MATCH longer paths or more complex patterns.

Related

Neo4j - Using Skip and limit with path and ordering the returned nodes

I am using Neo4j (version 3.5.1) and Spring-data-neo4j (5.0.10.RELEASE) in my application. I am also using OGM.
I have the below relationship between my nodes:
A vehicle (V) has Part(s) (P1, P2 and P3). Parts can themselves be linked with other parts (for e.g P2 is linked with P6)
I am trying to write a cypher query to get all the parts in a vehicle. However, I want to paginate the results and also want to order the parts ordered by the creation date (part created recently is returned first)
Below is my query:
MATCH (vehicle: Vehicle{id:{vehicleId}})
WITH vehicle MATCH p=(vehicle)-[:HAS_PART]-(part:Part)
WITH p, part SKIP 1 LIMIT 1 OPTIONAL MATCH m=(part)-[:IS_LINKED_WITH]->(:Part)
RETURN collect(nodes(p)), collect(relationships(p)), collect(nodes(m)), collect(relationships(m))
I sometimes get result size greater than 1. Also I am not sure how to order the returned Part by creation date (Part node has creationDate property set when it is created).
Any help would be highly appreciated. Thanks.
Data can be create as follows:
merge (v:Vehicle{id:'V1'})-[:HAS_PART]->(p:Part{id:'P1'})-[:IS_LINKED_WITH]->(p:Part{id:'P5'})
match (v :Vehicle{id:'V1'})
merge (v)-[:HAS_PART]->(p:Part{id:'P3'})
match (v :Vehicle{id:'V1'})
merge (v)-[:HAS_PART]->(p:Part{id:'P2'})-[:IS_LINKED_WITH]->(p:Part{id:'P6'})
You don't need to define paths in your patterns for this.
First match all parts of the vehicle (use :HAS_PART*.. if you have chained HAS_PART relationships):
MATCH (v:Vehicle {id:'V1'})-[:HAS_PART]-(part:Part)
I suppose not all parts have a IS_LINKED_WITH relationship, so use OPTIONAL MATCH for the linked parts (if you used only MATCH you wouldn't get parts with 0 linked relationships)
OPTIONAL MATCH (part)-[:IS_LINKED_WITH]-(linked:Part)
then collect all of the parts and use UNWIND so they are in single variable
WITH COLLECT(DISTINCT part) + COLLECT(DISTINCT linked) as allParts
UNWIND allParts as part
And use regular RETURN, ORDER BY, SKIP and LIMIT clauses:
RETURN DISTINCT part.id
ORDER BY part.id
SKIP 1 LIMIT 2
The whole query:
MATCH (v:Vehicle {id:'V1'})-[:HAS_PART]-(part:Part)
OPTIONAL MATCH (part)-[:IS_LINKED_WITH]-(linked:Part)
WITH COLLECT(DISTINCT part) + COLLECT(DISTINCT linked) as allParts
UNWIND allParts as part
RETURN DISTINCT part.id
ORDER BY part.id
SKIP 1 LIMIT 2

Return a graph based on the number of relationships between nodes

I am experimenting with creating multiple relationships between nodes to represent the importance between two given nodes.
For example, I want to know what 'genre' of reading material is most important to Joe.
I want a way to match the Joe node to genre nodes only if there is some number or greater relationships between them.
So, if I want matches with 3 or more relationships, I should get a graph with Joe --> Fantasy
I know I can get this when both endpoints are defined:
MATCH (p:PERSON {name:'Joe'})-[r]->(g: GENRE {name:'Fantasy'})
RETURN count(r)
What I want is something like:
MATCH p = (p:PERSON {name:'Joe'})-[r]->()
WHERE *pair_relationship_count*(r) >= 3
RETURN p
This is my proposition:
MATCH path = (p:PERSON {name:'Joe'})-[r]->()
WITH collect(path) as paths, collect(r) as pair_count
WITH paths WHERE size(pair_count) >= 3
UNWIND paths as path
RETURN path
But maybe it is more efficient to have one relationship with an internal count property on one relationship for each couple of nodes.
First, I think you can achieve your goal using WITH clause:
MATCH path = (:PERSON {name:'Joe'})-[r]->(:GENRE {name:'Fantasy'})
WITH path, count(r) as count
WHERE count > 3
RETURN path
But using one relationship for each read "event" seems to be a bad approach. Maybe you should use an integer property in the relationship, then increment this property for each "read". This way you can do queries like:
MATCH path = (:PERSON {name:'Joe'})-[r]->(:GENRE {name:'Fantasy'})
WHERE r.count > 3
RETURN path
To get a collection of all READS paths for "Joe" that involve each genre that he has read at least 3 times:
MATCH p = (:PERSON {name:'Joe'})-[:READS]->(genre)
WITH genre, COLLECT(p) AS paths
WHERE SIZE(paths) >= 3
RETURN genre, paths;

Cypher: Match All Elements In A Collection

I have a collection of element ids. I am currently using an UNWIND to find nodes that match at least one element.
UNWIND ids as id
MATCH (e:Element)-[]-(f:Foo {id:id})
RETURN DISTINCT e
I wanted to find out how I could find the elements that match ALL the ids. Such that if I have 5 ids, the elements that have a relation to all 5 matching nodes and exclude nodes that only match 1 or 2.
I was thinking I could do something with COUNT and pass in the number of elements as a query parameter, but that seemed pretty hacky.
Does anyone know how to do return nodes that match all elements in a collection?
Is there only a single relationship between Element and Foo nodes? If so, how about comparing the number of ids you have to the number of relationships connecting each distinct Element to Foo nodes:
UNWIND ids AS id
MATCH (e:Element)-[r]->(f:Foo {id: id})
WITH e, COUNT(r) AS num WHERE num = SIZE(ids)
RETURN e

How to add multiple relationships to a single node in one query

Consider this example: I have one Author node, and several Book nodes. I want to create the WROTE relationship between the Author and several Book nodes in a single cypher Query statement. Also, the only way I have to look up the nodes for both the Author and all the Book nodes is by their node ID.
Here's what I tried:
MATCH (a:Author) WHERE id(a) = '31'
MATCH (b0:Book) WHERE id(b0) = '32'
MATCH (b1:Book) WHERE id(b1) = '33'
CREATE (b0)<-[:WROTE {order : '0'}]-(a)
CREATE (b1)<-[:WROTE {order : '1'}]-(a)
However, it doesn't seem to work.
Thanks.
Neo4js native ids are stored as numbers, so do not match by string. Also Cypher has IN clause that lets you match arrays, so you can simplify you query to this
MATCH (a:Author) where id(a)=31
MATCH (b:Book) where id(b) in [32,33]
CREATE (a)-[:WROTE]->(b)
Found the issue to be that I shouldn't be using the ' when matching node IDs!

Neo4j cypher get status updates with 'total likes' and 'has liked'

I am using the following query to get user status updates and total likes each have received:
MATCH (n:user {username: "pewpewlasers"})-[:STATUSUPDATE|:NEXT*]->(o)
OPTIONAL MATCH (p:user)-[x:LIKED]->(o)
RETURN n, o, COUNT(p) AS totallikes
ORDER BY o.date DESC SKIP 0 LIMIT 10
This works great, but now I want to also get information on whether a particular user has liked these status updates (to implement 'unlike' feature). I tried the following and it is giving me big (wrong) numbers, even the totallikes
MATCH (n:user {username: "pewpewlasers"})-[:STATUSUPDATE|:NEXT*]->(o)
OPTIONAL MATCH (p:user)-[:LIKED]->(o)
OPTIONAL MATCH (q:user {username: "anotherUser"})-[:LIKED]->(o)
RETURN n, o, COUNT(p) AS totallikes, COUNT(q) AS hasLiked
ORDER BY o.date DESC SKIP 0 LIMIT 10
EDIT: The graph looks like the following (orange nodes -> user nodes, gray nodes -> status update nodes):
Your 2 OPTIONAL MATCH clauses are essentially identical, except for the username qualification in the second one. Therefore, logically, it should be possible for a single relationship to match both clauses. However, according to the neo4j manual:
While pattern matching, Cypher makes sure to not include matches where
the same graph relationship is found multiple times in a single
pattern.
It is possible that this is causing the number of matches to be too low.
Does the following work better for you?
[EDITED]
MATCH (n:user {username: "pewpewlasers"})-[:STATUSUPDATE|:NEXT*]->(o)
OPTIONAL MATCH (p:user)-[:LIKED]->(o)
WITH n, o, COLLECT(p) AS cp
RETURN n, o, length(cp) AS totallikes, length(filter(x IN cp WHERE x.username="anotherUser")) AS hasLiked
ORDER BY o.date DESC LIMIT 10

Resources