T-SQL PARTITION BY 和 DISTINCT 的高效使用

T-SQL Efficient use of PARTITION BY and DISTINCT

我有以下 table 来监视用户登录应用程序:

CREATE TABLE [dbo].[userActivity](
    [userType] [nchar](10) NULL,
    [userInstanceID] [nchar](10) NULL,
    [userID] [nchar](10) NULL,
    [login] [datetime2](7) NULL
) ON [PRIMARY]
GO

我的数据的一个特殊性是,唯一用户是由 userTypeuserInstanceIDuserID 的组合确定的。

例如,在下图中,我有三个不同的用户:

  1. 客户 1 1(红色)
  2. 员工 1 2(蓝色)
  3. 客户 2 1(绿色)

我的objective是知道:

  1. 每个用户登录的次数
  2. 最近登录
  3. 最早的登录

我有一些测试数据:

INSERT [dbo].[userActivity] ([userType], [userInstanceID], [userID], [login]) VALUES (N'customer  ', N'1         ', N'1         ', CAST(N'2020-09-17T18:00:07.2492412' AS DateTime2))
GO
INSERT [dbo].[userActivity] ([userType], [userInstanceID], [userID], [login]) VALUES (N'employee  ', N'1         ', N'2         ', CAST(N'2020-09-18T09:00:07.2494560' AS DateTime2))
GO
INSERT [dbo].[userActivity] ([userType], [userInstanceID], [userID], [login]) VALUES (N'customer  ', N'1         ', N'1         ', CAST(N'2020-08-17T03:00:07.2492412' AS DateTime2))
GO
INSERT [dbo].[userActivity] ([userType], [userInstanceID], [userID], [login]) VALUES (N'customer  ', N'2         ', N'1         ', CAST(N'2020-07-23T10:00:07.2492412' AS DateTime2))
GO
INSERT [dbo].[userActivity] ([userType], [userInstanceID], [userID], [login]) VALUES (N'customer  ', N'2         ', N'1         ', CAST(N'2020-10-25T11:00:07.2492412' AS DateTime2))
GO

我能够通过以下方式获得我需要的东西:

SELECT DISTINCT userType, userInstanceID, userID, numberOfLogins, MostRecentLogin, oldestLogin FROM (
    SELECT userType, userInstanceID, userID, 
        COUNT(login) OVER(PARTITION BY userType, userInstanceID, userID ORDER BY userType, userInstanceID, userID) AS numberOfLogins,
        max(login) OVER(PARTITION BY userType, userInstanceID, userID ORDER BY userType, userInstanceID, userID) AS MostRecentLogin,
        min(login) OVER(PARTITION BY userType, userInstanceID, userID ORDER BY userType, userInstanceID, userID) AS oldestLogin
        FROM dbo.userActivity) AS summary

我的问题是:这是一种有效的方法吗?我有数百万行和大约 20 列可供每个用户使用。

感谢任何建议。

谢谢!

你写的第一个“味道”是你的 PARTITION BY 列在每种情况下都是 a) 相同和 b) SELECT 列表中唯一的非聚合列1.

第二个“气味”是DISTINCT。不完全是。当有人说“好吧,我得到了我需要的结果,除了当我只想要一个时我得到了多行”时,它经常被使用。糟糕的方法是应用 DISTINCT 而不考虑为什么会得到这些多个结果。

在你的情况下,你得到了多个结果,因为你没有正确聚合。

回顾你的问题,你是说“对于 这些列的每个唯一组合 我想计算 这些聚合 ” .这很好地定义了 GROUP BY2。所以是的,编写此查询的直接方法是:

select userType, userInstanceID, userID,
       COUNT(*) as numLogins, MIN(login) as firstLogin, MAX(login) as lastLogin
from dbo.userActivity
group by userType, userInstanceID, userID

您会注意到它更短且使用的功能更少,这通常是一种告诉您已将查询转换为 a 形式的方式,最有可能被优化优化器。


1综合起来,这些意味着您将可能多次计算完全相同的结果行。您是否真的这样做取决于 a) 您是否有多行具有相同的唯一组合,以及 b) 优化器的智能程度。

2我强烈建议您在考虑 DISTINCT 时应该考虑 GROUP BYDISTINCT实际上是GROUP BY *3,但是在分组时很少有聚合。

3除了 * 是“SELECT 子句中的所有列”而不是“FROM/ JOINs".