How to limit the number of relationships between nodes? - neo4j

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)

Related

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

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

Neo4J Cypher v2 Create Uniquely Labelled Relation With Changing Fields

I have two users:
CREATE (a:user {id: 1})
CREATE (b:user {id: 2})
I want users to be able to follow each other:
MATCH (a:user {id: 1}), (b:user {id: 2})
CREATE (a)-[r:FOLLOWS]->(b)
But I also need to keep track of when that follow happened:
MATCH (a:user {id: 1}), (b:user {id: 2})
CREATE (a)-[r:FOLLOWS {t: 32409823}]->(b)
My issue is that I need create the :FOLLOWS relation if it does not already exist without making a query to check, then another query to create it. Ideally CREATE UNIQUE would solve this, which works just fine without any changing fields on the relation:
MATCH (a:user {id: 1}), (b:user {id: 2})
CREATE UNIQUE (a)-[r:FOLLOWS]->(b)
(THIS WORKS)
But when I include a timestamp on the relation, create unique will make a second relation because it has a different timestamp.
MATCH (a:user {id: 1}), (b:user {id: 2})
CREATE UNIQUE (a)-[r:FOLLOWS {t: 32409823}]->(b)
(THIS DOESN'T WORK)
The above creates a new relation every time because the timestamp is always changing. Is there any way I can check if any relation with the label :FOLLOWS exists and create the relation with fields if it doesn't?
MERGE and its ON CREATE clause should do what you want. MERGE will match on the :FOLLOWS relationship, and if it does not exist it will create it. ON CREATE is only performed if the MERGE operation created the relationship instead of matching on an existing one.
MATCH (a:user {id: 1}), (b:user {id: 2})
MERGE (a)-[r:FOLLOWS]->(b)
ON CREATE SET r.t = timestamp()

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

How to find all relationships in cypher

Having a graph where molecules are attached to a common scaffold with [:substructureOf] relationships and where similar molecules are connected to eachothers with a [:isSimilarTo] relationship, is the a way to return all [:isSimilarTo] relationships for a specific subset of molecules?
In pseudo cypher code, considering m as a collection (subset of molecules), I would like to assess that m1 and m2 of each sim relationship is part of m
MATCH (:Scaffold {Name: 'A'}) <-[:usbstructureOf]- (m:Molecule)
WITH m MATCH (m1:Molecule) -[sim:isSimilarTo]- (m2:Molecule)
WHERE m1 IN m AND m2 IN m
Is there a proper cypher way to write this? Example dataset below.
CREATE (:Scaffold {Name: 'A'});
CREATE (:Scaffold {Name: 'B'});
MATCH (s:Scaffold {Name: 'A'}) MERGE (s) -[:substructureOf]->(:Molecule {Name: 'm1'});
MATCH (s:Scaffold {Name: 'A'}) MERGE (s) -[:substructureOf]->(:Molecule {Name: 'm2'});
MATCH (s:Scaffold {Name: 'A'}) MERGE (s) -[:substructureOf]->(:Molecule {Name: 'm3'});
MATCH (s:Scaffold {Name: 'A'}) MERGE (s) -[:substructureOf]->(:Molecule {Name: 'm4'});
MATCH (s:Scaffold {Name: 'B'}) MERGE (s) -[:substructureOf]->(:Molecule {Name: 'm5'});
MATCH (m:Molecule {Name: 'm1'}), (n:Molecule {Name: 'm2'}) CREATE (m) -[isSimilarTo]-> (n);
MATCH (m:Molecule {Name: 'm1'}), (n:Molecule {Name: 'm3'}) CREATE (m) -[isSimilarTo]-> (n);
MATCH (m:Molecule {Name: 'm2'}), (n:Molecule {Name: 'm3'}) CREATE (m) -[isSimilarTo]-> (n);
MATCH (m:Molecule {Name: 'm3'}), (n:Molecule {Name: 'm4'}) CREATE (m) -[isSimilarTo]-> (n);
MATCH (m:Molecule {Name: 'm4'}), (n:Molecule {Name: 'm5'}) CREATE (m) -[isSimilarTo]-> (n);
I still don't fully understand what you want, a network scheme/drawing might help next time.
But I think what you want is similar molecules sharing the same scaffold. I.e. all Molecule pairs connected by isSimilarTo edges, where both Molecules are linked to a defined Scaffold.
You can get this by matching the complete path:
(Scaffold)--(Molecule)--(similar Molecule)--(same Scaffold)
In Cypher:
MATCH (s:Scaffold {Name: 'A'})<-[:isbstructureOf]-(m1:Molecule)
-[sim:isSimilarTo]-(m2:Molecule)-[:isbstructureOf]->(s)
// Return the relationships
RETURN DISTINCT m1.name, sim.value, m2.name
// Return count of relationships
RETURN count(DISTINCT sim)
Alternative answer:
MATCH (s:Scaffold {Name: 'A'})-[:substructureOf*]->(m:Molecule)
WITH collect(m) as mols MATCH p=(:Molecule) -[sim:isSimilarTo]- (:Molecule)
WHERE startNode(r) IN mols and endNode(r) IN mols
RETURN p
The overall path is shorter.

Cypher to find similar nodes without repeating matches

I am new to cypher. I want to find similar nodes without repeating matches.
Sample data
CREATE (r1:Repository {id:"repository1"})
CREATE (r2:Repository {id:"repository2"})
CREATE (r3:Repository {id:"repository3"})
CREATE (a1:Actor {id: "actor1"})
CREATE (a2:Actor {id: "actor2"})
CREATE (a3:Actor {id: "actor3"})
CREATE (o1:Organization {id:"organization1"})
CREATE (o2:Organization {id:"organization2"})
MATCH (a:Repository {id:"repository1"}) MATCH (b:Actor {id: 'actor1'})
CREATE (a)-[:IS_ACTOR]->(b)
MATCH (a:Repository {id:"repository1"}) MATCH (b:Actor {id: 'actor2'})
CREATE (a)-[:IS_ACTOR]->(b)
MATCH (a:Repository {id:"repository1"}) MATCH (b:Actor {id: 'actor3'})
CREATE (a)-[:IS_ACTOR]->(b)
MATCH (a:Repository {id:"repository1"}) MATCH (b:Organization {id:
'organization1'}) CREATE (a)-[:IN_ORGANIZATION]->(b)
MATCH (a:Repository {id:"repository2"}) MATCH (b:Actor {id: 'actor1'})
CREATE (a)-[:IS_ACTOR]->(b)
MATCH (a:Repository {id:"repository2"}) MATCH (b:Actor {id: 'actor2'})
CREATE (a)-[:IS_ACTOR]->(b)
MATCH (a:Repository {id:"repository2"}) MATCH (b:Organization {id:
'organization1'}) CREATE (a)-[:IN_ORGANIZATION]->(b)
MATCH (a:Repository {id:"repository3"}) MATCH (b:Actor {id: 'actor3'})
CREATE (a)-[:IS_ACTOR]->(b)
MATCH (a:Repository {id:"repository3"}) MATCH (b:Organization {id:
'organization2'}) CREATE (a)-[:IN_ORGANIZATION]->(b)
Cypher
MATCH (a)-[r1:IS_ACTOR|IN_ORGANIZATION]->(match)<-
[r2:IS_ACTOR|IN_ORGANIZATION]-(b)
where not a.id = b.id with a,b,count(match) as count, collect (match.id) as
connections, collect (type(r1)) as rel1
return a.id,b.id,count,connections,rel1 order by count desc
Result
a.id b.id count connections rel1
repository2 repository1 3 actor1,actor2,organization1 IS_ACTOR, IS_ACTOR,IN_ORGANIZATION
repository1 repository2 3 actor1,actor2,organization1 IS_ACTOR, IS_ACTOR,IN_ORGANIZATION
repository3 repository1 1 actor3 IS_ACTOR
repository1 repository3 1 actor3 IS_ACTOR
How can I remove row #2 & #4 from the result?
Based on response to a similar question I tried using filter but I get syntax error (cypher below)
MATCH (a)-[r1:IS_ACTOR|IN_ORGANIZATION]->(match)<-
[r2:IS_ACTOR|IN_ORGANIZATION]-(b)
with filter(x in connections where x <> b.id)
where not a.id = b.id with a,b,count(match) as count, collect (match.id) as
connections, collect (type(r1)) as rel1
return a.id,b.id,count,connections,rel1 order by count desc
You match the path once from both sides, something that you can do to force only one of those paths to be returned. Compare the id's so you put a and b in a fixed order and avoid the other combo.
MATCH (a)-[r1:IS_ACTOR|IN_ORGANIZATION]->(match)
<-[r2:IS_ACTOR|IN_ORGANIZATION]-(b)
where id(a) > id(b)
with a,b,count(match) as count,
collect (match.id) as connections, collect (type(r1)) as rel1
return a.id,b.id,count,connections,rel1 order by count desc

Resources