【问题标题】:T-SQL - Check if ID appears in a date or in a later dateT-SQL - 检查 ID 是否出现在日期或以后的日期
【发布时间】:2017-08-08 19:09:13
【问题描述】:

我有两张表,一张看起来像这样,用于衡量用户何时加入:

 userId        Date     
 ----      -------------  
   1        01-01-2013       
   2        01-01-2013       
   3        01-01-2013       
   4        01-01-2013       
   5        01-01-2013 ....

另一个看起来像这样的事件与用户相关:

  id    userID    event        Date     
 ----  -----     ---------    ------
   1    1        open         01-01-2013       
   2    2        close        01-02-2013       
   3    2        move         01-03-2013       
   4    3        open         01-04-2013       
   5    5        move         01-05-2013 ....    

我想获得另一个表格,给定一个日期,它会获得在该日期加入的用户中有多少在下一个连续日期或以后的日期发生了事件,并按这些连续日期对它们进行分组。

例如,在我的第一个表中,每个用户都加入了相同的日期,所以如果我查询那个日期,我想获得这个:

userCount        Date     
 ----      -------------  
   4        01-01-2013       
   3        01-02-2013       
   3        01-03-2013       
   2        01-04-2013       
   1        01-05-2013 ....

在第一行中,userCount 为 4,因为 userId: 1,2,3,5 出现在该日期或以后的日期,在下一行中 userCount 为 3,因为 userId: 2,3,5 出现在以后的日期,userId 1 不出现在那个日期或以后的日期,依此类推。

我如何查询才能获得这些结果?

我要衡量的是Unbounded retention 的用户。

【问题讨论】:

  • 没有一个例子是每个人都在同一天加入。
  • 你试过什么?示例数据将对这里有所帮助
  • 您的事件表中只有 4 个用户。那是错字吗?还是您仍然希望在某个时候恢复 5 个用户数?
  • 我刚刚编辑了描述。 @Xedni 你是对的,我有一个错字。

标签: sql sql-server tsql retention


【解决方案1】:

这就是我的处理方式。设置样本数据:

declare @JoinDate date = '01-01-2013'

declare @users table (UserId int primary key, JoinDate date)

insert into @Users
values
    (1, '01-01-2013'),
    (2, '01-01-2013'),
    (3, '01-01-2013'),
    (4, '01-01-2013'),
    (5, '01-01-2013')

declare @events table 
(
    Id int primary key,
    UserId int,
    EventName varchar(10),
    EventDate date
)

insert into @Events
values
    (1,1,'open','01-01-2013'),
    (2,2,'close','01-02-2013'),
    (3,2,'move','01-03-2013'),
    (4,3,'open','01-04-2013'),
    (5,5,'move','01-05-2013')

第一步是获取在指定日期加入的用户列表(在这种情况下,它恰好是每个人,但我假设这并不总是正确的)。然后将该结果加入到UserId 上的事件中。由于您只关心在加入日期或之后发生的事件,因此您可以将该条件包含在两者之间的联接中

select e.*
from @users u
inner join @Events e
    on u.UserId = e.UserId
        and e.EventDate >= @JoinDate
where u.JoinDate = @JoinDate

从那里,您可以在两个 EventDate 字段之间的不等式上自行加入该结果集。换句话说,如果某个日期有 4 个用户,则所有 4 个用户都将及时传播到该日期之后的任何行。通过执行count(distinct UserId),您可以得出当天或之后发生事件的不同用户数量。

;with src as
(
    select e.*
    from @users u
    inner join @Events e
        on u.UserId = e.UserId
            and e.EventDate >= @JoinDate
    where u.JoinDate = @JoinDate
)
select 
    UserCount = count(distinct b.UserId),
    EventDate = a.EventDate
from src a
inner join src b
    on a.EventDate <= b.EventDate
group by a.EventDate
order by UserCount desc

【讨论】:

    【解决方案2】:

    首先,由于我们正在处理日期,我将构建一个Calendar Table 表以使事情变得更容易:

    create table Calendar
    (
        id int primary key identity,
        [date] datetime,
        [day] as datepart(day, [date]) persisted,
        [month] as datepart(month, [date]) persisted,
        [year] as datepart(year, [date]) persisted,
    
        day_of_year as datepart(dayofyear, [date]) persisted,
        [week] as datepart(week, [date]),
    
        day_name as datename(dw, [date]),
    
        is_weekend as 
        case when datepart(dw, [date]) = 7 or datepart(dw, [date]) = 1 
        then 1 else 0 end,
    
        [quarter] as datepart(quarter, [date]) persisted        
    )
    
    go
    
    declare @date datetime
    set @date = '1-1-2010'
    
    while @date <= '12-31-2100'
    begin
    
        insert Calendar select @date
        set @date = dateadd(day, 1, @date)
    
    end
    

    因此,您的架构可能看起来像这样:

    create table Member
    (
        id int primary key identity,
        name nvarchar(100) mot null,
        joined int foreign key references Calendar not null
    )
    
    create table MemberActivityType
    (
        id int primary key identity,
        name varchar(50) not null
    )
    
    insert MemberActivityType 
        select 'Open' union
        select 'Close' union
        select 'Move'
    
    create table MemberActivity
    (
        id int primary key identity,
        member_id int foreign key references Member,
        activity_id int foreign key references MemberActivityType,
        [date] int foreign key references Calendar not null
    )
    

    现在,当您在查询中需要“if-then”时,Apply 运算符可能是您应该考虑的第一个工具。它具有以下形式:

     select * from Q [outer|cross] apply P(q1...qn)
    

    其中P 和Q 是关系(表),q1..qn 是Q 的属性(列),P(q1..qn) 表示对 P 的查询,该查询关于某些 q1...qn 的值Q 的行。这意味着 P(q1..qn) 会针对来自 Q 的每个结果进行评估

    因此,此函数将产生您想要的结果:

    create function UnboundedRetention
    (
        @join_date int
    )
    returns @results table(users int, date datetime)
    begin       
    
    with Q as
    (select K.member_id, K.date anchor from MemberActivity K outer apply
    
            (select J.member_id, J.date from MemberActivity J 
                where J.date = K.date + 1
                and J.member_id = K.id) L                
    
            outer apply
                    (
                        select A.date from MemberActivity A
                        where A.date > L.date and A.member_id = L.member_id
                    ) M
    
            where K.date >=  @join_date)
    
        insert @results
    
            select L.c, C.date from Calendar C inner join Q on Q.anchor = C.id
    
        cross apply
            (
                select count(M.id) c from Member M where M.id in (select member_id from Q K where K.anchor >= C.id)
            ) L
    
        return
    end
    

    在这种情况下,Q 是一个公用表表达式,意思是“对于 @join_date 或之后的每个日期,其中成员做了某事 (K),返回 立即 以下日期,其中该成员做了某事(L),并且对于L 中的每个日期,返回该成员在其中做了某事(M) 的所有后续日期。

    然后,对于Q 中的每个日期,我们返回在该日期 Q` 当天或之后做了某事的所有成员的计数。

    请自行测试solution;它被封装在一个未提交的事务中以使其变得容易。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-24
      • 1970-01-01
      • 2017-01-28
      • 2017-09-13
      相关资源
      最近更新 更多