在SQL Server 2012中正确使用最大聚合窗口函数

在SQL Server 2012中正确使用最大聚合窗口函数,sql,sql-server,Sql,Sql Server,我有一个记录各种后台作业运行历史的日志表 现在,我需要显示每个作业的最新运行以及一些数据 以下是我的解决方案: SELECT BackgroundJobId, bjl.LogId, ExecStartTime, ExecEndTime, ErrorDescription, Debug FROM BackgroundJobLog bjl JOIN ( SELECT LogId, ROW_NUMBER() OVER (PARTITION BY BackgroundJobId ORDER BY

我有一个记录各种后台作业运行历史的日志表

现在,我需要显示每个作业的最新运行以及一些数据

以下是我的解决方案:

SELECT BackgroundJobId, bjl.LogId, ExecStartTime, ExecEndTime, ErrorDescription, Debug
FROM BackgroundJobLog bjl
JOIN (
    SELECT LogId, ROW_NUMBER() OVER (PARTITION BY BackgroundJobId ORDER BY ExecStartTime DESC) rowNumber
    FROM BackgroundJobLog
    WHERE BackgroundJobStatusId IN (1, 3)
) AS bjl2 ON bjl.LogId = bjl2.LogId AND bjl2.rowNumber = 1
它按预期返回157行,每行包含一个不同的BackgroundJobId,其中包含来自该作业最近运行的信息

但是,性能是一个问题。现在,该日志表大约有25000000行满足嵌套的SELECT语句。加入25000000行似乎是一种可怕的浪费,而我所需要的只是包含最新ExecutStartTime的行

所以,我想我可以使用最大聚合窗口函数。但对于我的生活,我不明白如何。声明如下:

SELECT BackgroundJobId, LogId, MAX(ExecStartTime) OVER (PARTITION BY BackgroundJobId) ExecStartTime
FROM BackgroundJobLog
WHERE BackgroundJobStatusId IN (1, 3)
尝试返回相同的25000000行。是的,对于相同的BackgroundJobId,将返回最新的ExecStartTime值,但重复次数与具有相同BackgroundJobId的行相同!当然,每行都有自己的LogId。而我只想在同一BackgroundJobId中包含最新ExecutStartTime的行

我怎样才能有效地做到这一点

编辑

伙计们,嵌套选择是嵌套选择。无论是显式连接还是作为CTE或直接从中选择,差异都很小。只要有嵌套的选择,性能就会很好

编辑2

BackgroundJobStatusId上有一个索引:

编辑3

该表的架构为:

CREATE TABLE BackgroundJobLog
(
    LogId uniqueidentifier NOT NULL,
    BackgroundJobId int NOT NULL,
    ExecStartTime datetime NULL,
    ExecEndTime datetime NULL,
    ErrorDescription ntext NULL,
    BackgroundJobStatusId int NOT NULL,
    Debug ntext NULL,
    LogEntryId int IDENTITY(1,1) NOT NULL
    CONSTRAINT PK_LogEntryId PRIMARY KEY CLUSTERED (LogEntryId),
    CONSTRAINT IX_BackgroundJobLog UNIQUE NONCLUSTERED (LogId)
)
编辑4

请在下面找到哈科比扬哈姆雷特的答案的执行计划:

编辑5

请在下面找到Kirill Zorin回答的执行计划:

要使此查询快速运行,您需要两件事:

不同背景jobid的列表

BackgroundJobLog BackgroundJobId、ExecStartTime INCLUDE BackgroundJobStatusId上的复合索引

如果您有一个单独的包含作业的表,只需使用它:

SELECT  bl.*
FROM    job
CROSS APPLY
        (
        SELECT  TOP 1
                *
        FROM    BackgroundJobLog
        WHERE   BackgroundJobId = job.id
                AND BackgroundJobStatusId IN (1, 3)
        ORDER BY
                ExecStartTime DESC
        ) bl
如果没有,可以创建索引视图以获取这样的列表:

CREATE VIEW job
WITH SCHEMABINDING
AS
SELECT  backgroundJobId, COUNT_BIG(*) cnt
FROM    BackgroundJobLog
GROUP BY
        backgroundJobId
GO

CREATE UNIQUE CLUSTERED INDEX
        ux_job
ON      job (backgroundJobId)
GO
然后重复前面的查询,添加NOEXPAND:

或者,您可以在CTE中建立这样一个列表:

WITH    job (id) AS
        (
        SELECT  MIN(BackgroundJobId)
        FROM    BackgroundJobLog
        UNION ALL
        SELECT  (
                SELECT  backgroundJobId
                FROM    (
                        SELECT  backgroundJobId,
                                ROW_NUMBER() OVER (ORDER BY backgroundJobId) rn
                        FROM    BackgroundJobLog bl
                        WHERE   bl.backgroundJobId > job.id
                        ) q
                WHERE   rn = 1
                )
        FROM    job
        WHERE   id IS NOT NULL
        )
SELECT  bl.*
FROM    job
CROSS APPLY
        (
        SELECT  TOP 1
                *
        FROM    BackgroundJobLog
        WHERE   BackgroundJobId = job.id
                AND BackgroundJobStatusId IN (1, 3)
        ORDER BY
                ExecStartTime DESC
        ) bl
WHERE   job.id IS NOT NULL

我不认为加入是必要的

;WITH CTE
AS
(
    SELECT *,
       ROW_NUMBER() OVER (PARTITION BY BackgroundJobId ORDER BY ExecStartTime DESC) rn
    FROM BackgroundJobLog
    WHERE BackgroundJobStatusId IN (1, 3)
)
SELECT BackgroundJobId
      , LogId
      , ExecStartTime
      , ExecEndTime
      , ErrorDescription
      , Debug
FROM CTE
WHERE rn = 1

我觉得您应该能够完全避免连接,方法是在内部查询中选择所需的所有内容,再加上rownumber,在外部查询中选择除rownumber之外的所有内容,并将where子句更改为仅包含rownumber。那么,这个项目的执行计划看起来更好吗

SELECT BackgroundJobId, LogId, ExecStartTime, ExecEndTime, ErrorDescription, Debug
FROM (
    SELECT BackgroundJobId, LogId, ExecStartTime, ExecEndTime, ErrorDescription, Debug
        , ROW_NUMBER() OVER (PARTITION BY BackgroundJobId ORDER BY ExecStartTime DESC) rowNumber
    FROM BackgroundJobLog
    WHERE BackgroundJobStatusId IN (1, 3)
    ) bjl
WHERE rowNumber = 1

如果要使用MAX,可以尝试以下方法:

SELECT BackgroundJobId, LogId
FROM
(
SELECT BackgroundJobId, LogId, ExecStartTime, MAX(ExecStartTime) OVER (PARTITION BY BackgroundJobId) MaxExecStartTime
FROM BackgroundJobLog
WHERE BackgroundJobStatusId IN (1, 3)
)
WHERE ExecStartTime = MaxExecStartTime

只要有嵌套的选择,性能就会很好。您能备份一下吗?嵌套的select返回的行数超过25000000行,而实际只需要157行。这还不够吗?但我也尝试了其他方法。只要嵌套的select返回那么多行,就没有改进。不,这是不够的。嵌套的select不返回任何内容,db优化器比它更复杂。你试过创建我建议的索引吗?如果没有这个索引,服务器将不得不处理所有2500万条记录,不管发生什么情况。由于您想要的数据的性质,您在这里得到的任何答案都需要从您拥有的数据类型中进行排序。因此,像卡西诺建议的覆盖指数将极大地帮助你提高速度。性能不是基于简单的内部/外部select语句顺序。有一个名为IX_BackgroundJobLog_BackgroundJobStatusId的索引-请参阅编辑2。这是你指的索引吗?你没有看到任何区别吗?你在这里没有自动连接。是的,我运行了它。没有改善。这是可以理解的——CTE返回25000000行。BackgroundJobStatusId的选择性是什么?你有索引吗?BackgroundJobId和ExecStartTime也一样。是的,上面有一个索引-请参见编辑2。但表的99%满足状态1或3的条件。您能显示执行计划吗?那么BackgroundJobId和ExecutStartTime呢?我想在这里扩展我的知识,所以这更多的是一个为什么或者为什么不的问题,但是..对于这个特定的查询,复合索引,如果ExecStartTime按降序排列,效果不是更好吗?@JaazCole:SQL Server可以扫描任意方向的索引。谢谢你的回答。我的页面没有刷新,没有看到你的答案。我有一个单独的工作定义表,当然很小。让我再试试你的建议。执行计划见编辑5。阿比斯玛。
SELECT BackgroundJobId, LogId, ExecStartTime, ExecEndTime, ErrorDescription, Debug
FROM (
    SELECT BackgroundJobId, LogId, ExecStartTime, ExecEndTime, ErrorDescription, Debug
        , ROW_NUMBER() OVER (PARTITION BY BackgroundJobId ORDER BY ExecStartTime DESC) rowNumber
    FROM BackgroundJobLog
    WHERE BackgroundJobStatusId IN (1, 3)
    ) bjl
WHERE rowNumber = 1
SELECT BackgroundJobId, LogId
FROM
(
SELECT BackgroundJobId, LogId, ExecStartTime, MAX(ExecStartTime) OVER (PARTITION BY BackgroundJobId) MaxExecStartTime
FROM BackgroundJobLog
WHERE BackgroundJobStatusId IN (1, 3)
)
WHERE ExecStartTime = MaxExecStartTime