How to return a list of related nodes for each node in Cypher - neo4j

I would like to write a query for the graph below, which should return three lists, each list containing all persons working for the same person.
Given the graph below, the result should be this three lists:
[Fritz]
[Pepe]
[Susy, Peter]
I fail to write such query. My query returns all employes in one list.
The following statements create the graph model for the example I have given:
MATCH (c:Example) DETACH DELETE c;
CREATE (p1:Parent:Example {id: 1, name: 'Andy', title: 'Developer'});
CREATE (p2:Parent:Example {id: 2, name: 'Lila', title: 'Developer'});
CREATE (p3:Parent:Example {id: 3, name: 'Lula', title: 'Developer'});
CREATE (c11:Child:Example {id: 11, name: 'Peter', title: 'Developer'});
CREATE (c12:Child:Example {id: 12, name: 'Susy', title: 'Developer'});
CREATE (c21:Child:Example {id: 21, name: 'Fritz', title: 'Developer'});
CREATE (c31:Child:Example {id: 31, name: 'Pepe', title: 'Developer'});
MATCH (p {id: 1}), (c {id: 11}) MERGE (p)<-[:WORKS_FOR]-(c);
MATCH (p {id: 1}), (c {id: 12}) MERGE (p)<-[:WORKS_FOR]-(c);
MATCH (p {id: 2}), (c {id: 21}) MERGE (p)<-[:WORKS_FOR]-(c);
MATCH (p {id: 3}), (c {id: 31}) MERGE (p)<-[:WORKS_FOR]-(c);

It's relatively straightforward with Cypher
MATCH (c)-[:WORKS_FOR]->(p)
RETURN p.name AS boss, collect(c.name) AS coWorkers
Result
╒══════╤════════════════╕
│"boss"│"coWorkers" │
╞══════╪════════════════╡
│"Andy"│["Peter","Susy"]│
├──────┼────────────────┤
│"Lila"│["Fritz"] │
├──────┼────────────────┤
│"Lula"│["Pepe"] │
└──────┴────────────────┘
The trick is understanding aggregations https://neo4j.com/docs/cypher-manual/current/functions/aggregating/#grouping-keys

Related

Neo4j Group result based on property

I have some data looks like this:
{Name: Peter
Gender: Male
Like: movie, game}
{Name:Ame
Gender: Female
Like:dog, movie, hiking}
{Name: Greg
Gender: Male
Like: hiking}
I want to group them by what they like and generate a table like:
Like: movie //Peter, Ame
count: 2
like: game //Peter
count: 1
like: dog //Ame
count: 1
like: hiking //Ame, Greg
count: 2
How can I query to get this result? Or should I redo the schema?
Something like
MATCH (person:Person) RETURN DISTINCT person.like AS Like, count(person.like) AS Count
This gives me
Like: movie, game
Count: 1
Like: dog, movie, hiking
Count: 1
Like: hiking
Count: 1
Thank you for helping.
A more graph-oriented data model would be more suitable.
For example, you can have Person and Interest nodes connected by LIKES relationships:
MERGE (peter:Person {name: 'Peter', gender: 'M'})
MERGE (ame:Person {name: 'Ame', gender: 'F'})
MERGE (greg:Person {name: 'Greg', gender: 'M'})
MERGE (movies:Interest {name: 'Movies'})
MERGE (games:Interest {name: 'Games'})
MERGE (hiking:Interest {name: 'Hiking'})
MERGE (dogs:Interest {name: 'Dogs'})
MERGE (peter)-[:LIKES]->(movies)
MERGE (peter)-[:LIKES]->(games)
MERGE (ame)-[:LIKES]->(dogs)
MERGE (ame)-[:LIKES]->(movies)
MERGE (ame)-[:LIKES]->(hiking)
MERGE (greg)-[:LIKES]->(hiking);
With the above data, you can easily group people by their interests. A couple of examples:
MATCH (interest:Interest)
RETURN interest, [(p)-[:LIKES]->(interest) | p] AS people;
or
MATCH (interest:Interest)<-[:LIKES]-(p)
RETURN interest, COLLECT(p) AS people;
The result would be:
╒═════════════════╤═══════════════════════════════════════════════════════════╕
│"interest" │"people" │
╞═════════════════╪═══════════════════════════════════════════════════════════╡
│{"name":"Movies"}│[{"gender":"M","name":"Peter"},{"gender":"F","name":"Ame"}]│
├─────────────────┼───────────────────────────────────────────────────────────┤
│{"name":"Games"} │[{"gender":"M","name":"Peter"}] │
├─────────────────┼───────────────────────────────────────────────────────────┤
│{"name":"Hiking"}│[{"gender":"F","name":"Ame"},{"gender":"M","name":"Greg"}] │
├─────────────────┼───────────────────────────────────────────────────────────┤
│{"name":"Dogs"} │[{"gender":"F","name":"Ame"}] │
└─────────────────┴───────────────────────────────────────────────────────────┘
Assuming Like property is an array you could do the following
MATCH(person:Person)
UNWIND person.like as like
RETURN like, count(*)

