【问题标题】:Issues with DISTINCT when used in conjunction with ORDER与 ORDER 结合使用时 DISTINCT 的问题
【发布时间】:2012-02-06 04:16:07
【问题描述】:

我正在尝试构建一个网站,对特定赛事中选定运动员的表现进行排名 - 我之前发布了一个问题,收到了一些很好的回应,我确定了我目前代码的关键问题。

我有 2 个模型 - AthleteResult(运动员有很多结果)

每个运动员可以有多个特定事件的记录时间,我想确定每个运动员的最快时间,并将这些最快时间在所有运动员中排名。

我使用以下代码:

 <% @filtered_names = Result.where(:event_name => params[:justevent]).joins(:athlete).order('performance_time_hours ASC').order('performance_time_mins ASC').order('performance_time_secs ASC').order('performance_time_msecs ASC') %>

这成功地对所有运动员的所有结果进行了排名(即一名运动员可以在不同的地方出现多次,具体取决于他们记录的时间)。

我现在希望只为每个运动员提取最好的成绩并将他们列入排名。我可以使用以下方法选择与最佳结果相对应的时间:

 <% @currentathleteperformance = Result.where(:event_name => params[:justevent]).where(:athlete_id => filtered_name.athlete_id).order('performance_time_hours ASC').order('performance_time_mins ASC').order('performance_time_secs ASC').order('performance_time_msecs ASC').first() %>

但是,当我尝试识别@filtered_names 中列出的不同运动员姓名时,我的问题就出现了。我尝试使用&lt;% @filtered_names = @filtered_names.select('distinct athlete_id') %&gt;,但这并不符合我的预期,有时它会以错误的顺序获得排名。

我发现,就目前而言,我的代码本质上是寻找不同运动员结果之间的差异,从小时时间开始,一直到分钟、秒和毫秒。一旦发现每个不同运动员的结果之间存在差异,它就会相应地对其进行排序。

例如,如果我有 2 名运动员:

Time for Athlete 1 = 0:0:10:5
Time for Athlete 2 = 0:0:10:3

这将产生顺序,运动员 2,运动员 1

但是,如果我有:

Time for Athlete 1 = 0:0:10:5
Time for Athlete 2 = 0:0:10:3
Time for Athlete 2 = 0:1:11:5

然后将顺序指定为运动员 1,运​​动员 2,因为第一个差异在分钟数中,而运动员 2 较慢...

任何人都可以提出一种解决此问题的方法,并基本上将@filtered_names 中的条目在第一次出现时提取每个名称(即保持名称在@filtered_names 中首次出现的顺序)

感谢您的宝贵时间

【问题讨论】:

    标签: sql ruby-on-rails ruby-on-rails-3


    【解决方案1】:

    如果您使用的是 Ruby 1.9.2+,您可以使用 Array#uniq 并传递一个指定如何确定唯一性的块。例如:

    @unique_results = @filtered_names.uniq { |result| result.athlete_id }
    

    每个运动员应该只返回一个结果,并且一个结果应该是数组中的第一个结果,这反过来将是您已经订购结果以来最快的时间。

    一个警告:@filtered_names 可能仍然是一个ActiveRecord::Relation,它有自己的#uniq 方法。您可能首先需要调用 #all 以返回结果数组:

    @unique_results = @filtered_names.all.uniq { ... }
    

    【讨论】:

    • 布兰登,非常感谢!使用 1.9.2 并且仅顶部部分工作得很好
    • 知道如何在旧版本的 Rails 中做到这一点,我认为我们的主机提供商可能安装了 1.9.0...?对不起...
    • 哇!我从来没有真正看到过 1.9.0,尤其是 Rails(如果你使用的是 1.9,我相信 Rails 需要 1.9.2+)。您可以在您的托管服务中运行ruby -v 来确定吗?您可能必须实现自己的 #uniq 版本,但它会比 Ruby 在本机 C 中的实现要慢,后者可能已经比在数据库中的 SQL 中执行它要慢,所以请注意这一点。
    • Brandan - 感谢您的留言。您发现了我的错误猜测,它实际上是服务器上的 1.8.7,因此使用 uniq_by 而不是 uniq 使您的建议完美运行。再次感谢
    • 感谢您将我指向#uniq_by。我没有意识到这一点。
    【解决方案2】:

    您应该使用 DB 来执行最大值计算,而不是 ruby​​ 代码。在results 表中添加一个名为total_time_in_msecs 的新列,并在每次更改结果表时为其设置值。

    class Result < ActiveRecord::Base
    
      before_save :init_data
    
      def init_data
        self.total_time_in_msecs = performance_time_hours * MSEC_IN_HOUR +       
                                    performance_time_mins * MSEC_IN_MIN +  
                                    performance_time_secs * MSEC_IN_SEC + 
                                    performance_time_msecs
      end
    
      MSEC_IN_SEC  = 1000
      MSEC_IN_MIN  = 60 * MSEC_IN_SEC
      MSEC_IN_HOUR = 60 * MSEC_IN_MIN
    end
    

    现在您可以编写如下查询:

    athletes = Athlete.joins(:results).
      select("athletes.id,athletes.name,max(results.total_time_in_msecs) best_time").
      where("results.event_name = ?", params[:justevent])
      group("athletes.id, athletes.name").
      orde("best_time DESC")
    
    
    athletes.first.best_time # prints a number
    

    编写一个简单的助手来分解数字时间部分:

    def human_time time_in_msecs
    
      "%d:%02d:%02d:%03d" %   
        [Result::MSEC_IN_HOUR, Result::MSEC_IN_MIN, 
            Result::MSEC_IN_SEC, 1 ].map do |interval|
          r = time_in_msecs/interval
          time_in_msecs = time_in_msecs % interval
          r
        end
    
    end
    

    在您的视图中使用帮助器来显示细分的时间。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-29
      • 2021-11-28
      • 2017-09-24
      • 1970-01-01
      • 2015-07-09
      • 1970-01-01
      相关资源
      最近更新 更多