在Greenplum滚动(移动)中位数

时间:2017-08-29 15:48:54

标签: sql window-functions median greenplum

我想计算Greenplum中一列的滚动中位数,即如下:

|  x | rolling_median_x |
| -- + ---------------- |
|  4 |                4 |
|  1 |              2.5 |
|  3 |                3 |
|  2 |              2.5 |
|  1 |                2 |
|  6 |              2.5 |
|  9 |                3 |

x是一个整数,每行rolling_median_x显示当前行和前一行的x的中位数。例如。第三行rolling_median_x = median(4, 1, 3) = 3

到目前为止我发现的事情:

  • median函数不能用作框架窗函数,即median(x) OVER(RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
  • 对于percent_ranknth_value
  • 等许多其他功能也是如此
  • 此版本的Greenplum
  • 不支持递归自联接

事实上,我无法找到关于哪些功能可以用作Greenplum中的框架窗口功能的适当文档......

我正在使用Greenplum 4.3.4.0(基于Postgres 8.2.15),不幸的是,更新不是一个选项。

2 个答案:

答案 0 :(得分:1)

一句话 - 来自维基百科的引用:ORDER BY

  

ORDER BY是对结果集中的行进行排序的唯一方法。没有   在这个子句中,关系数据库系统可以返回任何行   订单。如果需要订购,则必须提供ORDER BY   应用程序发送的SELECT语句。虽然有些数据库   系统允许在子选择或中指定ORDER BY子句   查看定义,存在没有效果。一个观点是一个   逻辑关系表,关系模型要求a   table是一组行,暗示没有任何排序顺序。

由于您需要计算当前和前一行的中位数,因此您必须在表格中添加一行,以定义行的顺序,并且可以使用确定哪一行在给定行之前,哪些行在之后 让我们说一些id列如下:

| id | x | rolling_median_x |
|----|---|------------------|
|  1 | 4 |                4 |
|  2 | 1 |              2.5 |
|  3 | 3 |                3 |
|  4 | 2 |              2.5 |
|  5 | 1 |                2 |
|  6 | 6 |              2.5 |
|  7 | 9 |                3 |

如果您不能使用分析函数,请尝试纯SQL This article显示了使用SQL计算中位数的各种方法 我认为亨德森的中位数最符合我们的需求:

SELECT CASE COUNT(*) % 2
       WHEN 0        -- even sized table
       THEN (P1.part_wgt + MIN(CASE WHEN P2.part_wgt > P1.part_wgt
                                  THEN P2.part_wgt
                                  ELSE NULL END))/2.0
       ELSE P1.part_wgt --odd sized table
       END AS median 
  FROM Parts AS P1, Parts AS P2
 GROUP BY P1.part_wgt
HAVING COUNT(CASE WHEN P1.part_wgt >= P2.part_wgt
                  THEN 1
                  ELSE NULL END)
       = (COUNT(*) + 1) / 2;

只需将每一行的查询作为依赖子查询运行,一般的想法是这样的:

SELECT t.*, (
        SELECT .... Henderson's query FROM table x
        WHERE x.id <= t.id
        ......
       ) As our_median
FROM table t

您可以在this demo

中找到示例实现
SELECT t.*, (
    SELECT CASE COUNT(*) % 2
           WHEN 0        -- even sized table
           THEN (P1.x + MIN(CASE WHEN P2.x > P1.x
                                      THEN P2.x
                                      ELSE NULL END))/2.0
           ELSE P1.x --odd sized table
           END AS median 
      FROM Table333 AS P1, Table333 AS P2
      WHERE p1.id <= t.id AND p2.id <= t.id
     GROUP BY P1.x
    HAVING COUNT(CASE WHEN P1.x >= P2.x
                      THEN 1
                      ELSE NULL END)
           = (COUNT(*) + 1) / 2
    ) as Our_median
FROM Table333 t;

| id | x | rolling_median_x | our_median |
|----|---|------------------|------------|
|  1 | 4 |                4 |          4 |
|  2 | 1 |              2.5 |        2.5 |
|  3 | 3 |                3 |          3 |
|  4 | 2 |              2.5 |        2.5 |
|  5 | 1 |                2 |          2 |
|  6 | 6 |              2.5 |        2.5 |
|  7 | 9 |                3 |          3 |

此查询可能会很慢 - 这是您必须为使用古老版本的PostgreSQL付出的代价

答案 1 :(得分:0)

  

我正在使用psql 8.2.15并且遗憾的是更新不是一个选项。

哎哟。

如果是滚动平均值,事情会很简单,但由于需要排序,滚动中位数会很慢。避免这种情况的方法是将值插入到堆或btree中,这样可以获得滚动中值而无需对每个新值进行排序。但这需要自定义代码。

我会用plpython来实现这个:

Rolling median algorithm in C