How to limit the number of relationships between nodes?

I have a question concerning how to limit the number of created relationships between nodes. I sure can limit the number or resulted notes when performing the MATCH. But I am, in fact, more concerned with the idea of not storing data (in this case relationships), as I will never use it in the future.
In my scenario, I have the following graph:
CREATE (u:User {id: 100001}), (:Artist {id: "0001"}), (:Artist {id: "0002"}), (:Artist {id: "0003"}), (:Artist {id: "0004"}), (:Artist {id: "0005"}),(:Artist {id: "0006"}),(:Artist {id: "0007"}),(:Artist {id: "0008"}),(:Artist {id: "0009"}),(:Artist {id: "0010"});
Notice that I have a User and 10 different Artists.
My requirement is to store the last 5 artists that a User has listened to via the LISTENED_TO relationship. Therefore after executing:
MATCH (u:User {id: 100001}), (a:Artist {id: "0001"})
CREATE (u)-[:LISTENED_TO]->(a);
MATCH (u:User {id: 100001}), (a:Artist {id: "0003"})
CREATE (u)-[:LISTENED_TO]->(a);
MATCH (u:User {id: 100001}), (a:Artist {id: "0005"})
CREATE (u)-[:LISTENED_TO]->(a);
MATCH (u:User {id: 100001}), (a:Artist {id: "0007"})
CREATE (u)-[:LISTENED_TO]->(a);
MATCH (u:User {id: 100001}), (a:Artist {id: "0009"})
CREATE (u)-[:LISTENED_TO]->(a);
I would have a graph like:
(u:User {id: 100001})-[l:LISTENED_TO]->(a:Artist {id: "0001"})
(u:User {id: 100001})-[l:LISTENED_TO]->(a:Artist {id: "0003"})
(u:User {id: 100001})-[l:LISTENED_TO]->(a:Artist {id: "0005"})
(u:User {id: 100001})-[l:LISTENED_TO]->(a:Artist {id: "0007"})
(u:User {id: 100001})-[l:LISTENED_TO]->(a:Artist {id: "0009"})
Now, I have the information that this user has listened to 5 different artists. Let us assume now that the User listened to a song from the Artist {id: "0010"} and I would like that the first inserted relationship, (u:User {id: 100001})-[l:LISTENED_TO]->(a:Artist {id: "0001"}) being removed (like using a FIFO-like mechanism) and the new graph would be like:
(u:User {id: 100001})-[l:LISTENED_TO]->(a:Artist {id: "0003"})
(u:User {id: 100001})-[l:LISTENED_TO]->(a:Artist {id: "0005"})
(u:User {id: 100001})-[l:LISTENED_TO]->(a:Artist {id: "0007"})
(u:User {id: 100001})-[l:LISTENED_TO]->(a:Artist {id: "0009"})
(u:User {id: 100001})-[l:LISTENED_TO]->(a:Artist {id: "0010"})
Maybe I am stretching the features supported by Neo4J, but I wonder if this would be possible. My objective is to save space which I do not need to store as I just need the last 5 most recently used (in this case listened to) artists.
If a LISTENED_TO relationship contains a timestamp in a time property, then you can use this to retain just the 5 most recent relationships when adding a new one (assuming that the timestamp of the new relationship is always going to be recent enough, and that you pass userId, artistId, and time parameters):
MATCH (u:User {id: $userId})
OPTIONAL MATCH (u)-[lt:LISTENED_TO]->(:Artist)
WITH u, lt ORDER BY lt.time DESC
WITH u, COLLECT(lt) AS lts
FOREACH(x IN lts[4..] | DELETE x)
MERGE (a:Artist {id: $artistId})
CREATE (u)-[:LISTENED_TO {time: $time}]->(a)
[UPDATE]
NOTE: The above query allows the same artist to have multiple relationships to the same user if that user had listened to that artist multiple times recently.
If you want an artist to have at most one relationship to a specific user, then this more complex query should work:
MATCH (u:User {id: $userId})
OPTIONAL MATCH p= (u)-[lt:LISTENED_TO]->(a:Artist)
WITH u, {lt: lt, a: a} AS data ORDER BY lt.time DESC
WITH u, REDUCE(
s = {cnt: 0, del: []}, x IN COLLECT(data) |
CASE WHEN x.a.id = $artistId OR s.cnt = 4
THEN {cnt:s.cnt, del:s.del + x.lt}
ELSE {cnt:s.cnt + 1, del:s.del} END).del AS del
FOREACH(x IN del | DELETE x)
MERGE (a:Artist {id: $artistId})
CREATE (u)-[:LISTENED_TO {time: $time}]->(a)

