【问题标题】:T-SQL Efficient use of PARTITION BY and DISTINCTT-SQL 高效使用 PARTITION BY 和 DISTINCT
【发布时间】:2021-03-09 11:48:30
【问题描述】:

我有下表来监控用户登录到应用程序:

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(绿色)

我的目标是知道:

  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 列可供每个用户使用。

感谢任何建议。

谢谢!

【问题讨论】:

  • " 这是有效的方法吗?" 不,使用非窗口聚合和 GROUP BY 而不是 DISTINCT 和窗口函数。
  • 嗨@Larnu,感谢您的回复!您能否更具体地了解“窗口函数”?
  • 它看起来像一个教科书式的例子来介绍GROUP BY,而不是PARTITION
  • 由于使用了OVER 子句,您上面拥有的是窗口函数@Wilmar。 SELECT - OVER Clause (Transact-SQL)不想要窗口函数,只是“正常”聚合。 COUNT (Transact-SQL)
  • 嗨@Damien_The_Unbeliever,感谢您的回复。所以,像这样:SELECT userType, userInstanceID, userID, count(login) as numberLogin, MAX(login) as MostRecentLogin, MIN(Login) as oldestLogin FROM dbo.userActivity GROUP BY userType, userInstanceID, userID ?

标签: sql-server performance tsql distinct partitioning


【解决方案1】:

你写的第一个“味道”是你的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 最有可能被优化器优化的形式的方法。 p>


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

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

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

【讨论】:

  • 感谢@Damin_The_Unbeliever 的详细解释。对于GROUP BY,当我有不想分组的字段和不想分组的字段时,我总是感到困惑。例如,假设我有字段:a, b, c, d。在一张桌子上。我想带来所有的价值,但只被a, b 修饰。例如:SELECT a,b,,c,d, COUNT(c) FROM myTable GROUP BY a, b; 我收到一条错误消息,指出 c, d 也必须在 GROUP BY 中。这就是为什么我最终尝试使用更清晰的 PARTITION BY。
猜你喜欢
  • 1970-01-01
  • 2020-11-16
  • 2019-10-22
  • 2013-12-20
  • 1970-01-01
  • 2019-11-14
  • 1970-01-01
  • 2012-11-04
  • 1970-01-01
相关资源
最近更新 更多