Having a graph of people who like rated movies, I would like to extract for each pair of people their highest rated movie. I'm using the following query which requires sorting movies on their rate for each pair of people.
MATCH (p1:People) -[:LIKES]-> (m:Movie) <-[:LIKES]- (p2:People) WHERE id(p1) < id(p2)
WITH p1, p2, m ORDER BY m.Rating desc
RETURN p1, p2, head(collect(m) as best
I can put movie rating (1/rating or maxRating-rating) into :LIKES relationships, which hence let me identify which movie is in the top rating of both people.
MATCH (p1:People), (p2:People) call apoc.algo.dijkstra(p1, p2, 'LIKES', 'rating') YIELD path as path, weight as weight return path, weight
Is there a way to use a Dijkstra-like algorithm which would find the allOptimumPath through highest scored nodes to improve the performance of my first query and return paths rather than their starting, middle and ending nodes ?
Many thanks in advance.
Here is an alternate solution which preserves the path rather than reporting extracted nodes.
MATCH path=(p1:People) -[:LIKES]-> (m:Movie) <-[:LIKES]- (p2:People)
WHERE id(p1) < id(p2)
WITH head(nodes(p)) as p1, last(nodes(p)) as p2, path
ORDER BY m.Rating desc
WITH p1, p2, head(collect(p)) as optPath
RETURN optPath
Related
Hi I have a relationship Artist - Collaborated -> Writer and would like to find who are the artists who write mainly their own songs. Thus the weighted edge between writer and artist with the same name should be bigger than the sum of all other weights.
I managed to do this:
MATCH (n:Artist)-[r:Collaborated]-(m:Writer)
WITH n, m, sum(r.weight) as wrote
WHERE n.name = toLower(m.name)
RETURN n.name as Node, wrote ORDER BY wrote descending;
but I am not sure how to incorporate the second condition. Do I have to use post union processing? Any help pls?
To join the two WHERE conditions, I tried something like this and compare the first sum to the second sum but it doesn't work:
MATCH (o:Artist)-[q:Collaborated]-(p:Writer)
WITH o, p, sum(q.weight) as wrote1
WHERE o.name <> toLower(p.name)
MATCH (n:Artist)-[r:Collaborated]-(m:Writer)
WITH n, m, sum(r.weight) as wrote2
WHERE n.name = toLower(m.name) and wrote2>wrote1
RETURN n.name as Node, wrote2;
This is an example of how my graph looks like:
I would like to know if the weight between eminem and eminem is bigger than all the other weights
Firstly, your model is a little weird, you have two nodes Eminem, one with the label Artist and an other with the label Writer.
For my POV, you should have only one node Eminem with both labels.
To respond to your question I think that this query can helps you :
MATCH (o:Artist)-[r:Collaborated]->(p:Writer)
WITH o, CASE WHEN o.name = p.name THEN r.weight ELSE -1*r.weight END AS score
RETURN o, sum(score) AS score
If the score is superior to 0, then you know that eminem and eminem is bigger than all the other weights.
Having this data for example :
CREATE
(p1:Person {name:"p1"}),
(p2:Person {name:"p2"}),
(p3:Person {name:"p3"}),
(p4:Person {name:"p4"}),
(p5:Person {name:"p5"}),
(p1)-[:KNOWS]->(p2),
(p1)-[:KNOWS]->(p3),
(p1)-[:KNOWS]->(p4),
(p5)-[:KNOWS]->(p3),
(p5)-[:KNOWS]->(p4)
I want to get common relationships between p1 and p5 :
MATCH (p1:Person {name:"p1"})-[r1:KNOWS]-(p:Person)-[r2:KNOWS]-(p5:Person {name:"p5"})
RETURN p, p1, p5
This returns 4 nodes : p1, p3, p4, p5 and 4 edges.
My aim is to get edges with direction as table rows : from and to. So this seems to works :
MATCH (p1:Person {name:"p1"})-[r1:KNOWS]-(p:Person)-[r2:KNOWS]-(p5:Person {name:"p5"})
RETURN startNode(r1).name AS from, endNode(r1).name AS to
UNION
MATCH (p1:Person {name:"p1"})-[r1:KNOWS]-(p:Person)-[r2:KNOWS]-(p5:Person {name:"p5"})
RETURN startNode(r2).name AS from, endNode(r2).name AS to
The result is a table :
from | to
-----|----
p1 | p3
p1 | p4
p5 | p3
p5 | p4
My questions are :
Is it correct ?
Is it the best way to do it ? I mean about performance when there will be thousands of nodes.
And what if i want common nodes to 3 persons ?
The best way to check performance is to PROFILE your queries.
Is it correct ?
I'm not sure why you do a UNION, you can easily use a path check :
PROFILE MATCH (p1:Person {name:"p1"}), (p5:Person {name:"p5"})
MATCH path=(p1)-[*..2]-(p5)
UNWIND rels(path) AS r
RETURN startNode(r).name AS from, endNode(r).name AS to
Is it the best way to do it ? I mean about performance when there will be thousands of nodes.
Generally you would match first the start and end nodes of the path you want with single lookups (make sure you have an index/constraint on the label/property pair for the Person nodes).
Depending on your graph degree this can be an extensive operation, you can fine tune by limiting the max depth of the paths *..15 for example.
And what if i want common nodes to 3 persons ?
There are multiple ways depending on the size of your graph :
a) if not too many nodes :
Match the 3 nodes and find Persons that have at least one connection to ALL 3:
PROFILE MATCH (p:Person) WHERE p.name IN ["p1","p4","p3"]
WITH collect(p) AS persons
MATCH (p:Person) WHERE ALL(x IN persons WHERE EXISTS((x)--(p)))
RETURN p
b) some tuning, assume one common will be directly connected to the first node in the 3
PROFILE MATCH (p:Person) WHERE p.name IN ["p1","p4","p3"]
WITH collect(p) AS persons
WITH persons, persons[0] as p
MATCH (p)-[:KNOWS]-(other)
WHERE ALL (x IN persons WHERE EXISTS((x)--(other)))
RETURN other
c) if you need the commons in a multiple depth path :
PROFILE MATCH (p:Person) WHERE p.name IN ["p1","p4","p3"]
WITH collect(p) AS persons
WITH persons, persons[0] as p1, persons[1] as p2
MATCH path=(p1)-[*..15]-(p2)
WHERE ANY(x IN nodes(path) WHERE x = persons[2])
UNWIND rels(path) AS commonRel
WITH distinct commonRel AS r
RETURN startNode(r) AS from, endNode(r) AS to
I would suggest to grow your graph and try/tune your use cases
Not sure this is possible but will try asking. I am trying to find (Person) nodes groups that share at least 5 (Action) nodes where model is
(p:PERSON)-[:CHAT]->(a:ACTION)
I can do this for showing 2 Persons groups that share +5 Actions
MATCH path =(p1:PERSON)-[r1:CHAT]->(a:ACTION)<-[r2:CHAT]-(p2:PERSON)
WITH p1, p2, count(a) as ActionCount WHERE ActionCount >= 5
RETURN (p1)-[:CHAT]->(:ACTION)<-[:CHAT]-(p2)
However is there a smart way to do this dynamically or using collections where there are more people in a shared group? I am trying to identify efficient teams based on Action metrics, and flagging virtual teams if they share at least 5 actions
many thanks
So I think you can do this by programmatically generating a query. I'm not sure if you can do this programmatically in Cypher. To generate a query easily I would do something like:
MATCH
(a:ACTION),
(a)<-[:CHAT]-(p1:PERSON),
(a)<-[:CHAT]-(p2:PERSON),
(a)<-[:CHAT]-(p3:PERSON),
(a)<-[:CHAT]-(p4:PERSON),
(a)<-[:CHAT]-(p5:PERSON)
WITH p1, p2, p3, p4, p5, count(a) as ActionCount
WHERE ActionCount >= 5
RETURN [p1, p2, p3, p4, p5], ActionCount
Not that you don't need the path and relationship variables if you're not using them later.
I think you said this both ways (five actions per user / five users per action). It should work the same either way:
MATCH (p:PERSON)-[:CHAT]->(a:ACTION)
WITH p, count(a) AS action_count
WHERE action_count >= 5
MATCH (p)-[:CHAT]->(a:ACTION)
RETURN p, collect(a)
I just made up what is being returned there. You should be able to return anything that you like.
Another way to do it:
MATCH (p:PERSON)
WHERE size( (p)-[:CHAT]->(:ACTION) ) >= 5
WITH p
MATCH (p)-[:CHAT]->(a:ACTION)
RETURN p, collect(a)
I've this cypher case where i need to get the strength of a relation to utilize a better recommendation, my case has an A, B, C nodes with relations (A)-[:HAS {weight:n}]-(B), (A)-[RESPONSIBLE {weight:n}]-(C), what i want to get is the relation between (B)--(C) and to calculate weight of each C with A as weight.
I tried this query which is obviously wrong but that what i could do so far
MATCH (c:C {title:"some title"})
MATCH p=(c)<-[:RESPONSIBLE]-(A)-[:HAS]->(B)
RETURN DISTINCT(c.title) AS c, count(c.id) AS weight
ORDER BY weight DESC
can you guys help ?
I guess, you want to sum up the weight of all :HAS relationships?
MATCH (c:C {title:"some title"})
MATCH p=(c)<-[:RESPONSIBLE]-(A)-[r:HAS]->(B)
RETURN DISTINCT(c.title) AS c, sum(r.weight) AS weight
ORDER BY weight DESC
I have 100 nodes, n1, n2, n3 etc which are connected by three different kind of relationships, r1,r2, and r3. Each of this relationships have a parameter called "weight" which is a number between lets say, 5, 10 and 15. I need to develop a ranking based on the number of total paths per node and also another ranking based on the weight. By total paths i mean that if N1-[r1]->n2 and n2-[r1]->n3 and n3-[r3]->n4 then the total number of paths for n1 would be 3. the value of the ranking by weight would be 5+5+15=25.
Ideally the query would return a list of the nodes ranked.
Is there a way to do that in cypher?
thanks
Something like this??
MATCH (n1:Label {id:1})-[r1]->(n2:Label {id:2})-[r2]->(n3:Label {id:3})-[r3]->()
RETURN n1,
SUM(r1.weight+r2.weight+r3.weight) as weight,
count(*) as paths
ORDER BY weight desc, paths desc
Try this, of course with some tweaks for your data model:
MATCH path=(a:Foo)-[:r1|r2|r3*]->(d:Foo)
RETURN length(path) as NumberOfStepsInPath,
reduce(sum=0, y in
extract(x in relationships(path) | x.weight)
| sum + y)
as TotalCost;
So this matches a path from a to d, on any of the relationship types you specify, r1|r2|r3. The length of the path is easy, that's just length(path). Summing the weights is a bit more involved. First, you extract the weight attribute from each relationship in the path. Then you reduce the list of weights down to a single sum.