使用RowNumber和分区

时间:2017-09-13 17:20:57

标签: sql-server tsql

考虑以下代码:

Select U.[user_id] As UserID
  Max(AL.entry_dt) As LastLoginDate
From Users U with (nolock)
  Inner Join activity_log AL with (nolock) On AL.[user_id] = U.[user_id] 
                                              And AL.activity_type = 'LOGIN'
                                              And U.external_user = 1
Group By U.[user_id]
Having Max(al.entry_dt) < GetDate() - 30
Order By U.[user_id]

我很好奇是否可以在这里使用Row_Number / Partition?也许是为了让它更有效,或者它是否可以使用?

基本上,我希望每个用户使用最后一个用户登录的最后一个实例,其中用户在过去30天内没有登录

带来痛苦.....

3 个答案:

答案 0 :(得分:3)

要在row_number()子句中使用where的结果,请将查询包装在子查询/派生表或common table expression中:

过去30天内登录的用户的原始答案:

select UserId, LastLoginDate 
from (
    Select 
        U.[user_id] As UserID
      , AL.entry_dt As LastLoginDate
      , rn = row_number() over(partition by u.user_id order by AL.entry_dt desc)
    From Users U with (nolock)
      Inner Join activity_log AL with (nolock) 
        On AL.[user_id] = U.[user_id] 
        And AL.activity_type = 'LOGIN'
        And U.external_user = 1
    Where AL.entry_dt > GetDate() - 30 -- swapped < for >
  ) sub
where rn = 1
Order By sub.[userid]

rextester演示:http://rextester.com/XZU40394

返回:

+--------+---------------+
| UserId | LastLoginDate |
+--------+---------------+
|      1 | 2017-09-13    |
|      2 | 2017-09-10    |
|      3 | 2017-09-07    |
+--------+---------------+

为过去30天内未登录的用户更新了答案:

select UserId, LastLoginDate 
from (
    Select 
        U.[user_id] As UserID
      , AL.entry_dt As LastLoginDate
      , rn = row_number() over(partition by u.user_id order by AL.entry_dt desc)
    From Users U with (nolock)
      Inner Join activity_log AL with (nolock) 
        On AL.[user_id] = U.[user_id] 
        And AL.activity_type = 'LOGIN'
        And U.external_user = 1
  ) sub
where rn = 1
  and lastlogindate < getdate() - 30
Order By [userid]

rextester演示:http://rextester.com/XZU40394

返回:

+--------+---------------+
| UserId | LastLoginDate |
+--------+---------------+
|      4 | 2016-09-13    |
|      6 | 2016-09-10    |
+--------+---------------+

来自测试设置:

create table users (user_id int, external_user bit)
create table activity_log (user_id int, activity_type varchar(32), entry_dt date)

insert into users values (1,1),(2,1),(3,1),(4,1),(5,0),(6,1)
insert into activity_log values 
 (1,'login','20170913') ,(1,'login','20170912') ,(1,'login','20170911'),(1,'login','20160908')  
,(2,'login','20170910') ,(2,'login','20170909') ,(2,'login','20170908')
,(3,'login','20170907') ,(3,'login','20170906') ,(3,'login','20170905') 
,(4,'login','20160913') ,(4,'login','20160912') ,(4,'login','20160908') 
,(5,'login','20160910') ,(5,'login','20160909') ,(5,'login','20160908') 
,(6,'login','20160910') ,(6,'login','20160909') ,(6,'login','20160908') 

要更正问题中的查询,请将您的where移至having,如下所示:

Select U.[user_id] As UserID
  ,Max(AL.entry_dt) As LastLoginDate
From Users U with (nolock)
  Inner Join activity_log AL with (nolock) On AL.[user_id] = U.[user_id] 
                                              And AL.activity_type = 'LOGIN'
                                              And U.external_user = 1
Group By U.[user_id]
having max(al.entry_dt) < GetDate() - 30
Order By U.[user_id]

答案 1 :(得分:2)

CROSS APPLY或OUTER APPLY允许您从相关查询中为相关表中的每条记录返回n条记录。我认为交叉应用是您想要的,因为如果用户在过去30天内没有登录,您根本不想在结果中看到它们。交叉应用类似于内连接,但为每个记录相关表运行相关查询。 OUTER类似于OUTER join应用,因此它返回相关表中的所有记录,只返回相关查询中匹配的记录。

因此,在下面的示例中,对于每个用户,按entry_dT的降序返回前1条记录。为每个相关用户。外部应用类似于左连接,因此即使没有活动发生,也会返回所有用户。

修改后的演示:http://rextester.com/UQEI69366(以下全部3)再次向SQLZim索取测试人员/数据

SELECT U.[user_id] As UserID
     , AL.entry_dt As LastLoginDate
FROM Users U with (nolock)
CROSS APPLY (SELECT top 1 * 
             FROM activity_log IAL 
             WHERE U.User_ID = IAL.User_ID
               AND IAL.activity_type = 'LOGIN'

             ORDER BY IAL.entry_DT Desc) AL
WHERE U.external_user = 1
  AND IAL.entry_dt < GetDate() - 30
ORDER BY U.[user_id]

如果你所追求的是过去30天内没有登录的用户...... 一个简单的不存在似乎它会起作用。如果有的话,谁会关心约会时间;您只是在30天内未登录的用户列表之后。

SELECT U.[user_id] As UserID
FROM Users U
WHERE not exists (SELECT *
                  FROM activity_log IAL 
                  WHERE IAL.activity_type = 'LOGIN'
                    AND IAL.entry_dt > GetDate() - 30
                    AND IAL.[user_id] = U.[user_id])

  AND U.external_user = 1
ORDER BY U.[user_id]

一个简单的左连接也可以正常工作(返回所有从现在起30天内没有登录的外部用户。

SELECT U.[user_id] As UserID
FROM Users U with (nolock)
LEFT JOIN activity_log AL 
  ON AL.[user_id] = U.[user_id] 
 AND AL.activity_type = 'LOGIN'
 AND AL.entry_dt > GetDate() - 30
WHERE U.external_user = 1
  and AL.user_ID is null
ORDER BY U.[user_id]

答案 2 :(得分:1)

  

我很好奇是否可以在这里使用Row_Number / Partition?也许是为了使其更有效,或者是否可以使用它?

我更倾向于在你的情况下使用group by而不是行号,因为行号需要额外的索引而不是group by。阅读以下内容了解更多

假设您使用的是您发布的相同查询,下面是所需的索引

for users table ..

create index nci_test on 
dbo.usertable(userid,external_login)

对于活动日志表,您需要了解有关数据的更多信息..

<强>实施例
如果连接过滤掉比其他更多的行,则索引可以是

create index nci_test1 on 
    dbo.actvititlog(userid,entry_Dt,activity_type )

如果entry_dt列过滤掉更多行,那么前导列可以是上面索引中的entry_Dt

如果你使用RowNumber,它将需要一个POC索引,你的查询分布在两个表中,所以这不能完成