【问题标题】:Rails order posts by attributes from multiple tablesRails 按属性从多个表中排序帖子
【发布时间】:2012-12-25 17:09:01
【问题描述】:

所以我在这里有一个社交书签式的设置。 4 个表:posts、users、cmets 和 votes。主页只是指向帖子模型的索引页面并在Post.all 数组上运行一个循环并显示一些基本信息:titleupvotes - downvotes(我称它为帖子的“分数”)。

现在我需要做的是,通过它们的分数除以它们的相对创建时间(自页面加载以来创建这篇文章以来的秒数?)在模型中排序这些帖子(我猜)。 Rails 在我的帖子对象中给了我一个created_at 列,我有一个如下所示的投票表:

帖子模型

class Post < ActiveRecord::Base
  attr_accessible  :id, :title, :url, :user_id, :comment_count, :agreement
  has_many :votes
  has_many :comments
  belongs_to :user
end

投票模型

class Vote < ActiveRecord::Base
  attr_accessible :direction, :post_id, :vote_type, :user_id
  belongs_to :user
  belongs_to :post
  belongs_to :comment
end

post 模型中的 ID 与投票模型中的 post_id 相对应。

:direction 是一个布尔值。 0 与赞成投票相关,1 与反对投票相关。 Post_ID 是投票的帖子的 ID。

我应该如何排序帖子,以便它们在主页上的位置由它们的创建时间和他们的赞成票 - 反对票决定?

def index
    @posts = Post.join(:votes).order(<???>)
end

ETA:@shweta 在加入帖子和投票表方面走上了正轨。但是,他的答案的选择部分不起作用。

【问题讨论】:

  • 应该有belongs_to :user belongs_to :post belongs_to :comment # 单数不是复数
  • 如果我这样做,它会破坏来自数据库的 :user 属性。这是一个不同的错误,有什么想法吗?
  • @shweta 通过将帖子模型中的用户列重命名为 user_id 来解决此问题。

标签: mysql ruby-on-rails ruby sorting


【解决方案1】:

免责声明:我不知道 ruby​​ 或 rails,只知道 SQL。

shweta 的回答似乎非常接近,所以我将尝试用一些 SQL hack 和斜线来完成它:

按赞排序,然后created_at

@posts = Post.joins(:votes).select("posts.id,sum(if(direction = 0, 1, -1)) as score,posts.created_at,OTHER_ATTRS_YOU_NEED").group("posts.id").order("score DESC,posts.created_at")

此外,要包含零票的帖子,您需要进行左连接:

@posts = Post.joins("LEFT JOIN votes ON posts.id = votes.post_id").select("posts.id,sum(if(direction = 0, 1, -1)) as score,posts.created_at,OTHER_ATTRS_YOU_NEED").group("posts.id").order("score DESC,posts.created_at")

更新:修正未投票帖子的排序:

@posts = Post.joins("LEFT JOIN votes ON posts.id = votes.post_id").select("posts.id,sum(if(direction = 0, 1, if(direction is null, 0, -1))) as score,posts.created_at,OTHER_ATTRS_YOU_NEED").group("posts.id").order("score DESC,posts.created_at")

更新 2: 提升排名

如果您排除较旧的项目,您最终可能会得到一个空列表(例如在安静的日子),这可能不是您想要的。您最好添加另一列进行排名,如下所示:

@posts = Post.joins("LEFT JOIN votes ON posts.id = votes.post_id").select(
    "posts.id," +
    "sum(if(direction = 0, 1, if(direction is null, 0, -1))) as score," +
    "(sum(if(direction = 0, 1, if(direction is null, 0, -1))) * " +
    " if(unix_timestamp() - unix_timestamp(posts.created_at) < 7200, 3, 1)) as rank," +
    "posts.created_at,OTHER_ATTRS_YOU_NEED"
).group("posts.id").order("rank DESC,posts.created_at")

出于排名目的,这会使不到 2 小时的帖子得分增加三倍。

