2

我有一个数据库,其中有一个名为 matchstats 的表,其中包含一个名为 time 的列,每次发生操作时都会更新它。我还有一个名为 groundstatsid 的列,当它不为空时,表示动作发生在地面上,而不是站立。最后,我有一个名为 Round 的专栏。

例子:

Time | groundstatsid | Round

 1   | NULL          |  1
 8   | NULL          |  1
 15  | NULL          |  1
 18  | 1             |  1
 20  | 1             |  1
 22  | NULL          |  1
 30  | NULL          |  1
 1   | NULL          |  2

为了获得全职站立,我基本上希望查询第一次 (1) 并存储它,然后查看 groundstatsid 直到它看到一个 NON NULL 值并在该位置花费时间,减去存储到的较早数字获得站立时间 (17)。然后它会继续寻找 groundstatsid IS NULL 的位置。一旦找到该值,它应该执行相同的查找过程,直到在 groundstatsid 或新一轮中找到 NON NULL 值,在这种情况下,它将重新开始整个过程​​。

一旦它完成了整场比赛,我希望它对结果求和。

我希望该示例的查询返回 25。

4

2 回答 2

4

我会把这个问题归结为一个你考虑成对的行,time在每一轮中排序。PostgreSQL 可以一次性做到这一点——没有 JOIN,没有 PL/pgSQL——使用窗口函数

SELECT
  round,
  first_value(time) OVER pair AS first_time,
  last_value(time) OVER pair AS last_time,
  first_value(groundstatsid IS NULL) OVER pair AS first_is_standing,
  last_value(groundstatsid IS NULL) OVER pair AS last_is_standing
FROM matchstats
WINDOW pair AS (PARTITION BY round ORDER BY time ROWS 1 PRECEDING);

这告诉 PostgreSQL 从表中读取行(可能受约束WHERE fightid=?或其他东西),但要round分别考虑每个行以进行窗口操作。Window 的功能类似于first_value并且last_value可以访问我指定为的“窗口”,这ORDER BY time ROWS 1 PRECEDING意味着该窗口既包含当前行,也包含时间上紧接在其前面的行(如果有的话)。因此,窗口函数让我们可以直接输出当前行及其前行的值。

对于您提供的数据,此查询产生:

 round | first_time | last_time | first_is_standing | last_is_standing 
-------+------------+-----------+-------------------+------------------
     1 |          1 |         1 | t                 | t
     1 |          1 |         8 | t                 | t
     1 |          8 |        15 | t                 | t
     1 |         15 |        18 | t                 | f
     1 |         18 |        20 | f                 | f
     1 |         20 |        22 | f                 | t
     1 |         22 |        30 | t                 | t
     2 |          1 |         1 | t                 | t

查看这些结果帮助我决定下一步该做什么。根据我对您的逻辑的理解,我得出的结论是,该人应被视为从时间 1..1、1..8、8..15、15..18 开始,而不是从 18..20 开始,不站立从 20..22 开始,并从 22..30 再次站立。换句话说,我们想要总结两者之间的差异first_timelast_time哪里first_is_standing是真的。将其转回 SQL:

SELECT round, SUM(last_time - first_time) AS total_time_standing
FROM (
  SELECT
    round,
    first_value(time) OVER pair AS first_time,
    last_value(time) OVER pair AS last_time,
    first_value(groundstatsid IS NULL) OVER pair AS first_is_standing,
    last_value(groundstatsid IS NULL) OVER pair AS last_is_standing
  FROM matchstats
  WINDOW pair AS (PARTITION BY round ORDER BY time ROWS 1 PRECEDING)
) pairs
WHERE first_is_standing
GROUP BY round;
 round | total_time_standing 
-------+---------------------
     1 |                  25
     2 |                   0

您还可以从同一个内部查询中获取其他值,例如总时间或通过使用SUM(CASE WHEN ...)计数独立条件的跌倒次数:

SELECT
  round,
  SUM(CASE WHEN first_is_standing THEN last_time - first_time ELSE 0 END) AS total_time_standing,
  SUM(CASE WHEN first_is_standing AND NOT last_is_standing THEN 1 ELSE 0 END) AS falls,
  SUM(last_time - first_time) AS total_time
FROM (
  SELECT
    round,
    first_value(time) OVER pair AS first_time,
    last_value(time) OVER pair AS last_time,
    first_value(groundstatsid IS NULL) OVER pair AS first_is_standing,
    last_value(groundstatsid IS NULL) OVER pair AS last_is_standing
  FROM matchstats
  WINDOW pair AS (PARTITION BY round ORDER BY time ROWS 1 PRECEDING)
) pairs
GROUP BY round;

 round | total_time_standing | falls | total_time 
-------+---------------------+-------+------------
     1 |                  25 |     1 |         29
     2 |                   0 |     0 |          0
于 2012-11-16T17:11:21.903 回答
1

这将计算任意轮数的站立时间:

SELECT round, sum(down_time - up_time) AS standing_time
FROM  (
   SELECT round, grp, standing, min(time) AS up_time
         ,CASE WHEN standing THEN 
             lead(min(time), 1, max(time)) OVER (PARTITION BY round
                                                 ORDER BY min(time))
          ELSE NULL END AS down_time
   FROM  (
      SELECT round, time, groundstatsid IS NULL AS standing
            ,count(groundstatsid) OVER (PARTITION BY round
                                        ORDER BY time) AS grp
      FROM tbl
      ) x
   GROUP BY 1, 2, standing
   ) y
WHERE  standing
GROUP  BY round
ORDER  BY round;

-> sqlfiddle

解释

  • 子查询 x:

    count()利用不计算值的事实NULL(既不作为聚合也不作为窗口函数)。具有“站立”动作 ( groundstatsid IS NULL) 的连续行以相同的值结束grp

    简化groundstatsid为 boolean var standing,以便于使用和优雅。

  • 子查询 y:

    每组汇总 - 站立时间很重要。从地面开始,我们只需要每个站立阶段后的第一排。

    以每组的最短时间为up_time(站立)

    取下time一行 ( ) 中的 ( lead(min(time) ...) 作为down_time(在地面上)。请注意,您可以在窗口函数中使用聚合值:

    lead(min(time), 1, max(time)) OVER ...如果回合结束(没有下一行),则min(time)默认为当前行的下一个回合。max(time)

  • 最终选择:

    只考虑站立时间:WHERE groundstatsid IS NULL

    sum(down_time - up_time)汇总每轮的总站立时间。

    每轮结果排序。瞧。

这使得大量使用窗口函数。需要 PostgreSQL 8.4 或更高版本。
如果性能是您的首要要求,您可以在 plpgsql 函数中执行相同的程序。此处此处
的 示例。

于 2012-11-16T17:55:50.190 回答