Neo4j/Cypher: How to exclude/include nodes with multiple properties?

I have a collection of nodes with multiple properties, for example:
(:Item {id: 1, type: 10})
(:Item {id: 2, type: 10})
(:Item {id: 1, type: 11})
(:Item {id: 1, type: 13})
(:Item {id: 2, type: 11})
In other examples, I can include nodes using a single value, using a query like this (with the IN clause):
MATCH (x:Item)
WHERE x.type IN [10,13]
RETURN x
Im trying to include/exclude nodes with certain properties, something like the next query:
MATCH (x:Item)
WHERE x IN [({id: 1, type: 10}), ({id: 2, type: 11})]
RETURN x
with the above query i don't get an error, but i don't get any results either. Could it be possible so that it includes or excludes certain nodes? In the above example could be more easy do 2-MATCHs for the 2 nodes, but in the real problem i want to include/exclude more than 10 nodes dynamically.
x in your example is much more that just a map with type and id, it is an identifier that represents the complete node.
If you rewrite x as a map of x.id and x.type then you can make that comparison.
MATCH (x:Item)
WHERE {id: x.id, type: x.type} IN [({id: 1, type: 10}), ({id: 2, type: 11})]
RETURN x

Neo4J query doesn't show path

I'm creating test database for transport in a city.
My goal find path betweeb any stops.
I created this graph:
create (Stop_13_1:Tram {Id: 131}),
(Stop_13_2:Tram {Id: 132}),
(Stop_26_1:Tram {Id: 261}),
(Stop_26_2:Tram {Id: 262}),
(Stop_26_3_13_3:Tram {Id: 263133}),
(Stop_26_4_13_4:Tram {Id: 264134}),
(Stop_26_5_13_5:Tram {Id: 265135}),
(Stop_26_6_13_6:Tram {Id: 266136}),
(Stop_26_7_13_7:Tram {Id: 267137}),
(Stop_26_8:Tram {Id: 268}),
(Stop_7_1:Trollebus {Id: 71}),
(Stop_7_2:Trollebus {Id: 72}),
(Stop_7_3:Trollebus {Id: 73}),
(Stop_7_4:Trollebus {Id: 74}),
(Stop_7_5:Trollebus {Id: 75});
When I try find short way:
match p=shortestPath((a)-[:TO*]-(c))
where a.Id=131 and c.Id=268
return p, length(p) limit 1
Or this query:
MATCH (p1:Tram {id: 131}), (p2:Tram {id: 263133}),
path = shortestpath((p1)-[:NEXT*]-(p2))
RETURN path
It's doesn't show any route.
Can you please help me edit query?
P.S. I forgot add relation:
MATCH (Stop_13_1 {Id: 131}),
(Stop_13_2 {Id: 132}),
(Stop_26_1 {Id: 261}),
(Stop_26_2 {Id: 262}),
(Stop_26_3_13_3 {Id: 263133}),
(Stop_26_4_13_4 {Id: 264134}),
(Stop_26_5_13_5 {Id: 265135}),
(Stop_26_6_13_6 {Id: 266136}),
(Stop_26_7_13_7 {Id: 267137}),
(Stop_26_8 {Id: 268}),
(Stop_7_1 {Id: 71}),
(Stop_7_2 {Id: 72}),
(Stop_7_3 {Id: 73}),
(Stop_7_4 {Id: 74}),
(Stop_7_5 {Id: 75})
MERGE (Stop_13_1)- [:NEXT{distance:4.7,route:13,transport:'tram',direct:'down'}]->(Stop_13_2)
MERGE (Stop_13_2)-[:NEXT{distance:4.7,route:13,transport:'tram',direct:'up'}]->(Stop_13_1)
MERGE (Stop_13_2)-[:NEXT{distance:3.7,route:13,transport:'tram',direct:'down'}]->(Stop_26_3_13_3)
MERGE (Stop_26_3_13_3)-[:NEXT{distance:3.7,route:13,transport:'tram',direct:'up'}]->(Stop_13_2)
MERGE (Stop_26_1)-[:NEXT{distance:5.8,route:26,transport:'tram',direct:'down'}]->(Stop_26_2)
MERGE (Stop_26_2)-[:NEXT{distance:5.8,route:26,transport:'tram',direct:'up'}]->(Stop_26_1)
MERGE (Stop_26_2)-[:NEXT{distance:2.5,route:26,transport:'tram',direct:'down'}]->(Stop_26_3_13_3)
MERGE (Stop_26_3_13_3)-[:NEXT{distance:2.5,route:26,transport:'tram',direct:'up'}]->(Stop_26_2)
MERGE (Stop_26_3_13_3)-[:NEXT{distance:3.1,route:26,route:13,transport:'tram',direct:'down'}]->(Stop_26_4_13_4)
MERGE (Stop_26_4_13_4)-[:NEXT{distance:3.1,route:26,route:13,transport:'tram',direct:'up'}]->(Stop_26_3_13_3)
MERGE (Stop_26_4_13_4)-[:NEXT{distance:5.8,route:26,route:13,transport:'tram',direct:'down'}]->(Stop_26_5_13_5)
MERGE (Stop_26_5_13_5)-[:NEXT{distance:5.8,route:26,route:13,transport:'tram',direct:'up'}]->(Stop_26_4_13_4)
MERGE (Stop_26_5_13_5)-[:NEXT{distance:10.8,route:26,route:13,transport:'tram',direct:'down'}]->(Stop_26_6_13_6)
MERGE (Stop_26_6_13_6)-[:NEXT{distance:10.8,route:26,route:13,transport:'tram',direct:'up'}]->(Stop_26_5_13_5)
MERGE (Stop_26_6_13_6)-[:NEXT{distance:2.5,route:26,route:13,transport:'tram',direct:'down'}]->(Stop_26_7_13_7)
MERGE (Stop_26_7_13_7)-[:NEXT{distance:2.5,route:26,route:13,transport:'tram',direct:'up'}]->(Stop_26_6_13_6)
MERGE (Stop_26_5_13_5)-[:NEXT{distance:0.6,transport:'walking',direct:'down'}]->(Stop_7_2)
MERGE (Stop_7_2)-[:NEXT{distance:0.6,transport:'walking',direct:'up'}]->(Stop_26_5_13_5)
MERGE (Stop_26_8)-[:NEXT{distance:1,route:26,transport:'tram',direct:'down'}]->(Stop_26_7_13_7)
MERGE (Stop_26_7_13_7)-[:NEXT{distance:1,route:26,transport:'tram',direct:'up'}]->(Stop_26_8)
MERGE (Stop_7_1)-[:NEXT{distance:2.2,route:7,transport:'trolleybus',direct:'down'}]->(Stop_7_2)
MERGE (Stop_7_2)-[:NEXT{distance:2.2,route:7,transport:'trolleybus',direct:'up'}]->(Stop_7_1)
MERGE (Stop_7_2)-[:NEXT{distance:1.6,route:7,transport:'trolleybus',direct:'up'}]->(Stop_7_3)
MERGE (Stop_7_3)-[:NEXT{distance:2.5,route:7,transport:'trolleybus',direct:'up'}]->(Stop_7_4)
MERGE (Stop_7_4)-[:NEXT{distance:3.1,route:7,transport:'trolleybus',direct:'down'}]->(Stop_7_5)
MERGE (Stop_7_5)-[:NEXT{distance:4.4,route:7,transport:'trolleybus',direct:'down'}]->(Stop_7_2)
You are trying to match shortest paths that have a TO relationship, however your graph shows that the type of the relationship is NEXT.
I replicated your graph here http://console.neo4j.org/r/boin78
And the following query is working as expected by just specifying the correct relationship type :
match p=shortestPath((a)-[:NEXT*]-(c))
where a.Id=131 and c.Id=268
return p, length(p)
limit 1
For the second query, you used the id as property while your nodes have the Id property name, the following is working :
MATCH (p1:Tram {Id: 131}), (p2:Tram {Id: 263133}),
path = shortestpath((p1)-[:NEXT*]-(p2))
RETURN path

Active record: filter on distinct value

I have several entries in a table in my RoR app:
{id: 1, item_id: a, ...}
{id: 2, item_id: a, ...}
{id: 3, item_id: a, ...}
{id: 4, item_id: b, ...}
{id: 5, item_id: b, ...}
{id: 6, item_id: c, ...}
{id: 7, item_id: d, ...}
{id: 8, item_id: d, ...}
I would like to create a query that filters on distinct item_id and returns the first matching entry, so that the result would be
{id: 1, item_id: a, ...}
{id: 4, item_id: b, ...}
{id: 6, item_id: c, ...}
{id: 7, item_id: d, ...}
When using
select(:item_id).distinct
I cannot access the other attributes of the entries. How can this be accomplished?
If you just want to get the lowest id for every item_id, you could use an aggregate query to retrieve these ids:
grouped_with_min_id = Model.group(:item_id).minimum(:id)
rows = Model.find(grouped_with_min_id.map(&:id))
This may not be the most efficient solution as it uses two queries. But other methods will probably require you to use Model.find_by_sql and write your own sql.
Disclaimer: I did not try that out.

Resources