【问题标题】:SQL Pivoting based on previous data基于先前数据的 SQL 透视
【发布时间】:2025-11-25 07:20:05
【问题描述】:

这是我的第一个问题,说实话,在发布此问题之前我没有进行任何搜索,因为我不知道该主题将归类为什么。

这是一个与 SQL 相关的问题,我猜想 Pivot 数据以获得更友好的数据输出。

我有一个 4 列的表,分别是:ID、用户名、状态、日期时间。 状态是决定用户操作的因素,例如登录和注销。

ID  Username  Status  DateTime  
1   A         0       2017-10-20 05:00:00  
2   A         0       2017-10-20 07:23:10  
3   B         0       2017-10-20 07:24:45  
4   A         1       2017-10-20 09:50:55  
5   A         0       2017-10-20 13:00:56  
6   B         1       2017-10-20 17:13:28  
7   B         0       2017-10-20 17:50:47  
8   A         1       2017-10-20 21:38:17  
9   A         0       2017-10-20 21:38:19  
10  B         1       2017-10-20 21:40:02

我需要将 Status0 和 Status1 过滤为登录和注销,因此两者之间的任何 Status0 都将被忽略

ID  Username  Status  DateTime  
1   A         0       2017-10-20 05:00:00  
2   A         0       2017-10-20 07:23:10  
4   A         1       2017-10-20 09:50:55 

会导致

Username  Status0              Status1
A         2017-10-20 05:00:00  2017-10-20 09:50:55

下一个“A”将搜索大于或等于最后一个 Status1 (2017-10-20 09:50:55) 的 Status0 的 DateTime,依此类推,直到数据结束

我需要的最终数据格式如下:

Username  Status0              Status1
A         2017-10-20 05:00:00  2017-10-20 09:50:55
B         2017-10-20 07:24:45  2017-10-20 17:13:28 
A         2017-10-20 13:00:56  2017-10-20 21:38:17  
B         2017-10-20 17:50:47  2017-10-20 21:40:02
A         2017-10-20 21:38:19  null  

我怎样才能达到这个结果?我的逻辑说我需要递归比较Status1并找到下一个Status0,但我不确定如何将它放入sql查询中。

感谢任何帮助。之前谢谢。

编辑:我使用的是 SQL Server 2008。

【问题讨论】:

    标签: sql sql-server database sql-server-2008-r2 pivot-table


    【解决方案1】:

    嗯。这是一种方法:查找下一个状态1(使用apply)然后聚合:

    select username,
           min(datetime) as status_0_datetime,
           status_1_datetime
    from (select t.*, t2.datetime as status_1_datetime
          from t outer apply
               (select top 1 t2.*
                from t t2
                where t2.username = t.username and t2.status = 1 and
                      t2.datetime > t.datetime
                order by t2.datetime desc
               ) t2
          where t.status = 0
         ) t
    group by username, status_1_datetime
    order by username, min(datetime);
    

    这将为每个状态 1 日期时间提供一行。

    【讨论】:

    • 不太对劲dbfiddle.uk/…
    • 特定日期可能没有状态 1 数据,所以它不是我要找的。​​span>
    【解决方案2】:

    尝试以下方法。您使用 LAG 删除用户状态中的重复 0,然后使用 ROW_NUMBER 对用户的登录/注销进行适当的分组。

    select tt.username,
        MAX(CASE WHEN status = 0 THEN tt.datetime END) status0,
        MAX(CASE WHEN status = 1 THEN tt.datetime END) status1
    from
    (
        select *,
            row_number() over (partition by username, status order by datetime) rn
        from
        (
          select *, lag(status) over (partition by username order by datetime) prevstatus
          from your_table
        ) t
        where not(t.status = 0 and t.prevstatus = 0)  or t.prevstatus is null -- this is to remove repeating 0
    ) tt
    group by tt.username, tt.rn
    

    demo

    编辑: 好的,所以解决方案应该是针对 SQL Server 2008 R2,因此,没有LAG。然后可以使用NOT EXISTS 解决它,但是它的可读性不是很好:

    select tt.username,
        MAX(CASE WHEN status = 0 THEN tt.datetime END) status0,
        MAX(CASE WHEN status = 1 THEN tt.datetime END) status1
    from
    (
        select *,
            row_number() over (partition by username, status order by datetime) rn
        from
        (
          select *
          from your_table yt1
          where status = 1 or
                not exists(
                  select 1
                  from your_table yt2
                  where yt2.status = 0 and 
                        yt2.username = yt1.username and
                        yt2.datetime = (
                          select max(yt3.datetime)
                          from your_table yt3
                          where yt3.datetime < yt1.datetime and
                                yt3.username = yt1.username
                        )
                )
        ) t
    ) tt
    group by tt.username, tt.rn
    

    demo

    好的,最后一个版本使用外连接和GROUP BY,而不是依赖子查询。因此,在某些情况下它可能会更有效

    select tt.username,
        MAX(CASE WHEN status = 0 THEN tt.datetime END) status0,
        MAX(CASE WHEN status = 1 THEN tt.datetime END) status1
    from
    (
        select *,
            row_number() over (partition by username, status order by datetime) rn
        from
        (
            select xt.*, yt.status joinstatus
            from your_table yt
            right join (
                select yt1.id, yt1.datetime, yt1.username, yt1.status, max(yt2.datetime) prevdatetime
                from your_table yt1
                left join your_table yt2 on yt1.datetime > yt2.datetime and 
                                       yt2.username = yt1.username and
                                       yt1.status = 0 
                group by yt1.id, yt1.datetime, yt1.username, yt1.status
            ) xt on yt.datetime = xt.prevdatetime and yt.username = xt.username and xt.status = yt.status
        ) t
        where t.joinstatus is null
    ) tt
    group by tt.username, tt.rn
    

    【讨论】:

    • 演示中看起来很有希望,但我收到了关于 LAG 的错误,它说它不是一个公认的内置函数名...
    • 对不起。我发现LAG 是 SQL2012 及更高版本的函数。我使用的是 SQL Server 2008 R2,你能建议这个LAG 的替代方法吗?