【问题标题】:Select finishes where athlete didn't finish first for the past 3 events选择运动员在过去 3 场比赛中没有首先完成的比赛
【发布时间】:2013-06-19 07:43:25
【问题描述】:

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

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

【问题讨论】:

  • 你是怎么得到这两行的?
  • 在 2013 年 6 月 15 日获胜,因此忽略接下来的三个条目。然后抓住 2013-16-19, 2013-06-20 并忽略 2013-06-22 因为它只是最后一场胜利之后的一个事件
  • 快速提示:“日期”是一个糟糕的列名;它是一个类型名称,因此您必须在某些地方“双引号”它以避免语法错误。避免使用类型名或关键字作为列名。
  • 请始终在问题中包含您的 PostgreSQL 版本。在这种情况下,我和 Andomar 都使用了自 8.4 以来才可用的功能(可能是更新的)。

标签: sql postgresql window-functions


【解决方案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.

【讨论】:

  • 有趣的方法。我对 Pg 使用哈希反连接而不是嵌套循环执行它的事实印象深刻。
【解决方案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),所以它适用于最后一个n任何n。也比上次的尝试效率高很多。

当在非平凡大小的数据集上执行此查询与 @Andomar 查询时的相对效率,我真的很感兴趣。在这个微小的数据集上,它们几乎完全相同。需要在Table1(athlete, "date") 上建立索引才能在大型数据集上实现最佳性能。

【讨论】:

    【解决方案3】:

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

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

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

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

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

    WHERE  best IS DISTINCT FROM 1
    

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

    不要使用类型名称和保留字作为标识符,我用day 代替了您的date

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

    @Craig 已经提到了加快速度的索引。

    【讨论】:

    • +1 种有趣的东西,你会发现 min 聚合的性能最好:D
    • 关于min的使用非常好;这比使用array_agg 和包含测试要好得多。
    猜你喜欢
    • 1970-01-01
    • 2012-04-18
    • 1970-01-01
    • 1970-01-01
    • 2015-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-18
    相关资源
    最近更新 更多