【问题标题】:SQL query to get "vote score"获取“投票分数”的 SQL 查询
【发布时间】:2009-09-13 01:27:04
【问题描述】:

我的votes 表如下所示:

id: integer
vote: boolean
voteable_id: integer
voteable_type: string
voter_id: integer
voter_type: string

vote 列确定该行是代表“赞成票”(vote = true) 还是“反对票”(vote = false)。

voteable_type是被投票事物的类别,voteable_id是被投票事物的id,voter_type是投票者的类别,voter_id是投票者的id。

我需要的是一个查询,以按 “投票得分” 降序排列获得前 n posts,其中“投票得分”定义为(帖子的赞成票数)-(帖子的反对票数)。

如果您的解决方案不需要我求助于find_by_sql()(我在 Rails 中工作),则可以加分

如果您的解决方案在 SQLite 和 PostgreSQL 中以相同的方式工作,则会获得更多奖励积分(尽管更重要的是它在 PostgreSQL 中工作)。

【问题讨论】:

  • 如果您将其存储为 1/-1 而不是 true/false,也会让您的生活更轻松。

标签: sql ruby-on-rails postgresql


【解决方案1】:

通常,您可以通过使用带有sumcase 语句来实现:

select
    voteable_id,
    sum(case
        when vote then 1
        else -1
    end) as vote_score
from
    votes
group by
    voteable_id

请注意,这是 ANSI SQL,因此可以跨 SQLite、MySQL、Postgres、Oracle、SQL Server、DB2 等使用。

要获得前 N 个帖子,您只需附加到上述查询:

order by
    vote_score desc
limit 10

limit 被 Postgres 和 SQLite 使用(在 MySQL 中略有不同),而不是在 Oracle 或 SQL Server 中,仅供参考。

所以,要获取与此相关的帖子信息:

select
    p.title,
    p.author,
    p.createdate,
    sum(case
            when v.vote then 1
            else -1
        end) as vote_score
from
    posts p
    inner join votes v on
        p.post_id = v.voteable_id
group by
    p.title,
    p.author,
    p.createdate
order by
    vote_score desc
limit 10

【讨论】:

  • 现在也添加前N个需求?
  • 酷——然后我如何将join 添加到组合中以获取与返回的voteable_ids 关联的posts
  • 谢谢,但是当我尝试第二个查询时,我得到了SQL error: near "sum": syntax error
  • 最后一个查询没有group by,d'oh!
【解决方案2】:

SQLite 和 PostgreSQL(以及其他符合 ANSI 的 SQL 实现)通用的条件函数是 CASE -- 参见例如here 用于 PostgreSQL,here 用于 SQLite。所以“计数 +1 / -1 票数”的内部部分必须是

SUM(CASE WHEN vote THEN 1 ELSE -1 END)

您还不可避免地需要GROUP BY voteable_id 才能使SUM 正常工作。

这需要在ORDER BY 中进行排序(使用 DESC);不确定您是否也希望它出现在结果中,但我假设您这样做,在这种情况下,它还需要位于 SELECT 中(您可以在 ORDER BY 中引用它的别名)。最后,LIMIT n 可以在两个引擎中使用。

所以,把它们放在一起:

  SELECT voteable_id,
         SUM(CASE WHEN vote THEN 1 ELSE -1 END) AS vote_score
  FROM votes
  GROUP BY voteable_id
  ORDER BY vote_score DESC
  LIMIT 10

应该满足您的所有要求。

【讨论】:

  • 酷——然后我如何将join 添加到组合中以获取与返回的voteable_ids 关联的posts
  • 在 FROM 和 GROUP BY 之间,添加 JOIN posts USING(voteable_id)(如果这是两个表上的字段名称,否则使用 ON 条件)并在 SELECT 中从 posts. 中选择所需的字段;还要确保根据需要消除 votes.voteable_id 等的歧义(当涉及 > 1 个表时,建议明确限定字段!-)。
【解决方案3】:

如果您使用的是 VoteFu(看起来就是这样),那么我建议您转换为使用整数来存储投票值,而不是 :boolean。

VoteFu 中的投票存储为布尔值的唯一原因是我觉得我需要保持与 Acts_As_Voteable 的向后兼容性。我现在已经决定我对这个问题过于重视了。

我将发布一个新版本的 VoteFu 插件来为您处理转换,但在此之前我认为自己更改它是明智之举。方法如下:

class VoteFuIntegerMigration < ActiveRecord::Migration  
  def self.up  
    add_column :votes, :vote_int, :integer
    Vote.find(:all).each do |vote|
       vote.vote_int = vote.vote? ? 1 : -1
       vote.save!
    end
    remove_column :vote, :vote
    rename_column :vote, :vote_int, :vote
  end
end

然后:

module Juixe 
  module Acts   
    module Voteable   
      module InstanceMethods  
        def votes_for  
          Vote.sum(:vote, :conditions => [  
            "voteable_id = ? AND voteable_type = ? AND vote > 0",  
            id, self.class.name])  
        end  

        def votes_against  
          Vote.sum(:vote, :conditions => [  
            "voteable_id = ? AND voteable_type = ? AND vote < 0",  
            id, self.class.name])  
        end  

        def votes_total  
          Vote.sum(:vote, :conditions => [  
            "voteable_id = ? AND voteable_type = ?",  
            id, self.class.name])  
        end  
      end
    end
  end
end

将模块更改作为猴子补丁引入可能是个好主意。新的 VoteFu 将集成此代码(也包含测试)。

【讨论】:

  • Pete,下一个版本有进展吗? (我仍然爱你的宝石!)
  • 我在编写我的下一个版本时非常疏忽!其他几个项目篡夺了我。我目前正在寻找其他提交者来提供帮助。如果您想从您的解决方案中提交一个执行整数转换的补丁,我很乐意 1)完全信任您,并且 b)让您成为项目的提交者。让我知道!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-20
  • 1970-01-01
相关资源
最近更新 更多