【问题标题】:Average over hard to define partition平均超过难以定义的分区
【发布时间】:2012-10-25 14:59:53
【问题描述】:

我有这张桌子:

create table t (value int, dt date);

 value |     dt     
-------+------------
    10 | 2012-10-30
    15 | 2012-10-29
  null | 2012-10-28
  null | 2012-10-27
     7 | 2012-10-26

我想要这个输出:

 value |     dt     
-------+------------
    10 | 2012-10-30
     5 | 2012-10-29
     5 | 2012-10-28
     5 | 2012-10-27
     7 | 2012-10-26

当表格按日期降序排序时,我希望将空值以及前一个非空值替换为前一个非空值的平均值。在此示例中,值 15 是接下来两个空值的前一个非空值。所以 15 / 3 = 5。

SQL Fiddle

【问题讨论】:

  • +1 非常好的问题。它拥有它需要的一切——好吧,我从小提琴中推断出 PostgreSQL 9.2。

标签: sql postgresql null aggregate-functions window-functions


【解决方案1】:

我发现了一个非常简单的解决方案:

SELECT max(value) OVER (PARTITION BY grp)
      / count(*)  OVER (PARTITION BY grp) AS value
      ,dt
FROM   (
   SELECT *, count(value) OVER (ORDER BY dt DESC) AS grp
   FROM   t
   ) a;

-> sqlfiddle

由于count() 忽略NULL 值,您可以使用运行计数(窗口函数中的默认值)快速分组值(-> grp)。

每个组都有 一个 非空值,因此我们可以使用 min / max / sum 在另一个窗口函数中得到相同的结果。除以grp 中的成员数量(这次是count(*),计算NULL 的值!),我们就完成了。

【讨论】:

  • 不错,但似乎是 PostgreSQL 特有的。
  • @jsalvata:“但是”?你注意到 [PostgreSQL] 标签了吗?此外,这是标准 SQL。 -> sqlfiddle for SQL server with identical query.
  • 不,我没有。蹩脚的 mySQL 不支持它。是的,这是标准的。
【解决方案2】:

作为一个难题,这是一个解决方案...实际上,根据您的数据性质,它可能会表现得很糟糕。无论如何都要注意你的索引:

create database tmp;
create table t (value float, dt date); -- if you use int, you need to care about rounding
insert into t values (10, '2012-10-30'), (15, '2012-10-29'), (null, '2012-10-28'), (null, '2012-10-27'), (7, '2012-10-26');

select t1.dt, t1.value, t2.dt, t2.value, count(*) cnt 
from t t1, t t2, t t3 
where 
    t2.dt >= t1.dt and t2.value is not null 
    and not exists (
        select * 
        from t 
        where t.dt < t2.dt and t.dt >= t1.dt and t.value is not null
    ) 
    and t3.dt <= t2.dt 
    and not exists (
        select * 
        from t where t.dt >= t3.dt and t.dt < t2.dt and t.value is not null
    ) 
group by t1.dt;

+------------+-------+------------+-------+-----+
| dt         | value | dt         | value | cnt |
+------------+-------+------------+-------+-----+
| 2012-10-26 |     7 | 2012-10-26 |     7 |   1 |
| 2012-10-27 |  NULL | 2012-10-29 |    15 |   3 |
| 2012-10-28 |  NULL | 2012-10-29 |    15 |   3 |
| 2012-10-29 |    15 | 2012-10-29 |    15 |   3 |
| 2012-10-30 |    10 | 2012-10-30 |    10 |   1 |
+------------+-------+------------+-------+-----+
5 rows in set (0.00 sec)

select dt, value/cnt 
from (
    select t1.dt , t2.value, count(*) cnt 
    from t t1, t t2, t t3 
    where 
        t2.dt >= t1.dt and t2.value is not null 
        and not exists (
            select * 
            from t 
            where t.dt < t2.dt and t.dt >= t1.dt and t.value is not null
        ) 
    and t3.dt <= t2.dt 
    and not exists (
        select * 
        from t 
        where t.dt >= t3.dt and t.dt < t2.dt and t.value is not null
    ) 
    group by t1.dt
) x;

+------------+-----------+
| dt         | value/cnt |
+------------+-----------+
| 2012-10-26 |         7 |
| 2012-10-27 |         5 |
| 2012-10-28 |         5 |
| 2012-10-29 |         5 |
| 2012-10-30 |        10 |
+------------+-----------+
5 rows in set (0.00 sec)

解释:

  • t1 是原始表
  • t2 是表中日期最小且非空值的行
  • t3 都是中间的行,所以我们可以按其他分组并计数

抱歉,我说的再清楚不过了。我也很困惑:-)

【讨论】:

  • 如果解释起来太复杂,很可能,就是太复杂了。 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多