【问题标题】:SQL Server: grouping continuous value of a columnSQL Server:对列的连续值进行分组
【发布时间】:2016-07-25 06:29:50
【问题描述】:

我有这样的观点vMobileHistory_MobileRegion,就像:

strUnitID       strDate     strTime   iMobileHistory   isValidRegion
---------------------------------------------------------------
352848028160311 1394/11/01  10:35:16    33029937      0
352848028160311 1394/11/01  10:35:17    33029938      0
352848028160311 1394/11/01  10:35:18    33029939      1
352848028160311 1394/11/01  10:35:19    33029940      1
352848028160311 1394/11/01  10:35:20    33029941      1
352848028160311 1394/11/01  10:35:22    33029942      0
352848028160311 1394/11/01  10:35:25    33029943      0
352848028160311 1394/11/01  10:35:28    33029944      0
352848028160311 1394/11/01  10:35:34    33029945      1
352848028160311 1394/11/01  10:35:35    33029946      1

索引是:

ClusterdIndex, strUnitID ASC, strDate DESC, strTime DESC
NonClusterdIndex, iMobileHistory ASC
NonClusterdIndex, strDate ASC, strTime ASC
NonClusterdIndex, strUnitID ASC, strDate ASC

我有这个查询是对基于连续值“isValidRegion”的行进行分组

SELECT
    strUnitID,
    strDate,
    strTime,
    isValidRegion,
    iMobileHistory,
    (ROW_NUMBER () OVER (PARTITION BY strUnitId ORDER BY strDate, strTime)
     - 
     ROW_NUMBER () OVER (PARTITION BY strUnitId, isValidRegion ORDER BY strDate, strTime)
    ) AS grp
FROM 
    vMobileHistory_MobileRegion 
GROUP BY
    strUnitID, strDate, isValidRegion, grp

问题是vMobileHistory_MobileRegion 有超过 100M 的行,并且在其上使用 ROW_NUMBER() 会导致计算所有行的 ROW_NUMBER(),这真的很慢,并导致在 tempdb 中排序数据,因为大数据不适合在记忆中。

不使用ROW_NUMBER()还有其他方法吗?

实际上,我需要isValidRegion的每个连续值的持续时间

【问题讨论】:

  • 你想用 row_number 函数计算什么?
  • 这是计算两个不同分区的RowNumber的差异,以了解“isValidRegion”是否连续
  • 你有索引吗?主要性能问题不是ROW_NUMBER(),排序100M记录很慢。
  • 其实我需要isValidRegion的每个连续值的持续时间
  • Paweł Dyl:是的,我有索引,问题是排序 100M 记录会导致操作员将数据溢出到 tempdb,这真的很慢。如果有帮助,我可以发布实际执行计划

标签: sql-server performance common-table-expression


【解决方案1】:

我无法完全重现您的环境,但这是我所做的(概念上):

CREATE TABLE Regions
(
    UnitID char(15),
    EventTime datetime2,
    MobileHistory int,
    IsValidRegion bit
)

--Notice EventTime is not DESC, compacted to one column
CREATE CLUSTERED INDEX IX_MobileHistory ON Regions(UnitId, EventTime)

--You can join columns strDate and strTime in CTE expression
--I omited this for simiplicity
TRUNCATE TABLE Regions
INSERT Regions VALUES
('352848028160311', '1394/11/01 10:35:16', 33029937, 0),
('352848028160311', '1394/11/01 10:35:17', 33029938, 0),
('352848028160311', '1394/11/01 10:35:18', 33029939, 1),
('352848028160311', '1394/11/01 10:35:19', 33029940, 1),
('352848028160311', '1394/11/01 10:35:20', 33029941, 1),
('352848028160311', '1394/11/01 10:35:22', 33029942, 0),
('352848028160311', '1394/11/01 10:35:25', 33029943, 0),
('352848028160311', '1394/11/01 10:35:28', 33029944, 0),
('352848028160311', '1394/11/01 10:35:34', 33029945, 1),
('352848028160311', '1394/11/01 10:35:35', 33029946, 1)

这是最终查询,拆分为 CTE 以查看部分解决方案:

WITH Lagged AS
(
    SELECT CASE WHEN LAG(IsValidRegion) OVER (PARTITION BY UnitId ORDER BY EventTime) <> IsValidRegion THEN 1 ELSE 0 END IsChange,
        ISNULL(DATEDIFF(second, EventTime, LEAD(EventTime) OVER (PARTITION BY UnitId ORDER BY EventTime)), 0) TimeSpan,
        IsValidRegion*1 IsValidRegion, MobileHistory, UnitId, EventTime
    FROM Regions
),
LaggedGroupChanges AS
(
    SELECT *, SUM(CASE WHEN IsChange=1 THEN 1 ELSE 0 END) OVER (PARTITION BY UnitId ORDER BY EventTime) ChangeGroup
    FROM Lagged
)
SELECT UnitId, SUM(TimeSpan) TotalTime,
    MAX(IsValidRegion) IsValidRegion,
    MIN(MobileHistory) MinMobileHistory,
    MAX(MobileHistory) MaxMobileHistory
FROM LaggedGroupChanges
GROUP BY UnitId, ChangeGroup

它只需要一个由最终 GROUP BY 引起的排序操作。结果如下:

UnitIdTotalTime TotalTime IsValidRegion MinMobileHistory MaxMobileHistory
352848028160311 2         0             33029937         33029938
352848028160311 4         1             33029939         33029941
352848028160311 12        0             33029942         33029944
352848028160311 1         1             33029945         33029946

如果没有指定结束时间,我使用 0。TotalTime 以秒为单位,由于 strDate 和 strTime 列,您的查询可能会被调整以使用一些不同的方法来计算时间跨度。

【讨论】:

  • 这是完美的 Pawel,我真的很喜欢这种思维方式,但这也需要大约 40 秒才能返回结果(与我的查询相同)。我主要将此查询用于特定日期(其中 strDate = XXXX )。这大约需要 40 秒才能返回结果。但是如果我过滤更多,比如(其中 strDate = XXX 和 strTIme > '12:00'),这将花费不到 1 秒的时间!和执行计划的唯一区别是排序不再溢出到 tempdb,也许我必须对服务器内存做点什么
  • 你/我的执行计划中有多少排序操作?只有1吗?该表与服务器内存有多大?
  • 你有 1 种排序,这是完美的(我的查询有 2 种排序)。但我认为这些类型已经完成,因为索引。我将发布您和我的查询的实际执行计划。
猜你喜欢
  • 2012-09-11
  • 1970-01-01
  • 1970-01-01
  • 2018-04-09
  • 1970-01-01
  • 2020-08-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多