如何在 Redshift 中实现窗口运行中位数?
Posted
技术标签:
【中文标题】如何在 Redshift 中实现窗口运行中位数?【英文标题】:How can I achieve a windowed running median in Redshift? 【发布时间】:2015-06-18 11:44:05 【问题描述】:我正在努力创建一个分区值的运行/累积median
,按时间顺序排列。基本上我有一张桌子:
create table "SomeData"
(
ClientId INT,
SomeData DECIMAL(10,2),
SomeDate TIMESTAMP
);
有一些数据:
INSERT INTO "SomeData" (ClientId, SomeData, SomeDate) VALUES
(1, 1, '1 Jan 2000'),
(1, 2, '2 Jan 2000'),
(1, 3, '3 Jan 2000'),
(1, 4, '4 Jan 2000'),
(2, 100, '1 Jan 2000'),
(2, 100, '2 Jan 2000'),
(2, 100, '3 Jan 2000'),
(2, 200, '4 Jan 2000'),
(2, 200, '5 Jan 2000'),
(2, 200, '6 Jan 2000'),
(2, 200, '7 Jan 2000');
我需要一个按ClientId
划分的运行中位数,按SomeDate
排序。
基本上,我需要制作的是这样的:
ClientId SomeDate Median of SomeData
1 "2000-01-01" 1.000
1 "2000-01-02" 1.500
1 "2000-01-03" 2.000
1 "2000-01-04" 2.500
2 "2000-01-01" 100.0
2 "2000-01-02" 100.0
2 "2000-01-03" 100.0
2 "2000-01-04" 100.0
2 "2000-01-05" 100.0
2 "2000-01-06" 150.0
2 "2000-01-07" 200.0
我可以在 PostgresSql 9.x 中使用 Aggregate_median
function 以多种方式做到这一点,但事实证明这在 Redshift 中很困难,它只有一个聚合中位数
SELECT ClientId, SomeDate, median(SomeData) OVER (PARTITION BY ClientId ORDER BY SomeDate)
FROM "SomeData" xout
ORDER BY ClientId, SomeDate;
在 Redshift 上运行上述程序会出现错误:
错误:窗口规范不应包含窗口函数中位数的框架子句和排序
中值可以用手动关联子查询替换回原始表,但是 RedShift 似乎也不支持这些。
错误:由于内部错误,不支持这种类型的关联子查询模式
Here are a bunch of fiddles 在 PostGres 中工作,在 Redshift 中都不工作
此时,我似乎需要将数据拉入内存和do this in code,但如果可以直接在 Redshift 中完成,将不胜感激。
【问题讨论】:
【参考方案1】:我想知道你是否可以使用nth_value()
:
SELECT ClientId, SomeDate,
NTH_VALUE(seqnum / 2) OVER (PARTITION BY ClientId ORDER BY SomeDate)
FROM (SELECT s.*,
COUNT(*) OVER (PARTITION BY ClientId ORDER BY SomeDate) as seqnum
FROM SomeData s
) s
ORDER BY ClientId, SomeDate;
注意:使用COUNT(*)
而不是ROW_NUMBER()
需要一些时间来适应。
【讨论】:
感谢 Gordon - 我需要进行一些调整,但能够获得 this working here。显然,这是偶数数据的轻微近似值,即应该对最中间的 2 个点进行插值,但这肯定足以满足 99% 的应用程序。 @StuartLC。 . .修改偶数很容易。它只是使查询本身变得更加复杂。很酷,这在 Red Shift 中有效。让我想知道为什么他们实现了这个功能,而不是median()
使用窗口子句。
@GordonLinoff 实际上,我很想看到偶数的修改,因为当 seqnum = 1 时 Redshift 不会评估 nth_value( seqnum/2::INT ),即使有一个 case 语句阻止case seqnum = 1.
@Korbonits 。 . .你能再问一个问题吗?这需要一些思考。
@StuartLC 请参阅下面的非近似实现:) ***.com/a/36636914/3320944【参考方案2】:
我认为@GordonLinoff 提出的解决方案是不正确的,因为它没有按照您尝试查找中位数的值对行进行排序。正确方法灵感来自:
Moving Median, Mode in T-SQL
适用于红移:
WITH CTE
AS
(
SELECT ClientId,
ROW_NUMBER() OVER (PARTITION BY ClientId ORDER BY SomeDate ASC) row_num,
SomeDate,
SomeData
FROM "SomeData"
)
SELECT A.SomeDate,
A.SomeData,
(SELECT MEDIAN(B.SomeData)
FROM CTE B
WHERE B.row_num BETWEEN 1 AND A.row_num
GROUP BY A.ClientId) AS median
FROM CTE A
【讨论】:
这应该是 IMO 的最高评论。【参考方案3】:这是对您要查找的数量的精确计算。
本身并不性感,但它可以正确处理奇数和偶数长度的中位数。
with row_numbers as (
SELECT d.partitionField -- the field (or fields) you are partitioning the window function by
, d.orderField -- your sort field for the window functions
, d.medianField -- quantity your are computing the median of
, ROW_NUMBER()
OVER (PARTITION BY partitionField ORDER BY orderField) as seqnum
FROM data d
)
, medians as (
SELECT nth_value(medianField, CASE
WHEN mod(seqnum, 2) = 0 THEN (seqnum/2)::int
ELSE ((seqnum/2)::int + 1)
END)
OVER (PARTITION BY partitionField ORDER BY orderField ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as median1
, nth_value(medianField, (seqnum/2)::int + 1) OVER (PARTITION BY partitionField ORDER BY orderField ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as median2
, mod(seqnum, 2) as mod1
FROM row_numbers
ORDER BY partitionField, orderField
)
select CASE
when mod(mod1,2) = 0
then ((median1 + median2)/2)::FLOAT
else median1
end as median
from medians
【讨论】:
以上是关于如何在 Redshift 中实现窗口运行中位数?的主要内容,如果未能解决你的问题,请参考以下文章