如何对非空值重置的空值执行 运行 计数

How to perform a running count on null values that resets on non-null values

我正在尝试计算执行连续空值 运行 计数的列,但 运行 计数将在非空值时重置。

我目前正在尝试在此版本的 redshift 上实现此目的:

PostgreSQL 8.0.2 on i686-pc-linux-gnu,由 GCC gcc (GCC) 3.4.2 20041017 (Red Hat 3.4.2-6.fc3)、Redshift 1.0.8187 编译

我尝试使用此 window 函数,但这只是不断增加每个空值的数字。

ROW_NUMBER() OVER (PARTITION BY ID, VAL ORDER BY VAL ROWS UNBOUNDED PRECEDING)

例如,如果我有这样的数据集:

id  | date  | val
----+-------+-------
  1 |   1/1 | NULL
  1 |   1/2 | NULL 
  1 |   1/3 | NULL 
  1 |   1/4 |  1
  1 |   1/5 | NULL 
  1 |   1/6 | NULL 
  1 |   1/7 |  1 
  2 |   1/8 |  2
  2 |   1/9 | NULL
  2 |   1/1 | NULL
  2 |   1/2 |  1
  2 |   1/3 | NULL
  2 |   1/4 |  0
  2 |   1/5 | NULL
  2 |   1/6 | NULL  

我希望输出如下所示:

id  | date  | val   | foo
----+-------+-------+-------
  1 |   1/1 | NULL  |  1
  1 |   1/2 | NULL  |  2
  1 |   1/3 | NULL  |  3
  1 |   1/4 |  1    |
  1 |   1/5 | NULL  |  1 
  1 |   1/6 | NULL  |  2
  1 |   1/7 |  1    |
  2 |   1/8 |  2    |
  2 |   1/9 | NULL  |  1
  2 |   1/1 | NULL  |  2
  2 |   1/2 |  1    |
  2 |   1/3 | NULL  |  1
  2 |   1/4 |  0    |
  2 |   1/5 | NULL  |  1
  2 |   1/6 | NULL  |  2

初学者

我认为您的样本数据存在问题,在下面突出显示的记录中:

id  | date  | val   | foo
----+-------+-------+-------
  1 |   1/1 | NULL  |  1
  1 |   1/2 | NULL  |  2
  1 |   1/3 | NULL  |  3
  1 |   1/4 |  1    |
  1 |   1/5 | NULL  |  1 
  1 |   1/6 | NULL  |  2
  1 |   1/7 |  1    |
  2 |   1/8 |  2    |       --> this record is not in sequence
  2 |   1/9 | NULL  |  1    --> neither this one
  2 |   1/1 | NULL  |  2    --> so this record should have foo = 1, not 2
  2 |   1/2 |  1    |
  2 |   1/3 | NULL  |  1
  2 |   1/4 |  0    |
  2 |   1/5 | NULL  |  1
  2 |   1/6 | NULL  |  2

我只是从数据集中删除了这三个记录。如果您对此不满意,请不要继续阅读...


回答

这是间隙和孤岛问题的变体。为了解决这个问题,想法是建立由连续的空记录组成的组。为此,我们在两个不同的分区上计算 row_number()s(id vs id and null/not null val).行号之间的差异定义了组。

然后,剩下要做的就是为每个记录分配新的行号,该记录在其所属的组中具有空 val

查询:

select 
    id,
    date,
    val,
    case when val is null
        then row_number() over(partition by id, rn1 - rn2 order by date) 
        else null
    end foo
from (
    select
        t.*,
        row_number() 
            over(order by id, date) rn1,
        row_number() 
            over(partition by id, case when val is null then 1 else 0 end order by date ) rn2
    from mytable t
) t
order by id, date   

Demo on DB Fiddle:

| id  | date                     | val | foo |
| --- | ------------------------ | --- | --- |
| 1   | 2019-01-01T00:00:00.000Z |     | 1   |
| 1   | 2019-01-02T00:00:00.000Z |     | 2   |
| 1   | 2019-01-03T00:00:00.000Z |     | 3   |
| 1   | 2019-01-04T00:00:00.000Z | 1   |     |
| 1   | 2019-01-05T00:00:00.000Z |     | 1   |
| 1   | 2019-01-06T00:00:00.000Z |     | 2   |
| 1   | 2019-01-07T00:00:00.000Z | 1   |     |
| 2   | 2019-01-02T00:00:00.000Z | 1   |     |
| 2   | 2019-01-03T00:00:00.000Z |     | 1   |
| 2   | 2019-01-04T00:00:00.000Z | 0   |     |
| 2   | 2019-01-05T00:00:00.000Z |     | 1   |
| 2   | 2019-01-06T00:00:00.000Z |     | 2   |