选择运动员在过去的3场比赛中没有首先完成的表现

时间:2013-06-22 05:46:24

标签: sql postgresql window-functions

假设我有一个运动会议结果数据库,其架构如下

DATE,NAME,FINISH_POS

我希望进行查询以选择运动员参加过至少三场比赛而没有获胜的所有行。例如,使用以下示例数据

2013-06-22,Johnson,2
2013-06-21,Johnson,1
2013-06-20,Johnson,4
2013-06-19,Johnson,2
2013-06-18,Johnson,3
2013-06-17,Johnson,4
2013-06-16,Johnson,3
2013-06-15,Johnson,1

以下行:

2013-06-20,Johnson,4
2013-06-19,Johnson,2

会匹配。我只是设法开始使用以下存根:

select date,name FROM table WHERE ...;

我一直试图绕过where子句,但我甚至无法开始

3 个答案:

答案 0 :(得分:4)

我认为这可以更简单/更快:

SELECT day, place, athlete
FROM  (
   SELECT *, min(place) OVER (PARTITION BY athlete
                              ORDER BY day
                              ROWS 3 PRECEDING) AS best
   FROM   t
   ) sub
WHERE  best > 1

->SQLfiddle

使用聚合函数min()作为窗口函数来获取最后三行加上当前行的最小位置。
由于在 best > 1子句之后应用了窗口函数,因此必须在下一个查询级别上执行“无法获胜”(WHERE)的平凡检查。因此,对于窗口函数的结果,您至少需要一个CTE子选择。

有关window function calls in the manual here的详细信息。特别是:

  

如果省略 frame_end ,则默认为CURRENT ROW

如果placefinishing_pos)可以为NULL,请改用:

WHERE  best IS DISTINCT FROM 1

min()会忽略NULL个值,但如果框架中的所有行都为NULL,则结果为NULL

不要使用类型名称和保留字作为标识符,我将day替换为date

这假定每天最多1场比赛,否则你必须定义如何在时间线上与同伴交易,或者使用timestamp代替date

@Craig已经提到了使这个速度更快的索引。

答案 1 :(得分:2)

这是一种替代配方,可以在没有子查询的情况下进行两次扫描:

SELECT
  "date", athlete, place
FROM (
  SELECT 
    "date",
    place,
    athlete,
    1 <> ALL (array_agg(place) OVER w) AS include_row
  FROM Table1
  WINDOW w AS (PARTITION BY athlete ORDER BY "date" ASC ROWS BETWEEN 3 PRECEDING AND CURRENT ROW)
) AS history
WHERE include_row;

请参阅:http://sqlfiddle.com/#!1/fa3a4/34

这里的逻辑几乎就是问题的字面翻译。获取最后四个位置 - 当前和之前的3个 - 然后返回运动员在其中任何一个中没有完成的任何行。

因为窗口框架是唯一要定义要考虑的历史记录行数的位置,所以可以参数化此变体,这与我以前的工作(过时的,http://sqlfiddle.com/#!1/fa3a4/31)不同,因此它适用于最后一个{{ 1}}适用于任何n。它比上一次尝试效率更高。

在对非平凡大小的数据集执行时,我真的对这个vs @Andomar查询的相对效率感兴趣。它们在这个微小的数据集上几乎完全相同。为了在大型数据集上以最佳方式执行,需要n上的索引。

答案 2 :(得分:1)

; with  CTE as
        (
        select  row_number() over (partition by athlete order by date) rn
        ,       *
        from    Table1
        )
select  *
from    CTE cur
where   not exists
        (
        select  *
        from    CTE prev
        where   prev.place = 1
                and prev.athlete = cur.athlete
                and prev.rn between cur.rn - 3 and cur.rn
        )

Live example at SQL Fiddle.