跑步比赛
领域
描述
我们喜欢跑步,并互相挑战。因此,我们设立了一个小型的比赛。选择了一些跑步组织。为每个组织分配一个主要比赛,而其他比赛则是副比赛。
对于我们小型比赛中的参赛者,主要比赛的第一名得 50 分,第二名得 45 分,第三名得 40 分,然后是 39 分、38 分…… 依次递减。
所有参加副比赛的参赛者获得相同的积分。积分数与他们在主要比赛中排名最后一名之后的积分相同。
例如,参加主要比赛的 5 名参赛者将获得 50、45、40、39 和 38 分。然后,参加副比赛的每位参赛者将获得 37 分。如果主要比赛中没有参赛者,那么副比赛中的每个人都将获得 50 分。
有时,一些人会参加比赛,但不是比赛的一部分。这种情况适用于那些有兴趣明年加入俱乐部的人,或者那些会员费支付过期的。我们想记住他们的比赛成绩,以便在每周的俱乐部公报中展示,或者在他们成为俱乐部成员后更改他们的状态并重新计算积分。
相反,有时俱乐部成员选择参加比赛,但不参加比赛,因此放弃高分(50 - 45),让其他人获得这些积分。可以通过在比赛中不穿俱乐部制服来做到这一点。
最好有一个网站,可以在比赛结束后尽快显示比赛结果(与我们的比赛相关)和总体排名。计划使用 Graphgist 建立和验证模型,然后使用 Neo4J 和 Flask 建立网站。使用 Flask 和 Neo4j 构建 Web 应用程序培训对此进行了令人信服的介绍。
模型
每个组织都表示为一个节点,连接到位置和日期。然后,每个组织都有一个主要比赛(例如,10 英里)和副比赛(10 公里、5 公里、21 公里……)。
该模型有一个比赛节点(“2015 年夏季比赛”、“2014 年夏季比赛”……)。当俱乐部成员的年龄超过 12 岁时,他们会自动参加比赛。
人员由人员节点表示。在大多数情况下,人员节点将连接到“姓名”节点。对于我们可能不知道确切姓名的 newcomers,将使用人员节点的姓名属性。姓名节点必须正确,而人员节点的姓名属性是目前已知的最准确的表示。
对于参加我们比赛并在比赛中完赛的每位参赛者和每位同情者,都将创建一个完赛节点。最好将完赛节点设置为比赛中的位置和结束时间,但真正需要的信息只是相对于其他参赛者的相对位置。这足以计算比赛中获得的积分。这种方法允许在比赛结束后立即输入数据,并在享受啤酒并等待官方结果时讨论新的排名。
这个 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 after 7”和“72 after 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 浏览器对(完赛者)节点上的笛卡尔积并不满意。需要重新配置查询以添加参与者的匹配行。这将链接比赛和完赛者
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 (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)
特殊情况
单个完赛者
需要验证一些特殊情况:只有一名参赛者的主要比赛和没有参赛者的主要比赛。下一个查询将为德伦的主要比赛添加一名参赛者
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 表更容易。
此页面是否有用?