使用Postgres的SQL中的复杂秩
我对复杂的秩函数所需的SQL非常熟悉。这是一个赛车运动的应用程序,我需要根据条目的:总时间对时间表中的每个条目进行排名 相关模型:使用Postgres的SQL中的复杂秩,sql,ruby-on-rails,postgresql,Sql,Ruby On Rails,Postgresql,我对复杂的秩函数所需的SQL非常熟悉。这是一个赛车运动的应用程序,我需要根据条目的:总时间对时间表中的每个条目进行排名 相关模型: class Timesheet has_many :entries end class Entry belongs_to :timesheet belongs_to :athlete end class Run belongs_to :entry end 条目的:总时间未存储在数据库中。这是一个计算的运行列。总和:finish。我使用Postg
class Timesheet
has_many :entries
end
class Entry
belongs_to :timesheet
belongs_to :athlete
end
class Run
belongs_to :entry
end
条目的:总时间未存储在数据库中。这是一个计算的运行列。总和:finish。我使用Postgres9.3Rank函数来获取给定时间表的条目,并按此计算列对它们进行排序
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属性的条目对象,我可以在时间表上显示该属性
现在是棘手的部分。在时间表上,并非每个条目都有相同的运行次数。通常会有一个前20名的截止日期,但并不总是如此。这使得Postgres的排名不准确,因为有些参赛者的总时间低于比赛冠军,因为他们没有进入第二轮比赛的截止时间
我的问题是:是否可以在一个列组中执行类似列组的操作,以生成一个与下面的表类似的表?还是有其他更可取的方式?谢谢
注意:我将时间存储为整数,但为了清晰起见,我在下面的简化表中将它们格式化为更熟悉的MM:SS
让我们创建一个表。养成在所有SQL问题中包含CREATETABLE和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
听起来你不应该选择所有的行所有的运行?首先。如果您选择了正确的行—该选择将排除只有一次运行的所有条目,那么rank将返回您期望的结果。在你的问题背景下,我想我会说,首选的方法是先选择正确的行,然后再进行排名非常简单。我选择所有行是因为我想包括排名中只有一次的条目。无论跑步次数多少,每个参赛项目都需要排名。前20名是根据总时间排名的,而21名是根据他们的第一次跑步完成时间与整个场地的比较排名的。不如只排名平均值而不是总时间?我不完全确定他们的第一次跑步完成时间与整个场地的比较意味着什么,但我写了一个答案来说明这一技巧。生成一个新列,用于区分finisher和non finisher,并在排名排序中使用它。我用的是CTE;如果你想的话,你可以使用子查询。嘿,戈登,我也想到了同样的事情,而且在大多数情况下都是有效的。问题是,如果天气状况在两次热身之间发生变化,那么排名就会下降。可能第一次加热的条件非常快,第二次加热的条件非常慢。没有获得预选赛资格的运动员的平均成绩比那些获得预选赛资格的运动员要快,因此应该排在第一位!我会尝试一下,让你知道结果如何。效果很好,非常感谢!一次跟进。这将获取数据库中的所有运行,但我只需要一个时间表的运行。您建议我如何将运行限制为其条目。时间表?再次感谢!是的,沿着这些路线。在公共表表达式中,您肯定需要这样才能为时间表中的每个条目获得正确的运行次数。根据entry\u id和timesheet\u id的关系,在主查询中可能也需要相同的WHERE子句。或者,您可以在CTE中包含时间表id,并在主查询中加入条目id和时间表id。
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