这个问题很长,所以我会在提出我的方法之前先谈谈不同的话题,准备好回答很长:
- 数据规范化
- 具有相同值轴的二维表
数据规范化
存储总数据量很有用,但按其排序则没有用,因为顺序不能确定组合是否好与另一个,它确定大多数时候赢/输的组合与相反的组合,但总数玩的游戏数量也很重要。
在排序结果时,您希望按前两个的胜率、平局率、松率进行排序,因为第三个是线性组合。
具有相同值轴的二维表
在两个维度表示相同数据的二维表(在本例中为一组 5 个冠军)的问题是,您要么制作一个三角形表,要么将数据加倍,因为您必须存储组合 A 与组合 B 和组合 B 与组合 A,组合 X 是一组特定的 5 个冠军。
这里有两种方法,使用三角表或手动加倍数据:
1。三角桌:
您创建一个表格,其中右上半部分为空或左下角为空。然后,您在应用程序中处理哪个哈希是 A,哪个是 B,并且您可能需要交换它们的顺序,因为没有重复的数据。例如,您可以考虑始终 A
2。手动加倍数据:
通过使用反射值(A、B、wins、draws、loose & B、A、looses、draws、wins)进行两次插入,您将复制数据。这使您可以以任何顺序查询,代价是使用两倍的空间并需要两次插入。
优点和缺点:
一种方法的优点是另一种方法的缺点。
三角桌的优点
数据翻倍的优点
我可能会使用三角表方法,因为应用程序复杂性的增加并不大,但可扩展性确实很重要。
提议的架构
使用您想要的任何键空间,我从 stackoverflow 中选择。根据需要修改复制策略或因素。
CREATE KEYSPACE so WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 3};
冠军名单
冠军表将包含有关不同冠军的信息,目前它只包含名称,但您将来可以存储其他内容。
CREATE TABLE so.champions (
c boolean,
id smallint,
name text,
PRIMARY KEY(c, id)
) WITH comment='Champion names';
boolean 用作分区键,因为我们希望将所有冠军存储在单个分区中以提高查询性能,并且我们将始终使用 c=True 的记录数量很少(约 100 条)。为id 选择了smallint,因为 2^7 = 128 是为了接近实际的冠军数量,并在不使用负数的情况下为未来的冠军留出空间。
在查询冠军时,您可以通过以下方式获得所有冠军:
SELECT id, name FROM so.champions WHERE c=True;
或通过以下方式请求特定的:
SELECT name FROM so.champions WHERE c=True and id=XX;
历史匹配结果表
此表将存储匹配结果而不进行汇总:
CREATE TABLE so.matches (
dt date,
ts time,
id XXXXXXXX,
teams list<frozen<set<smallint>>>,
winA boolean,
winB boolean,
PRIMARY KEY(dt, ts, id)
) WITH comment='Match results';
对于历史数据表的分区,正如您提到的每日精度,date 似乎是一个不错的分区键。 time 列用作排序原因的第一个聚类键并完成时间戳,无论这些时间戳属于结束瞬间还是结束瞬间,选择一个并坚持下去。集群键中需要一个额外的标识符,因为 2 个游戏可能在同一时刻结束(时间具有纳秒精度,这基本上意味着丢失重叠的数据将非常微不足道,但您的数据源可能没有这个精度,因此使这最后一个键列是必要的)。您可以在此列中使用您想要的任何类型,可能您已经拥有一些可以在此处使用的数据的标识符之王。您还可以选择随机数、由应用程序管理的增量 int,甚至是第一个玩家的姓名,因为您可以确保同一玩家不会在同一秒开始/完成两场比赛。
teams 列是最重要的一列:它存储了在游戏中玩过的英雄的 ID。使用了两个元素的序列,每个团队一个。内部(冻结)集用于每个团队中的冠军 ID,例如:{1,3,5,7,9}。我尝试了几个不同的选项:set< frozen<set<smallint>> >、tuple< set<smallint>> , set<smallint> > 和 list< frozen<set<smallint>> >。第一个选项不存储球队的顺序,所以我们无法知道谁赢了比赛。第二个不接受在此列上使用索引并通过CONTAINS 进行部分搜索,所以我选择了第三个,它保持顺序并允许部分搜索。
另外两个值是两个布尔值,代表谁赢得了比赛。你可以有额外的列,比如draw boolean,但如果你想存储游戏的长度,这不是必需的或duration time(我没有使用Cassandra的duration类型,因为它只值得这需要几个月或至少几天),end timestamp/start timestamp 如果您想将不使用的那个存储在分区和集群键等中。
部分搜索
在团队上创建索引可能很有用,以便您可以在此列上进行查询:
CREATE INDEX matchesByTeams ON so.matches( teams );
然后我们可以执行以下SELECT语句:
SELECT * FROM so.matches WHERE teams CONTAINS {1,3,5,7,9};
SELECT * FROM so.matches WHERE teams CONTAINS {1,3,5,7,9} AND dt=toDate(now());
第一个将选择任何球队选择该组合的比赛,第二个将进一步过滤到今天的比赛。
统计缓存表
通过这两个表,您可以保存所有信息,然后请求您需要的数据来计算所涉及的统计数据。一旦你计算了一些数据,你可以将这些信息作为“缓存”存储回 Cassandra 中,这样当用户请求显示一些统计信息时,你首先检查它们是否已经计算过,如果它们没有计算过.该表需要为用户可以输入的每个参数提供一列,例如:冠军组成、开始日期、结束日期、敌方队伍;以及统计数据本身的附加列。
CREATE TABLE so.stats (
team frozen<set<smallint>>,
s_ts timestamp,
e_ts timestamp,
enemy frozen<set<smallint>>,
win_ratio float,
loose_ratio float,
wins int,
draws int,
looses int,
PRIMARY KEY(team, s_ts, e_ts, enemy)
) WITH comment="Already calculated queries";
按胜负比排序:
要按比例而不是敌方队伍获得结果顺序,您可以使用物化视图。
CREATE MATERIALIZED VIEW so.statsByWinRatio AS
SELECT * FROM so.stats
WHERE team IS NOT NULL AND s_ts IS NOT NULL AND e_ts IS NOT NULL AND win_ratio IS NOT NULL AND enemy IS NOT NULL
PRIMARY KEY(team, s_ts, e_ts, win_ratio, enemy)
WITH comment='Allow ordering by win ratio';
注意:
当我回答时,我意识到在数据库中引入“补丁”的概念,这样用户就不能确定日期,但补丁可能是一个更好的解决方案。如果您有兴趣发表评论,我将编辑答案以包含补丁概念。这意味着稍微修改so.historic 和so.stats 表,但改动很小。