2

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

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 子句,但我什至无法开始

4

3 回答 3

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()作为窗口函数来获取最后三行加上当前行的最小位置。由于窗口函数是在子句之后应用的,因此必须在下一个查询级别上
对“no win”()进行简单的检查。因此,对于窗口函数结果的条件,您至少需要一个CTE的子选择。best > 1WHERE

此处手册中有关窗口函数调用的详细信息。尤其:

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

如果place( finishing_pos) 可以为 NULL,请改用:

WHERE  best IS DISTINCT FROM 1

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

不要使用类型名称和保留字作为标识符,我用day你的date.

这假设每天最多 1 场比赛,否则您必须定义如何在时间线上与同行打交道或使用timestamp而不是date.

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

于 2013-06-22T12:44:52.403 回答
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

这里的逻辑几乎是问题的直译。获取最后四个展示位置 - 当前和前三个 - 并返回运动员在其中任何一个中没有首先完成的任何行。

因为窗口框架是唯一定义要考虑的历史行数的地方,所以你可以参数化这个变体,不像我以前的努力(过时,http ://sqlfiddle.com/#!1/fa3a4/31 ),所以它适用于n任何最后一个n。它也比上次尝试更有效率。

在非平凡大小的数据集上执行此查询与 @Andomar 查询的相对效率时,我真的很感兴趣。在这个微小的数据集上,它们几乎完全相同。Table1(athlete, "date")要在大型数据集上以最佳方式执行此操作,将需要一个索引。

于 2013-06-22T10:57:58.973 回答
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
        )

SQL Fiddle 上的实时示例。

于 2013-06-22T05:54:48.747 回答