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

cn_boris · 发布于 2014年03月02日 · 最后由 xwf286 回复于 2014年06月15日 · 5759 次阅读
7119
本帖已被设为精华帖!

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

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

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

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

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


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

共收到 38 条回复
22014d

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

6291

超过 1 行切JJ

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

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

2973

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

1

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

6291

#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 万条记录,应该可以支撑到楼主融资请大牛吧

1

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

6291

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

96

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

6291

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

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

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

6291

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

2973

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

96

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

1

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

2973

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

6291

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

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

#15楼 @small_fish__

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

我是 环保主义者 ,别学我

1

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

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

162

我们的应用有类似需求,不同的是用牛顿冷却算法,比你这个稍微复杂一点点,我们加了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计算既可。

96

换个算法

6291

#21楼 @Rei

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

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

1

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

6291

#25楼 @Rei

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

基于用户投票的排名算法(二):Reddit http://www.ruanyifeng.com/blog/2012/03/ranking_algorithm_reddit.html

6291

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

7119

最后经过权衡准备参考 @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

6291

#31楼 @CN_Boris :plus1: 麻烦发到我们北京公司吧 http://www.ihaveu.com/about/contact

7119

#32楼 @swordray twitter私信你咯😄

11955

不错,又学习到了...

96

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

11587

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

37楼 已删除
10912

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

3454

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

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