【问题标题】:How to perform a running count on null values that resets on non-null values如何对在非空值上重置的空值执行运行计数
【发布时间】:2025-12-20 23:20:08
【问题描述】:

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

我目前正在尝试在这个版本的 redshift 上实现这一点:

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

我尝试使用这个窗口函数,但这只是不断增加每个 null 的数字。

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

【问题讨论】:

  • 你完整的 SQL 是什么?
  • 这是模拟数据吗?您提供的数据没有为运行 sum 提供足够的不同值来生成输出,您只能得到带有 id、val 组合的运行 sum 和上述示例数据
  • 这确实是模型数据。我的数据集中确实有其他字段,但它们都是交易数据,我不知道它们是否可以用作分区,即 LTV、支出速度等。
  • 我玩了一下,得出了 id、val 组合的运行总和。总和列像这样 1,2,3,0,4,5,0,1,0,2... 因为 IMO 无法使用上述输入创建输出。使用sum(case when val = 'NULL' then 1 else 0 end) over (partition by id,val order by id,val,date rows between unbounded preceding and current row) as foo
  • 很高兴知道这一点。要使上述输出成为可能,还需要什么额外的东西?

标签: sql count amazon-redshift gaps-and-islands date-arithmetic


【解决方案1】:

初学者

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

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 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   |

【讨论】:

    最近更新 更多