跑步比赛
领域
描述
我们热爱跑步并互相挑战。因此,我们组织了一个小型的比赛。我们选择了一些跑步组织,对于每个组织,其中一场比赛被指定为主赛,而其他比赛则为副赛。
对于我们小型比赛的参与者,主赛的第一名完赛者获得50分,第二名45分,第三名40分,随后的完赛者依次获得39、38分……
所有副赛的参与者获得相同的分数。分数数量如同他们在主赛最后一名完赛者之后。
例如,主赛的5名参与者将分别获得50、45、40、39和38分。此时,所有副赛的参与者都将获得37分。如果主赛没有参与者,那么所有副赛的参与者都将获得50分。
有时,一些人会来参加比赛,但他们并非本次比赛的参赛者。例如,对明年加入俱乐部感兴趣的人,或延迟缴纳会费的人。我们希望记住他们的比赛成绩,以便在每周的俱乐部公报中展示,或在他们成为俱乐部成员后立即更改其状态为参与者并重新计算分数。
反过来,有时俱乐部成员选择跑步但不安徽本次比赛,从而将高分(50-45)留给其他人。这通常通过比赛时不穿俱乐部T恤来实现。
最好能在比赛结束后尽快有一个网站来展示比赛结果(与我们的比赛相关)和总排名。计划是使用Graphgist构建和验证模型,然后使用Neo4J和Flask构建网站。使用Flask和Neo4j构建Web应用的培训为此提供了一个很有说服力的入门。
模型
每个组织表示为一个节点,链接到地点和日期。然后每个组织有一个主赛(例如10英里)和副赛(10公里,5公里,21公里,……)
该模型有一个比赛节点(如'2015夏季赛','2014夏季赛',……)。年龄大于12岁的俱乐部成员会自动参加比赛。
人物用人物节点表示。大多数情况下,人物节点会连接到一个'姓名'节点。对于可能不知道确切姓名的新人,将使用人物节点的姓名属性。'姓名'节点需要准确,而人物节点的姓名属性是在当时已知的最佳表示。
对于我们比赛中完成比赛的每位参与者以及每位完成比赛的支持者,都会创建一个完赛者节点。最好完赛者节点包含在比赛中的名次和结束时间,但唯一真正需要的信息是相对于其他参与者的相对位置。这足以计算在比赛中获得的分数。这种方法允许在比赛后立即输入数据,一边享用啤酒一边等待官方结果,一边讨论新的排名。
本GraphGist将重点介绍配置一些组织及其比赛,将比赛添加到类别(主赛 - 副赛),定义一些人物并将其添加为比赛的完赛者。
本次练习的主要目的是提取按到达顺序排列的完赛者列表。然后,Flask (Python) 应用程序可以轻松计算每位参与者在比赛中的得分并得出总排名。
使用数据
获取完赛者
现在让我们使用模式来获取特定组织的所有完赛者列表。使用模式可以提高查询的可读性。
MATCH races_in_Lier = (organization:Organization {name: 'Lier - Halve Marathon'})-[:has_race]->(race),
race_types = (race)-[:race_category]->(type:RaceType),
finishers = (race)<-[:runs_in]-(finisher)<-[:participates]-(person)
RETURN organization, race, type, finisher, person
现在我们需要获取足够的信息来计算完赛者的得分。因此,我们首先需要获取主赛中按到达顺序排列的完赛者。
第一次尝试提供了太多信息
MATCH races_in_Lier = (organization:Organization {name: 'Lier - Halve Marathon'})-[:has_race]->(race),
main_race = (race)-[:race_category]->(g:RaceType {label: 'Main'}),
finishers = (finisher)<-[:after*]-()
WHERE (race)<-[:runs_in]-(finisher)
RETURN finishers
图是正确的,但表格提供了部分路径'36在7之后'和'72在36之后'的行。这不是我们想要的。我们只希望获取完整路径用于Web应用程序的处理。因此,查询通过收集所有路径进行了扩展,获取路径的最大长度,然后只过滤最长路径。在这个模型中,只有一条最长路径,就是所需的那条。
MATCH races_in_Lier = (organization:Organization {name: 'Lier - Halve Marathon'})-[:has_race]->(race),
main_race = (race)-[:race_category]->(g:RaceType {label: 'Main'}),
finishers = (finisher)<-[:after*]-()
WHERE (race)<-[:runs_in]-(finisher)
WITH COLLECT(finishers) AS results, MAX(length(finishers)) AS maxLength
WITH FILTER(result IN results WHERE length(result) = maxLength) AS result_coll
UNWIND result_coll AS result
RETURN nodes(result)
好的,这提供了一行所需的数据,Web应用程序可以处理。然而,Neo4j浏览器对(finisher)节点上的笛卡尔积不太满意。需要重新配置查询,为参与者添加一个MATCH行。这将连接比赛和完赛者。
MATCH races_in_Lier = (organization:Organization {name: 'Lier - Halve Marathon'})-[:has_race]->(race),
main_race = (race)-[:race_category]->(g:RaceType {label: 'Main'}),
participants = (race)<-[:runs_in]-(finisher),
finishers = (finisher)<-[:after*]-()
WITH COLLECT(finishers) AS results, MAX(length(finishers)) AS maxLength
WITH FILTER(result IN results WHERE length(result) = maxLength) AS result_coll
UNWIND result_coll AS result
RETURN nodes(result)
为下一场比赛添加数据
现在将为下一个组织的主赛(10公里)添加参与者。使用MERGE来创建或匹配每个参与者。
MERGE (johan:Person {name: 'Johan'})
MERGE (luc:Person {name: 'Luc'})
MERGE (gert:Person {name: 'Gert'})
MERGE (stefan:Person {name: 'Stefan'})
WITH johan, luc, gert, stefan
MATCH (organization:Organization {name: 'Berchem - Bollekesloop'})-[:has_race]->(race),
(race)-[:race_category]->(g:RaceType {label: 'Main'})
CREATE (race)<-[:runs_in]-(f_johan:Finisher {place: 14})<-[:participates]-(johan),
(race)<-[:runs_in]-(f_luc:Finisher {place: 15})<-[:participates]-(luc),
(race)<-[:runs_in]-(f_gert:Finisher {place: 62})<-[:participates]-(gert),
(race)<-[:runs_in]-(f_stefan:Finisher {place: 114})<-[:participates]-(stefan),
(f_stefan)-[:after]->(f_gert)-[:after]->(f_luc)-[:after]->(f_johan)
RETURN johan,luc,gert,stefan,f_johan,f_luc,f_gert,f_stefan,race,organization
我想验证上面获取比赛所有完赛者的查询是否可以重用。唯一需要更改的是组织名称。
MATCH races_in_Berchem = (organization:Organization {name: 'Berchem - Bollekesloop'})-[:has_race]->(race),
main_race = (race)-[:race_category]->(g:RaceType {label: 'Main'}),
participants = (race)<-[:runs_in]-(finisher),
finishers = (finisher)<-[:after*]-()
WITH COLLECT(finishers) AS results, MAX(length(finishers)) AS maxLength
WITH FILTER(result IN results WHERE length(result) = maxLength) AS result_coll
UNWIND result_coll AS result
RETURN nodes(result)
特殊情况
单人完赛者
需要验证一些特殊情况:只有一名参与者的主赛和没有参与者的主赛。下一个查询将为Deurne的一场主赛添加一名参与者。
MERGE (luc:Person {name: 'Luc'})
WITH luc
MATCH (organization:Organization {name: 'Deurne - Rivierenhofloop'})-[:has_race]->(race),
(race)-[:race_category]->(racetype:RaceType {label: 'Main'})
CREATE (race)<-[:runs_in]-(f_luc:Finisher {place: 18})<-[:participates]-(luc)
RETURN luc, f_luc, race, racetype, organization
所以Luc在这场比赛中获得了50分。但他会显示在查询结果中吗?
MATCH races_in_Berchem = (organization:Organization {name: 'Deurne - Rivierenhofloop'})-[:has_race]->(race),
main_race = (race)-[:race_category]->(g:RaceType {label: 'Main'}),
participants = (race)<-[:runs_in]-(finisher),
finishers = (finisher)<-[:after*]-()
WITH COLLECT(finishers) AS results, MAX(length(finishers)) AS maxLength
WITH FILTER(result IN results WHERE length(result) = maxLength) AS result_coll
UNWIND result_coll AS result
RETURN nodes(result)
不在此查询中。显然,关系[:after*]默认要求至少有一个链接。将关系修改为[:after*0..]更好。
MATCH races_in_Berchem = (organization:Organization {name: 'Deurne - Rivierenhofloop'})-[:has_race]->(race),
main_race = (race)-[:race_category]->(g:RaceType {label: 'Main'}),
participants = (race)<-[:runs_in]-(finisher),
finishers = (finisher)<-[:after*0..]-()
WITH COLLECT(finishers) AS results, MAX(length(finishers)) AS maxLength
WITH FILTER(result IN results WHERE length(result) = maxLength) AS result_coll
UNWIND result_coll AS result
RETURN nodes(result)
好的,这是预期的结果。
无完赛者
如果没有完赛者,我希望得到一个空的结果集。此检查是为了确定是否不会出现意外错误。因此,将添加另一场比赛。请注意,Neo4j浏览器会警告断开模式之间的笛卡尔积,但在这种情况下,这是我们想要的。
MATCH (main: RaceType {label: 'Main'}),
(side: RaceType {label: 'Side'})
CREATE (organization:Organization {name: 'Mechelen - RAM'}),
(mainrace:Race {label: '10k'}),
(siderace:Race {label: '5k'}),
(organization)-[:has_race]->(mainrace),
(organization)-[:has_race]->(siderace),
(mainrace)-[:race_category]->(main),
(siderace)-[:race_category]->(side)
RETURN organization, mainrace, siderace, main, side
现在检查获取完赛者列表的查询是否返回空数据集
MATCH races_in_Mechelen = (organization:Organization {name: 'Mechelen - RAM'})-[:has_race]->(race),
main_race = (race)-[:race_category]->(g:RaceType {label: 'Main'}),
participants = (race)<-[:runs_in]-(finisher),
finishers = (finisher)<-[:after*0..]-()
WITH COLLECT(finishers) AS results, MAX(length(finishers)) AS maxLength
WITH FILTER(result IN results WHERE length(result) = maxLength) AS result_coll
UNWIND result_coll AS result
RETURN nodes(result)
好的,空数据集是预期的结果。
这是练习结束时的数据库
结论
到达顺序由“after”关系确定。每场比赛结束后,这是可用的信息片段。然而,这足以计算新的总排名。稍后,当更多信息(例如每位完赛者在比赛中的名次和时间)可用时,可以将其添加到数据库中。此数据模型是开始使用Flask构建Web应用程序的概念证明。
Web应用程序计划进行一些扩展
-
添加女子/男子类别。
-
进一步扩展越野跑类别
-
16岁以下
-
23岁以下
-
成年组
-
大师组
-
大师组 +50
-
-
添加其他田径项目……
本GraphGist文档也应扩展,以便
-
为完赛者添加姓名。这不是Web应用程序必需的,但作为演示会很好。
-
任何其他查询改进。
每场比赛的排名和总排名将存储在SQL (sqlite) 表中。目前还不清楚是否可以使用图数据库来表示。我也不确定对于这种特定用途,图数据库是否会比SQL表更容易。
此页面有帮助吗?