带有加权分数的 Sql 流行度算法

Posted

技术标签:

【中文标题】带有加权分数的 Sql 流行度算法【英文标题】:Sql Popularity algorithm with weighted score 【发布时间】:2014-10-12 01:05:44 【问题描述】:

我正在实现一个算法,根据他的好恶返回当前的热门帖子。

为此,对于每个帖子,我添加他所有的喜欢 (1) 和不喜欢 (-1) 以获得他的分数,但每个喜欢/不喜欢都是加权的:最新的,最重的。例如,在用户喜欢帖子的那一刻,他的喜欢权重为 1。1 天后,它的权重为 0.95(如果不喜欢,则为 -0.95),2 天后,0.90,等等...... 21 天后达到 0.01。 (PS:这些都是近似值)

我的桌子是这样制作的:

帖子表

id | Title                 | user_id | ...
-------------------------------------------
1  | Random post           | 10      | ...
2  | Another post          | 36      | ...
n  | ...                   | n       | ...

点赞表

id | vote | post_id | user_id | created
----------------------------------------
1  | 1    | 2       | 10      | 2014-08-18 15:34:20
2  | -1   | 1       | 24      | 2014-08-15 18:54:12
3  | 1    | 2       | 54      | 2014-08-17 21:12:48 

这是我目前正在使用的 SQL 查询完成这项工作

SELECT Post.*, Like.*, 
SUM(Like.vote * 
    (1 - IF((TIMESTAMPDIFF(MINUTE, Like.created, NOW()) / 60 / 24) / 21 > 0.99, 0.99, (TIMESTAMPDIFF(MINUTE, Like.created, NOW()) / 60 / 24) / 21))
   ) AS score 
FROM posts Post 
LEFT JOIN likes Like ON (Post.id = Like.post_id) 
GROUP BY Post.id
ORDER BY score DESC

PS:我直接使用TIMESTAMPDIFFMINUTE 而不是DAY,因为我自己计算日期,否则它会返回一个整数,我想要一个浮点值,以便逐渐衰减加班和不是每天一天。所以TIMESTAMPDIFF(MINUTE, Like.created, NOW())/60/24 只是给了我从小数部分创建以来经过的天数。

这是我的问题:

    查看IF(expr1, expr2, expr3) 部分:有必要为like 的权重设置最小值,这样它就不会低于0.01 并变为负数(等等,甚至更老的还有一点权重)。但我计算的是同一件事的 2 倍:expr1 与 expr2 相同。有没有办法避免这种重复的表达方式? 我打算缓存这个查询并每 5 分钟更新一次,因为我认为它在 PostLike 的大表上会非常繁重。缓存真的有必要吗?我的目标是在一个有 50 000 个条目的表上运行此查询,并且对于每 200 个关联的喜欢(这会产生一个 10 000 000 个条目 Like 表)。 我应该在Like 表中为 post_id 创建索引吗?而对于创造?

谢谢!

编辑: 想象一个Post 可以有多个标签,每个标签可以属于多个帖子。如果我想获得给定标签或多个标签的热门帖子,我无法缓存每个查询;因为有大量可能的查询。查询仍然可行吗?

编辑最终解决方案:我终于做了一些测试。我创建了一个包含 30 000 个条目的表 Post 和一个包含 250 000 个条目的 Like。 如果没有索引,查询会非常长(超时 > 1000 万),但在 Post.id (primary)、Like.id(primary) 和 Like.post_id 上的索引需要大约 0.5 秒。

所以我没有缓存数据,也没有每 500 万次使用更新。如果表不断增长,这仍然是可能的解决方案(超过 1 秒是不可接受的)。

【问题讨论】:

#1 - 在 mysql 中,您可以使用 in-line 变量赋值。因此,您将第一次使用它,第二次使用该变量。 你能给我看看如何使用它的示例吗? 非常小的例子只是为了说明语法在this SQL Fiddle*中。 问题#2和#3更适合DBA site。 我知道这很简单。但是如何在聚合函数中使用它?我不能直接把它放在 SUM 中。否则,它只返回第一行(第一个关联的like)的结果(权重)。 【参考方案1】:

2:我打算缓存此查询并每 5 分钟更新一次,因为我认为它在大型 Post 和 Like 表上会非常繁重。缓存真的有必要吗?我的目标是在一个有 50 000 个条目的表上运行这个查询,并且对于每 200 个关联的喜欢(这会产生一个 10 000 000 个条目的 Like 表)。

10000 和 50000 在当前硬件上被认为很小。使用这些表大小,您可能不需要任何缓存,除非查询每秒运行几次。 无论如何,我会在决定使用缓存之前进行性能测试。

3:我应该在 Like 表中为 post_id 创建索引吗?而对于创造?

我将为 (post_id, created, vote) 创建一个索引。这样查询就可以从索引中获取所有信息,根本不需要读取表。

编辑(响应 cmets)

额外的索引会稍微减慢插入/更新速度。最后,您选择的路径将决定您在 CPU/RAM/磁盘 I/O 方面所需的特性。 如果您有足够的 RAM 用于 DB,因此您希望整个 Like 表缓存在 RAM 中,那么您最好只在 post_id 上创建一个索引。

就总负载而言,您需要考虑insertselect 之间的比率以及插入和选择有或没有索引的相对成本。 我的直觉是总负载会随着索引而降低。

关于您的并发问题(同时选择和插入)。发生什么取决于隔离级别。一般的建议是保持插入/更新尽可能短。如果你在insertcommit 之间没有做不必要的事情,你应该没问题。

【讨论】:

好吧,查询的目的是让每个客户在“热门”页面上检索。该网站最多可以连接 2000 个客户端,所以如果你告诉我应该没问题,我相信你。我的另一个想法是在 Post 表中创建流行度字段,并每 500 万次运行一个脚本来更新帖子的流行度。这是一个好方法吗?例如,如果客户端在脚本处于更新过程中时按受欢迎程度对帖子进行排序,是否会出现问题? (PS:不是10000而是10000000(总点赞数,每条推200个赞)) 还有关于索引,Likes表的每一项都代表一个“like”,所以这个表被设计成一个频繁更新的表,索引是不是会拖慢数据库的速度呢?感谢您的回答,如果我有很多问题,我很抱歉,但我真的对 SQL 性能一无所知。感谢您的宝贵时间。 我已尝试在对我的回答进行编辑时解决您的后续问题。

以上是关于带有加权分数的 Sql 流行度算法的主要内容,如果未能解决你的问题,请参考以下文章

3月数据库流行度排行:SQL Server分数暴跌

哪种算法/实现通过用户选择的距离属性来加权相似度?

使用 TF-IDF 加权的空间向量模型实现句子相似度计算

ML之CB:基于自定义电影数据集利用CB基于内容推荐算法(多个指标基于同种相似度加权得分)实现电影Top5推荐案例

推荐算法小结

简单的流行度算法