【讨论】:

  • 哇,这几乎行得通!唯一的问题是 LEFT JOIN 一个命令带有反对票的帖子高于没有投票的帖子。我怎样才能包括没有投票的人,保留那些有反对票的人?
  • 非常感谢!效果很好!我可以排除太旧的东西吗?我要使用类似 Reddit 的算法。如果某些东西是新的并且有很多选票,那就让它成为#1。但如果有更新的内容出现,则排除几个小时前的内容。
  • 嗯,是的,这将会发生,因为它还没有投票。在获得更多选票之前,它的排名分数将为 0。您可能需要找到一些更聪明的方法来计算排名。您可以使用max(7200 - unix_timestamp() + unix_timestamp(posts.created_at), 1) 而不是if(unix_timestamp() - unix_timestamp(posts.created_at) &lt; 7200, 3, 1)),这应该大致按时间排序新帖子(
  • 糟糕,最后一条评论有误:greatest(7200 - unix_timestamp() + unix_timestamp(posts.created_at), 1)
  • 把它包裹在abs()中,比如abs(sum(if(direction = 0, 1, if(direction is null, 0, -1))))
【解决方案2】:

按赞排序,然后created_at

@posts = Post.joins(:votes).select("posts.id,count(votes.id) AS vote_count,posts.created_at,OTHER_ATTRS_YOU_NEED").group("posts.id").order("vote_count DESC,posts.created_at")

按 created_at 排序,然后点赞

@posts = Post.joins(:votes).select("posts.id,count(votes.id) AS vote_count,posts.created_at,OTHER_ATTRS_YOU_NEED").group("posts.id").order("posts.created_at, vote_count DESC")

【讨论】:

  • 我没有 vote_count 列,只有一个有投票行的投票表。
  • 好的,那我得到Mysql2::Error: Column 'created_at' in field list is ambiguous ...有什么想法吗?
  • 这就是我所拥有的:Post.joins(:votes).select("posts.id,count(votes.id) AS vote_count,url,title,user,created_at").group("posts.id").order("vote_count DESC,posts.created_at")
  • 所有这一切似乎都是为了排除没有投票的项目,它不会重新排列数组。
  • 我认为问题在于这应该是加入:votes,但投票是通过赞成票 - 反对票来计算的。你能再看看这个吗?谢谢。
【解决方案3】:

所以您想通过rating/(time elapsed since creation) 订购。它可以在纯 Ruby 中使用 sort_by 完成,但不如原始 sql 高效。

所以...在数据库上执行此操作需要计算自 Post#creation 以来经过的时间。在 Mysql 上,您有 TIMEDIFF(在 Postres/Oracle/etc 上可能会有所不同)用于这种计算。 SQL 或多或少包含(在轻量级/伪代码中):

class Post
  def self.order_by_popularity
    score        = "sum(if(direction = 0, 1, -1)) as score"
    votes_count  = "count(votes.id) as votes_count"
    time_elapsed = "ABS(Timediff(#{Time.now}, created_at)) as time_elapsed"

    select("posts.*, votes.id, #{score}, #{votes_count}, #{time_elapsed}").
    join(:votes).group("votes.id").
    order "ABS(votes_count/time_elapsed)"
  end
end

更新:汇总您获得上述所有建议(顺便说一句,默认情况下 join(:votes) 是左连接)。我想知道是否有任何陷阱....

其中 vote_count 将来自 Posts 上的缓存列或之前的 cmets 建议的 join+group 事物。

旁注:我成功地使用了这种类型的查询,因此我希望为您指明正确的方向,但坦率地说,这可能需要比典型的 stackoverflow 帮助提供的更多的测试/试验。

【讨论】:

  • 感谢您的新方向!我将对此进行试验。
  • 但是 votes_count 是在哪里定义的?我知道 timediff 是如何工作的,但 votes_count 是如何工作的?
  • 帖子或select("posts.*, count(votes.id) as vote_count").joins(:votes)上的常规/魔术rails属性
  • 只需将“votes_count”列添加到您的帖子表中。 belongs_to :post, :counter_cahe =&gt; trueapi.rubyonrails.org/classes/ActiveRecord/Associations/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-07-10
  • 1970-01-01
  • 1970-01-01
  • 2012-07-12
  • 2014-11-11
  • 2022-12-09
  • 2011-05-13
相关资源
最近更新 更多