新手问题 请教如何让 ActiveRecord 的查询按照某个虚拟字段排序

cn_boris · 2014年03月02日 · 最后由 xwf286 回复于 2014年06月15日 · 10212 次阅读
本帖已被管理员设置为精华贴

具体的业务场景是:需要使用类似 hacker news 的投票算法进行排序。

比如现在有个 aticle 模型,模型有个字段 P 代表喜欢某篇文章的人数。 T 表示距离发帖已经过去的时间(单位为小时)

我想按照 score 的值对列表进行排序,请问该如何操作?

我在网上找了好几天的资料,目前还没头绪。

刚转型的新手,接触 Rails 没多久。跪求大侠指点一二,感激不尽。 如果大侠愿意的话,我可以用淘点点请您喝顿下午茶。


已有解决方案,请见 #31 楼

等数据查出来后,map 一下就好了么 这个是最简单的办法。 不符合的话,看一下源代码,扩展一一个方法,应该不难。

超过 1 行切 JJ

Article.all.sort_by { |article| (article.p - 1) / ((Time.now - article.created_at) / 1.hour + 2) ** article.g }

给 aticle 加个 score 字段,然后开一个 worker 去给每一个 aticle 跑出这个值,存进数据库。 然后前台直接 order 这个字段。 这个方法会因为你这个 worker 的周期导致一定的延迟,不是很实时。 如果想比较快,比较实时的的得到结果。 据我所知淘宝的热门商品是用一个 hadoop 集群在后台提供支持,不过思路和上面的的方法其实差不多,都是异步的。

#2 楼 @swordray 这种写法无法解决数据大于分页条数的问题。毕竟你不可能一次 instance 所有数据,然后再拉出执行 sort_by..

2 楼方案内存会爆,3 楼方案可行。避免开 worker 增加复杂度并且不实时,可以用 reddit 的算法。

#5 楼 @small_fish__ #6 楼 @Rei

Article.connection.select_all('SELECT id, p, g, created_at FROM articles').to_a.sort_by { |article| (article['p'] - 1) / ((Time.now - article['created_at']) / 1.hour + 2) ** article['g'] }.each_slice(per_page).each_with_index.map { |articles, index| cache("page_#{index}") { articles['id'] } }

增加分页和缓存也不能超过 1 行代码,开发成本 10 分钟,理论估算 8 GB 内存可以处理 1000 万条记录,应该可以支撑到楼主融资请大牛吧

#7 楼 @swordray 不用过早优化,也不能用明显有问题的方案啊。

#8 楼 @Rei 开发环境 8 GB 内存可以处理 1000 万条记录,生产环境 64 GB 内存可以处理 1 亿条记录,不算大问题吧。难的是写 1 亿篇文章的人。

#9 楼 @swordray 我 YY 一下,并发 10 个请求会不会内存消耗也跟着上去了..然后你 ruby 的内存消耗是一部分,mysql 的内存消耗也是另一部分。然后如果硬盘 IO 没跟上,卡住了也不妥啊...

#10 楼 @ruohanc 我没说这个是放到 controller 里,后台定时跑也是 1 行代码

ruby -e "require 'config.ru'; Article.connection..."
Acticle.select("((p-1) /  POWER((TIMESTAMPDIFF(HOUR,created_at,'2014-03-02 22:32')+2), g)) as score").order("score desc")

#7 楼 @swordray 个人感觉能通过数据库解决的问题,最好不要 load 出来,再通过 ruby 来执行。。。除非数据库解决写法很难懂,很难维护。其实解决办法很多时候与业务相关的拉。。。谢谢分享。。。

#13 楼 @small_fish__ 标准 SQL 的函数是很少,难以实现复杂的计算,比如楼上的方法 Sqlite 就不一定支持。Rails 的哲学是用 ActiveModel 接管数据库,数据库只是底层存储,程序逻辑由 Ruby 完成。Thinking in Rails。

#14 楼 @swordray 那我还是比较喜欢添加字段来处理类似需求,一个 delay job 来自动 trigger 那些 change...

#15 楼 @small_fish__ 动态的数据放到 redis 之类的地方怎么样...放 sql 里觉得有点重.?

#7 楼 @swordray 删文章怎么办。

#16 楼 @ruohanc 如果变化频率块,只是显示 top xxx 那么打 redis,无所谓。但是如果是 list 显示所有,个人感觉还是存数据库好点。你觉得呢??

#17 楼 @Rei 前端查询的时候检查一下缓存的 id

Article.where(id: [1, 2, 3, 99999]).size # => 3

#15 楼 @small_fish__

  • 增加了数据库字段
  • 维护 delayed_job 服务
  • 超过 1 行代码

我是 环保主义者 ,别学我

#19 楼 @swordray 所以 cache("page_#{index}") 是个会产生数据不一致的方案,当然删数据情况不多,可能一页有 8 条一页有 10 条用户也不会察觉,只要不怕被用户认为网站在糊弄自己。

#20 楼 @swordray 你不是为了解决问题而编程。

我们的应用有类似需求,不同的是用牛顿冷却算法,比你这个稍微复杂一点点,我们加了 2 个字段 initial_score 和 current_score 在内容创建地时候算一个原始分(根据用户等级,内容质量等),然后用 crontab 每天执行一次 sql: update xxx set current_score = initial_score * exp(timestampdiff(day, created_at, now()) * -0.12) 需要用到排序的地方就按 current_score 来排序就可以了。

hacker news 的算法因为不需要保留原始分,你只需要加一个字段,用 crontab 每小时执行一次 sql 计算既可。

换个算法

#21 楼 @Rei

缓存当然会跟实时数据不一致,以上的所有方法都会。清缓存的策略也有很多种,看需求了。

要看问题是什么?如果楼主的问题是写出高效代码,我那行代码不好。但如果楼主的问题是如何快速上线运营融资上市,我觉得我是在解决问题。

#24 楼 @swordray 3 楼方案没这个问题。

#25 楼 @Rei

这个方法会因为你这个worker的周期导致一定的延迟,不是很实时。

#28 楼 @Rei 👍 不过我个人没时间看了,7 * 12 小时干活

最后经过权衡准备参考 @hooopo 的建议改用 reddit 的算法。同时,也非常感谢@swordray @bydmm @kaka @Rei @quakewang 等大侠的回复,颇有收益。如果几位需要,欢迎在 Twitter 私信我收件地址和手机号,非常乐意请几位喝下午茶。

另,如果有朋友仍期望考虑在 rails 中实现 hacker news 算法,可参考: http://blog.chh.tw/posts/hacker-news-de-tui-wen-xi-tong-yan-suan-fa-yong-ruby-shi-zuo/ https://gist.github.com/ChiChou/8862382

#32 楼 @swordray twitter 私信你咯😄

不错,又学习到了...

看了各位的发言,很受启发。

Mysql 本身就有这中功能,比如一个表有 2 个 int 型属性:a,b 下面 sql:select a, b ,a + b from mytable order by a + b desc

37 楼 已删除

使用 sort_by 或 sort 可以,以前做过。

楼主的图片没有了,我帮把图片补回来

cn_boris 关闭了讨论。 01月16日 12:08
需要 登录 后方可回复, 如果你还没有账号请 注册新账号