Reputation: 90
Can someone help a neo4j newbie (this is my 2nd day!) solve the following problem?
I have a database and I have all players (P) that have played a game session (S) and the scores they achieved. There are numerous Ss, let's call them S1, S2, S3 ... etc. There are numerous Ps, P1, P2, P3 ... etc.
Each session has players, e.g.
(P)-[:PLAYED]->(S)
Each session has a variable number of players, from 2 to 10.
What I want to do, is access every SESSION, get every player for that session and then rank them according to score. The players need to be first sorted by score and then ranked, with each player with a higher score than the preceding player having a BEAT relationship. Normally, I would use a FOREACH loop, but I can't figure out how to do the same with Cypher.
For example, S1 has players P1, P3, and P5. If P3 got 100, P1 got 70, and P5 30, I would like to create the following relationships:
(P3)-[:BEAT]->(P1)-[:BEAT]->(P5)
I need to do this for every session. What would be the best way to approach this problem?
Regards,
Upvotes: 1
Views: 3014
Reputation: 90
If anyone finds this post useful, I would like to add, to process a large result set (to avoid running out of memory), try this:
// Find all players who have played in a session
MATCH (p:Player)-[r:PLAYED]->(s:Session)
// for each Session, order the players by their score for that session
WITH s, p ORDER BY r.score DESC
//Paginate/batch process results to avoid exhausting memory
SKIP 500000*n
LIMIT 500000
// for each session, group the players (now ordered by their scores)
WITH s, COLLECT(p) AS players
CALL apoc.nodes.link(players, 'BEAT')
// can't end a query with CALL, so just do a dummy return of some kind
RETURN DISTINCT true
i.e. the lines
SKIP 500000*n
LIMIT 500000
Were added. Set n = 0, and keep increasing until no more records are updated.
Thank you to everyone who contributed to this thread.
Upvotes: 1
Reputation: 30397
Just to add a shortcut approach to save a few lines of Cypher, you can install APOC Procedures and use apoc.nodes.link()
to quickly create your relationship chain.
Using William Lyon's query as a base:
// Find all players who have played in a session
MATCH (p:Player)-[r:PLAYED]->(s:Session)
// for each Session, order the players by their score for that session
WITH s, p ORDER BY r.score DESC
// for each session, group the players (now ordered by their scores)
WITH s, COLLECT(p) AS players
CALL apoc.nodes.link(players, 'BEAT')
// can't end a query with CALL, so just do a dummy return of some kind
RETURN DISTINCT true
Upvotes: 2
Reputation: 8546
Assuming that score
is stored on the :PLAYED
relationship, this should work:
// Find all players who have played in a session
MATCH (p:Player)-[r:PLAYED]->(s:Session)
// for each Session, order the players by their score for that session
WITH s, p ORDER BY r.score DESC
// for each session, group the players (now ordered by their scores)
WITH s, COLLECT(p) AS players
// iterate over the sequence 0..number_of_players_in_this_session-2
UNWIND range(0,size(players)-2) AS i
// grab pairs of players, starting from the highest scoring
WITH players[i] AS l1, players[i+1] AS l2
// create :BEAT relationship
CREATE (l1)-[:BEAT]->(l2)
There is a simple Neo4j console example here
Of course there is a data modeling problem here, you are not associating the :BEAT
relationships with a particular session.
Upvotes: 5