2015-04-05 41 views
1

我对于复杂排名函数所需的SQL很感兴趣。这是一款适用于赛车运动的应用程序,我需要根据条目的:total_time对每个Entry进行排名TimesheetSQL中使用Postgres进行复杂排名

相关机型:

class Timesheet 
    has_many :entries 
end 

class Entry 
    belongs_to :timesheet 
    belongs_to :athlete 
end 

class Run 
    belongs_to :entry 
end 

条目的:total time不存储在数据库中。这是一个计算列runs.sum(:finish)。我使用Postgres(9.3)rank()函数获取给定时间表的条目,并按计算出的列对它们进行排名。

def ranked_entries 
    Entry.find_by_sql([ 
    "SELECT *, rank() OVER (ORDER BY total_time asc) 
    FROM(
     SELECT Entries.id, Entries.timesheet_id, Entries.athlete_id, 
     SUM(Runs.finish) AS total_time 
     FROM Entries 
     INNER JOIN Runs ON (Entries.id = Runs.entry_id) 
     GROUP BY Entries.id) AS FinalRanks 
     WHERE timesheet_id = ?", self.id]) 
end 

到目前为止好。这会返回具有rank属性的我的输入对象,我可以在timesheet#show上显示该属性。

现在棘手的部分。在Timesheet,并非每个Entry将具有相同的运行次数。有一个截止点(通常是前20名,但并不总是)。这使得Postgres的rank()不准确,因为一些参赛者比竞赛获胜者有更低的:total_time,因为他们没有为第二次高潮做出决定。

我的问题:是否有可能像做一个rank()内的rank()产生一个表,看起来像下面的一个?还是有另一种首选的方式?谢谢!

注:我店倍整数,但我格式化它们作为比较熟悉MM:在简化见下表SS为清楚起见

| rank | entry_id | total_time | 
|------|-----------|------------| 
| 1 |  6  | 1:59.05 | 
| 2 |  3  | 1:59.35 | 
| 3 |  17 | 1:59.52 | 
|......|...........|............| 
| 20 |  13 |  56.56 | <- didn't make the top-20 cutoff, only has one run. 
+0

这听起来像你不应该摆在首位来选择所有行(所有运行?)。如果你选择了正确的行 - 一个将排除所有只有一次运行的条目的选择 - 那么rank()将返回你期望的结果。在你的问题的上下文中,我想我会说,首选的方法是选择正确的行* first *,之后排名非常简单。 – 2015-04-05 19:20:35

+0

我选择所有行,因为我想包括在排名中只有一次运行的条目。无论运行次数如何,每个条目都需要进行排名。排名前20的球队是根据total_time排名的,而21球队的排名是他们首轮比赛的结束时间。 – jktress 2015-04-05 19:24:15

+0

只需对平均值进行排名而不是总数呢? – 2015-04-05 19:45:27

回答

0

让我们创建一个表。 (获取包括CREATE在所有SQL问题TABLE和INSERT语句的习惯。)

create table runs (
    entry_id integer not null, 
    run_num integer not null 
    check (run_num between 1 and 3), 
    run_time interval not null 
); 

insert into runs values 
(1, 1, '00:59.33'), 
(2, 1, '00:59.93'), 
(3, 1, '01:03.27'), 
(1, 2, '00:59.88'), 
(2, 2, '00:59.27'); 

此SQL语句会给你你想要的顺序总数,但没有排名他们。

with num_runs as (
    select entry_id, count(*) as num_runs 
    from runs 
    group by entry_id 
) 
select r.entry_id, n.num_runs, sum(r.run_time) as total_time 
from runs r 
inner join num_runs n on n.entry_id = r.entry_id 
group by r.entry_id, n.num_runs 
order by num_runs desc, total_time asc 
 
entry_id num_runs total_time 
-- 
2   2   00:01:59.2 
1   2   00:01:59.21 
3   1   00:01:03.27 

此语句级别添加一列。

with num_runs as (
    select entry_id, count(*) as num_runs 
    from runs 
    group by entry_id 
) 
select 
    rank() over (order by num_runs desc, sum(r.run_time) asc), 
    r.entry_id, n.num_runs, sum(r.run_time) as total_time 
from runs r 
inner join num_runs n on n.entry_id = r.entry_id 
group by r.entry_id, n.num_runs 
order by rank asc 
 
rank entry_id num_runs total_time 
-- 
1  2   2   00:01:59.2 
2  1   2   00:01:59.21 
3  3   1   00:01:03.27 
+0

谢谢迈克!我会试验这个,并让你知道它是如何发生的。 – jktress 2015-04-05 20:03:50

+0

精美的作品,非常感谢!一个跟进。这将获得数据库中的所有运行,但我只需要运行一个时间表。你会如何推荐我限制运行到他们的entry.timesheet?再次感谢! – jktress 2015-04-05 20:48:09

+0

是的,沿着这些线。在公用表格表达式中,您一定需要这样做,以便为时间表中的每个条目获取正确的运行次数。根据entry_id和timeheet_id的关联方式,您可能也需要在主查询中使用相同的WHERE子句。或者,您可以在CTE中包含timesheet_id,并在主查询中加入entry_id *和* timesheet_id。 – 2015-04-05 21:22:25