SQL根据以前的数据进行透视

时间:2017-10-26 11:06:27

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

这是我的第一个问题,如果我在发布此问题之前没有进行任何搜索,因为我不知道这个主题会被归类为什么。

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

我有一个4列表,分别是:ID,Username,Status,DateTime。 状态是决定用户操作的因素,例如登录和注销。

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'将搜索Status0的DateTime,该DateTime大于或等于上一个Status1(2017-10-20 09:50:55),依此类推,直到数据结束

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

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。

2 个答案:

答案 0 :(得分:0)

嗯。这是一种方法:查找下一个状态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日期时间提供一行。

答案 1 :(得分:0)

尝试以下方法。您使用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