Window 函数过滤当前行
Window functions filter through current row
这是 问题的后续问题,我的查询已改进为使用 window 函数而不是 LATERAL
连接内的聚合。虽然查询现在快了很多,但我发现结果不正确。
我需要对 x 年的跟踪时间范围执行计算。例如,price_to_maximum_earnings
是每行计算的,方法是将十年前的 max(earnings)
计算到当前行,然后用 price
除以结果。为简单起见,我们将在这里使用 1 年。
SQL Fiddle 这个问题。 (Postgres 9.6)
举个简单的例子,2010-01-01
的 price
和 peak_earnings
可以像这样分别计算:
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_earnings
和 minimum_earnings
只是最大值和整个数据集的最小值。它们应该是earnings
从1年前到当前行的max/min值。
这是怎么回事?我从 问题的答案中知道我不能简单地说 FILTER (WHERE date >= date1 AND date <= current_row.date)
,所以我缺少解决方案吗?我不能使用 window 帧,因为在任何给定时间范围内我都有不确定的行数,所以我不能只说 OVER (ROWS BETWEEN 365 PRECEDING AND CURRENT ROW)
。我可以使用框架 和 过滤器吗?这可能超过一年,然后过滤器可以捕获每个无效日期。我已经试过了,但没有成功。
Can I use a frame and a filter?
你可以。但两者都有限制:
FILTER
子句中的表达式只看到它获取值的相应行。无法引用 window 函数为其计算值的行。所以我看不到根据 that 行制定过滤器的方法,除非我们进行 huge, expensive cross join - 同一行是用于许多不同的计算。或者我们回到 LATERAL
子查询 可以 引用父行。
另一方面,框架定义根本不允许变量。正如您引用的相关答案中所讨论的那样,它需要一个固定的数字:
这些限制使您的特定查询难以实现。现在应该正确:
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;
备注
问题中没有任何内容表示每个 security_id
都会有同一天的行。在子查询 minmax
中计算每个 security_id
的最小/最大日期给我们最小时间范围。
计算的时间范围正好是行当前日期之前的 365 天,不包括当前行 (ROWS BETWEEN 365 PRECEDING AND 1 PRECEDING
)。 从聚合中排除当前行以与当前行进行比较通常更有用。
我将计算条件调整为相同的时间范围以避免极端情况:(date - 365) >= min_date
在 fiddle 中,每 1 月 1 日添加 1 行,您可以看到闰年与 365 天的固定数字对比的效果。 window 帧在闰年(2001、2005、...)之后为空。
我正在使用所有子查询,这通常比 CTE 快一点。
当然,我们需要在框架定义中包含ORDER BY
。我相应地更新了您链接到的旧答案:
我使用 w1
作为 window name,用于“1 年”期间。您可以添加 w2
,等等,并且每个天数可以是任意天数。如果需要,您毕竟可以适应闰年。甚至可能根据当前日期生成整个查询...
这是 LATERAL
连接内的聚合。虽然查询现在快了很多,但我发现结果不正确。
我需要对 x 年的跟踪时间范围执行计算。例如,price_to_maximum_earnings
是每行计算的,方法是将十年前的 max(earnings)
计算到当前行,然后用 price
除以结果。为简单起见,我们将在这里使用 1 年。
SQL Fiddle 这个问题。 (Postgres 9.6)
举个简单的例子,2010-01-01
的 price
和 peak_earnings
可以像这样分别计算:
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_earnings
和 minimum_earnings
只是最大值和整个数据集的最小值。它们应该是earnings
从1年前到当前行的max/min值。
这是怎么回事?我从 FILTER (WHERE date >= date1 AND date <= current_row.date)
,所以我缺少解决方案吗?我不能使用 window 帧,因为在任何给定时间范围内我都有不确定的行数,所以我不能只说 OVER (ROWS BETWEEN 365 PRECEDING AND CURRENT ROW)
。我可以使用框架 和 过滤器吗?这可能超过一年,然后过滤器可以捕获每个无效日期。我已经试过了,但没有成功。
Can I use a frame and a filter?
你可以。但两者都有限制:
FILTER
子句中的表达式只看到它获取值的相应行。无法引用 window 函数为其计算值的行。所以我看不到根据 that 行制定过滤器的方法,除非我们进行 huge, expensive cross join - 同一行是用于许多不同的计算。或者我们回到LATERAL
子查询 可以 引用父行。另一方面,框架定义根本不允许变量。正如您引用的相关答案中所讨论的那样,它需要一个固定的数字:
这些限制使您的特定查询难以实现。现在应该正确:
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;
备注
问题中没有任何内容表示每个
security_id
都会有同一天的行。在子查询minmax
中计算每个security_id
的最小/最大日期给我们最小时间范围。计算的时间范围正好是行当前日期之前的 365 天,不包括当前行 (
ROWS BETWEEN 365 PRECEDING AND 1 PRECEDING
)。 从聚合中排除当前行以与当前行进行比较通常更有用。
我将计算条件调整为相同的时间范围以避免极端情况:(date - 365) >= min_date
在 fiddle 中,每 1 月 1 日添加 1 行,您可以看到闰年与 365 天的固定数字对比的效果。 window 帧在闰年(2001、2005、...)之后为空。
我正在使用所有子查询,这通常比 CTE 快一点。
当然,我们需要在框架定义中包含
ORDER BY
。我相应地更新了您链接到的旧答案:我使用
w1
作为 window name,用于“1 年”期间。您可以添加w2
,等等,并且每个天数可以是任意天数。如果需要,您毕竟可以适应闰年。甚至可能根据当前日期生成整个查询...