Neo4J (Cypher) 按节点对多个关系进行分组

Neo4J (Cypher) Group the multiple relationships by node

我正在开发一个小型推荐系统,为下一场比赛推荐玩家。推荐顺序是先关注的玩家,再是之前玩过的玩家,按游戏次数排序。

结果会是Player, is_followed (true|false), GameIds

我有 PlayerGame 节点,它们的关系为 Game -[:HAS_PLAYER]-> Player

Player 个节点直接相关为 Player -[:FOLLOWS]-> Player

我的测试数据库:

CREATE (superman:Player {playerID: 'superman', name:'Superman', sanitizedName: 'superman'})
CREATE (batman:Player {playerID: 'batman', name:'Batman', sanitizedName: 'batman'})
CREATE (catwoman:Player {playerID: 'catwoman', name:'Catwoman', sanitizedName: 'catwoman'})
CREATE (lois:Player {playerID: 'lois', name:'Lois Lane', sanitizedName: 'lois lane'})
CREATE (wonderwoman:Player {playerID: 'wonderwoman', name:'Wonder Woman', sanitizedName: 'wonder woman'})
CREATE (aquaman:Player {playerID: 'aquaman', name:'Aquaman', sanitizedName: 'aquaman'})
CREATE (flash:Player {playerID: 'flash', name:'Flash', sanitizedName: 'flash'})
CREATE (cyborg:Player {playerID: 'cyborg', name:'Cyborg', sanitizedName: 'cyborg'})
CREATE (joker:Player {playerID: 'joker', name:'Joker', sanitizedName: 'joker'})
CREATE (lex:Player {playerID: 'lex', name:'Lex Luthor', sanitizedName: 'lex luthor'})
CREATE (green:Player {playerID: 'green', name:'Green Lantern', sanitizedName: 'green lantern'})

CREATE (g1:Game {gameId:"game-1"})
CREATE (g1)-[:HAS_PLAYERS {teamId:"0"}]->(superman)
CREATE (g1)-[:HAS_PLAYERS {teamId:"0"}]->(aquaman)
CREATE (g1)-[:HAS_PLAYERS {teamId:"1"}]->(batman)
CREATE (g1)-[:HAS_PLAYERS {teamId:"1"}]->(flash)

CREATE (g2:Game {gameId:"game-2"})
CREATE (g2)-[:HAS_PLAYERS {teamId:"0"}]->(superman)
CREATE (g2)-[:HAS_PLAYERS {teamId:"0"}]->(batman)
CREATE (g2)-[:HAS_PLAYERS {teamId:"1"}]->(joker)
CREATE (g2)-[:HAS_PLAYERS {teamId:"1"}]->(lex)

CREATE (g3:Game {gameId:"game-3"})
CREATE (g3)-[:HAS_PLAYERS {teamId:"0"}]->(flash)
CREATE (g3)-[:HAS_PLAYERS {teamId:"0"}]->(lois)
CREATE (g3)-[:HAS_PLAYERS {teamId:"1"}]->(wonderwoman)
CREATE (g3)-[:HAS_PLAYERS {teamId:"1"}]->(aquaman)

CREATE (g4:Game {gameId:"game-4"})
CREATE (g4)-[:HAS_PLAYERS {teamId:"0"}]->(joker)
CREATE (g4)-[:HAS_PLAYERS {teamId:"0"}]->(cyborg)
CREATE (g4)-[:HAS_PLAYERS {teamId:"1"}]->(wonderwoman)
CREATE (g4)-[:HAS_PLAYERS {teamId:"1"}]->(green)

CREATE (g5:Game {gameId:"game-5"})
CREATE (g5)-[:HAS_PLAYERS {teamId:"0"}]->(catwoman)
CREATE (g5)-[:HAS_PLAYERS {teamId:"1"}]->(green)

CREATE (batman)-[:FOLLOWS]->(superman)
CREATE (batman)-[:FOLLOWS]->(catwoman)

CREATE (lex)-[:FOLLOWS]->(joker)
CREATE (joker)-[:FOLLOWS]->(lex)
;

根据这些数据,我想向蝙蝠侠推荐下一场比赛的球员,结果必须是:

superman true  [game-1, game-2]
catwoman true  []
aquaman  false [game-1]
flash    false [game-1]
joker    false [game-2]
lex      false [game-2]

更新: 我有下一个查询,但缺少以下玩家:

MATCH (u:Player{playerID:"batman"})
OPTIONAL MATCH (u)<-[:HAS_PLAYERS]-(g:Game)-[:HAS_PLAYERS]->(p:Player) 
RETURN p, EXISTS((u)-[:FOLLOWS]->(p)) AS is_followed, COUNT(g) AS num_games, collect(g) AS games
ORDER BY num_games DESC

谢谢!

更新 2

这种方法可能更 'readable' 但性能比公认的答案差得多。

MATCH (u:Player{playerID:"batman"})
CALL {
    WITH u
    MATCH (u)<-[:HAS_PLAYERS]-(g:Game)-[:HAS_PLAYERS]->(p:Player) 
    RETURN p

    UNION

    WITH u
    MATCH (u)-[:FOLLOWS]->(p:Player)
    RETURN p
}
WITH p,
    EXISTS((u)-[:FOLLOWS]->(p)) AS is_followed,
    [(u)<-[:HAS_PLAYERS]-(g:Game)-[:HAS_PLAYERS]->(p) | g.gameId] AS games
RETURN p.name, is_followed, size(games) AS num_games, games
ORDER BY num_games DESC

你没有得到猫女,因为你只通过 :Game 节点匹配与蝙蝠侠相关的 :Player,你还需要匹配 (u:Player {playerID:"batman"})-[:FOLLOWS]->( p:玩家)

MATCH (u:Player{playerID:"batman"})
OPTIONAL MATCH (u)<-[:HAS_PLAYERS]-(g:Game)-[:HAS_PLAYERS]->(p:Player) 
RETURN p.playerID, EXISTS((u)-[:FOLLOWS]->(p)) AS is_followed, COUNT(g) AS num_games, collect(g.gameId) AS games
ORDER BY num_games DESC
union
match (u:Player{playerID:"batman"})-[:FOLLOWS]->(p:Player)
where not exists( (u)<-[:HAS_PLAYERS]-(:Game)-[:HAS_PLAYERS]->(p:Player) )
RETURN p.playerID, true AS is_followed, 0 AS num_games, [] AS games

(想不出没有联合的方法,但它可能是可能的)