窗口函数过滤当前行

问题描述 投票:3回答:1

这是this问题的后续问题,我的查询被改进为使用窗口函数而不是LATERAL连接中的聚合。虽然查询现在要快得多,但我发现结果不正确。

我需要在x年尾随时间帧上执行计算。例如,price_to_maximum_earnings是通过将max(earnings)在十年前获得到当前行并将price除以结果来计算的。我们将在这里使用1年的简单性。

SQL Fiddle提出这个问题。 (Postgres 9.6)

举个简单的例子,pricepeak_earnings2010-01-01可以像这样单独计算:

SELECT price
FROM security_data
WHERE date = '2010-01-01'
AND security_id = 'SPX';

SELECT max(earnings) AS min_earnings
FROM bloomberg.security_data
WHERE date >= '2000-01-01'
AND date <= '2010-01-01'
AND security_id = 'SPX';

为了每行执行此操作,我使用以下内容:

SELECT security_id, date, price
     , CASE WHEN date1 >= min_date
            THEN price / NULLIF(max(earnings) FILTER (WHERE date >= date1) OVER w, 0) END AS price_to_peak_earnings
FROM
(
  SELECT record_id, security_id, price, date, earnings
           , (date - interval '1 y')::date AS date1
           , min(date) OVER (PARTITION BY security_id) AS min_date
      FROM   security_data
) d
WINDOW w AS (PARTITION BY security_id);

我认为这里的问题源于FILTER的使用,因为它似乎没有像我想要的那样工作。请注意,在链接的SQL Fiddle中,我显示了FILTER的结果,并且对于每一行,peak_earningsminimum_earnings只是整个数据集的最大值和最小值。它们应该是earnings从1年前到当前行的最大/最小值。

这里发生了什么?我从this问题的答案中知道我不能简单地说FILTER (WHERE date >= date1 AND date <= current_row.date),所以有没有我错过的解决方案?我不能使用窗框,因为我在任何给定的时间范围内都有不确定的行数,所以我不能只说OVER (ROWS BETWEEN 365 PRECEDING AND CURRENT ROW)。我可以使用框架和过滤器吗?这可能超过一年前,然后过滤器可以捕获每个无效的日期。我试过这个,但没有成功。

sql postgresql window-functions postgresql-performance
1个回答
1
投票

我可以使用框架和过滤器吗?

您可以。但要么有限制:

  • FILTER子句中的表达式只能看到它获取值的相应行。无法引用窗口函数计算值的行。因此,我没有看到根据该行制定过滤器的方法,除非我们进行巨大,昂贵的交叉连接 - 同一行用于许多不同的计算。或者我们回到可以引用父行的LATERAL子查询。
  • 另一方面,帧定义根本不允许变量。它需要一个固定的数字,如您引用的相关答案中所述: Referencing current row in FILTER clause of window function

这些限制使您的特定查询难以实现。这应该是正确的:

SELECT *
FROM  (
   SELECT record_id, security_id, date, price
        , CASE WHEN do_calc THEN                max(earnings) OVER w1     END AS peak_earnings
        , CASE WHEN do_calc THEN                min(earnings) OVER w1     END AS minimum_earnings
        , CASE WHEN do_calc THEN price / NULLIF(max(earnings) OVER w1, 0) END AS price_to_peak_earnings
        , CASE WHEN do_calc THEN price / NULLIF(min(earnings) OVER w1, 0) END AS price_to_minimum_earnings
   FROM  (
      SELECT *, (date - 365) >= min_date AND s.record_id IS NOT NULL AS do_calc
      FROM  (
         SELECT security_id, min_date
              , generate_series(min_date, max_date, interval '1 day')::date AS date
         FROM  (
            SELECT security_id, min(date) AS min_date, max(date) AS max_date
            FROM   security_data
            GROUP  BY 1
            ) minmax
         ) d
      LEFT   JOIN  security_data s USING (security_id, date)
      ) sub1
   WINDOW w1 AS (PARTITION BY security_id ORDER BY date ROWS BETWEEN 365 PRECEDING AND 1 PRECEDING)
   ) sub2
WHERE  record_id IS NOT NULL 
ORDER  BY 1, 2;

SQL Fiddle.

Notes

  • 问题中没有任何内容表明每个security_id都会在同一天有行。在子查询security_id中计算每个minmax的最小/最大日期给出了最小时间范围。
  • 计算的时间范围恰好是行当前日期之前365天,不包括当前行(ROWS BETWEEN 365 PRECEDING AND 1 PRECEDING)。从聚合中排除当前行以与当前行进行比较通常更有用。 我将计算条件调整到相同的时间范围,以避免角落奇怪:(date - 365) >= min_date
  • fiddle,您在1月1日每增加1行,您可以看到leapyears的效果与固定数量的365天形成对比。在leapyears(2001年,2005年,...)之后窗框是空的。
  • 我正在使用所有子查询,这通常比CTE快一些。
  • 可以肯定的是,我们需要在帧定义中包含ORDER BY。我相应地更新了您链接到的旧答案: Referencing current row in FILTER clause of window function
  • 在“1年”期间,我使用w1作为窗口名称。您可以添加w2等,每个都可以有任意天数。如果你需要的话,你可以适应水稻。甚至可能根据当前日期生成整个查询...
© www.soinside.com 2019 - 2024. All rights reserved.