sql查询上一行优化

Posted

技术标签:

【中文标题】sql查询上一行优化【英文标题】:Sql Query Pervious Row Optimisation 【发布时间】:2010-08-06 14:19:17 【问题描述】:

这是我的表结构

MyTable
-----------

ObjectID int (Identity),           -- Primary Key
FileName varchar(10),
CreatedDate datetime
...........
...........
...........

我需要获取在文件中创建记录所花费的时间...即...同一文件中的上一条记录与同一文件的当前记录之间经过的时间

ie...如果记录是

ObjectID    FileName    CreatedDate (just showing the time part here)
--------    --------    -----------
1           ABC         10:23
2           ABC         10:25
3           DEF         10:26
4           ABC         10:30
5           DEF         10:31
6           DEF         10:35

所需的输出是...

ObjectID    FileName    CreatedDate     PrevRowCreatedDate
--------    --------    ----------- ---------------
1           ABC         10:23           NULL
2           ABC         10:25           10:23
3           DEF         10:26           NULL
4           ABC         10:30           10:25
5           DEF         10:31           10:26
6           DEF         10:35           10:31

到目前为止,我得到了这个查询,但它比预期花费了很长时间......有没有更好的方法来做到这一点......

    Select  A.ObjectID, 
        A.FileName
        A.CreatedDate as CreatedDate, 
        B.PrevRowCreatedDate,
        datediff("SS", '1900-01-01 00:00:00', Coalesce((A.CreatedDate - B.PrevRowCreatedDate),0)) as secondsTaken
    from MyTable as A 
        Cross Apply (       
        (Select PrevRowCreatedDate = Max(CreatedDate) from MyTable as BB 
                        where   BB.FileName = A.FileName and 
                                BB.CreatedDate < A.CreatedDate
        )
        ) as B  

如果您需要更多信息,请告诉我

谢谢

【问题讨论】:

你有FileName, CreatedDate的索引吗? 您是一次对一行执行此操作,还是对表中的所有行执行此操作?您是将其添加为新列,包括它作为返回集中的列,还是其他?表是小、大还是巨大(以 kb/mb/gb 为单位)? 很遗憾 SQL Server 还不支持 LEAD 和 LAG,尤其是现在它们是 ANSI...:/ @Martin,是的,列已编入索引,如果查询需要任何其他索引,我也可以这样做... @Philip,我需要计算处理特定文件所花费的时间......但问题是,如果两行之间花费的时间超过 20 分钟,则必须从计算……(奇怪的要求,我同意) 【参考方案1】:
SELECT t1.FileName, t1.CreatedDate, t2.CreatedDate as PrevCreatedDate
FROM 
   (SELECT FileName, CreateDate,
          ROW_NUMBER() OVER(PARTITION BY FileName ORDER BY CreatedDate) AS OrderNo
   FROM MyTable) t1
LEFT JOIN
   (SELECT FileName, CreateDate,
     ROW_NUMBER() OVER(PARTITION BY FileName ORDER BY CreatedDate) AS OrderNo
     FROM MyTable) t2
ON (t1.FileName = t2.FileName AND t1.OrderNo = t2.OrderNo - 1)

或者最好使用“WITH”,因为查询是相同的:

WITH t(ObjectID, FileName, CreatedDate, OrderNo) AS
   (SELECT ObjectID, FileName, CreatedDate,
          ROW_NUMBER() OVER(PARTITION BY FileName ORDER BY CreatedDate) AS OrderNo
   FROM MyTable) 
SELECT t1.ObjectID, t1.FileName, t1.CreatedDate, t2.CreatedDate AS PrevCreatedDate,
        DATEDIFF("SS", '1900-01-01 00:00:00', 
           COALESCE((t1.CreatedDate - t2.CreatedDate),0)) AS secondsTaken
FROM t t1 LEFT JOIN t t2 
ON (t1.FileName = t2.FileName AND t1.OrderNo = t2.OrderNo + 1)

【讨论】:

值得尝试并查看计划,但我不确定这是否会比 OP 已有的更有效。 @Martin,不幸的是 mssql 只有几个窗口函数。在 oracle 中,相同的查询看起来会更优雅:SELECT FileName, CreateDate, LAG(CreatedDate, 1, NULL) OVER(PARTITION BY FileName ORDER BY CreatedDate) AS PrevCreatedDate FROM MyTable。但是mssql中不存在LAG ...尽管当我在FileName, CreatedDate 上添加复合索引时,它们都以相同的成本出现。我会做更多的测试。 @Martin Smith - 我想使用 CROSS APPLY 处理的行数会迅速飙升。使用给定的 ROW_NUMBER 解决方案和适当的索引,处理的行数大致为 n*2。对于较大的表,我认为 ROW_NUMBER 解决方案的性能要好得多。 OP 应该尝试一下(并发布一个查询计划)。 @Michael - 那是该图像中的第一个。当我在FileName, CreatedDate 上添加复合索引和几千行测试数据时,尽管您的看起来可能是小赢家,尽管它非常接近img715.imageshack.us/img715/3774/plansb.jpg【参考方案2】:

我认为迈克尔的回答确实应该更有效。在评估效率时,我只想提请注意 Management Studio 中显示的查询成本(相对于批次)的问题。

我设置了一个包含 23,174 行的测试表,并在问题和迈克尔的问题中运行了查询。查看实际执行计划中的“查询成本(相对于批次)”,原始查询为 1%,而 Michael 的成本为 99%,因此看起来效率非常低。

然而,实际的统计数据却说明了一个完全不同的故事

交叉应用方法

SQL Server 执行时间:CPU 时间 = 0 毫秒,经过的时间 = 0 毫秒。 表“我的表”。扫描计数23175, 逻辑读取 49335,物理读取 0, read-ahead 读取 0,lob 逻辑读取 0, lob 物理读取 0, lob 预读为 0。

ROW_NUMBER 方法

SQL Server 执行时间:CPU 时间 = 391 毫秒,经过的时间 = 417 毫秒。 表“工作台”。扫描计数 0, 逻辑读取 0,物理读取 0, read-ahead 读取 0,lob 逻辑读取 0, lob 物理读取 0, lob 预读读数为 0。 表“我的表”。 扫描计数 2,逻辑读取 148, 物理读取 0,预读读取 0, lob 逻辑读取 0,lob 物理 读取 0,lob 预读读取 0。

ROW_NUMBER 计划中,rownumber=rownumber+1 上的 Merge Join 有 23,174 行进入两侧。该值是唯一的,实际行数也是 23,174。然而,SQL Server 估计从该连接产生的行将是 34,812,000,因此它在计划中稍后插入的估计成本非常不准确。

测试脚本

BEGIN TRAN

CREATE TABLE MyTable
  (
     [ObjectID]    [INT] IDENTITY(1, 1) NOT NULL PRIMARY KEY CLUSTERED,
     [FileName]    [VARCHAR](50) NULL,
     [CreatedDate] [DATETIME] NULL
  )

GO

INSERT INTO MyTable
SELECT ISNULL(type, NEWID()),
       DATEADD(DAY, CAST(RAND(CAST(NEWID() AS VARBINARY)) * 10000 AS INT), GETDATE())
FROM   master.dbo.spt_values,
       (SELECT TOP 10 1 AS X FROM  master.dbo.spt_values) V


DELETE FROM MyTable
WHERE  EXISTS(SELECT *
              FROM   MyTable m2
              WHERE  MyTable.CreatedDate = m2.CreatedDate
                     AND MyTable.FileName = m2.FileName
                     AND MyTable.ObjectID < m2.ObjectID)

CREATE UNIQUE NONCLUSTERED INDEX [IX_MyTable]
  ON MyTable ([FileName] ASC, [CreatedDate] ASC)

SET STATISTICS IO ON
SET STATISTICS TIME ON

SELECT A.ObjectID,
       A.FileName,
       A.CreatedDate                                                                                AS CreatedDate,
       B.PrevRowCreatedDate,
       DATEDIFF("SS", '1900-01-01 00:00:00', COALESCE(( A.CreatedDate - B.PrevRowCreatedDate ), 0)) AS secondsTaken
INTO   #A
FROM   MyTable AS A
       CROSS APPLY ((SELECT PrevRowCreatedDate = MAX(CreatedDate)
                     FROM   MyTable AS BB
                     WHERE  BB.FileName = A.FileName
                            AND BB.CreatedDate < A.CreatedDate)) AS B;

WITH t(ObjectID, FileName, CreatedDate, OrderNo)
     AS (SELECT ObjectID,
                FileName,
                CreatedDate,
                RANK() OVER(PARTITION BY FileName ORDER BY CreatedDate) AS OrderNo
         FROM   MyTable)
SELECT t1.ObjectID,
       t1.FileName,
       t1.CreatedDate,
       t2.CreatedDate                                                                          AS PrevCreatedDate,
       DATEDIFF("SS", '1900-01-01 00:00:00', COALESCE(( t1.CreatedDate - t2.CreatedDate ), 0)) AS secondsTaken
INTO   #B
FROM   t t1
       LEFT JOIN t t2
         ON ( t1.FileName = t2.FileName
              AND t1.OrderNo = t2.OrderNo + 1 )

/*Test the 2 queries give the same result*/
SELECT *
FROM   #A
EXCEPT
SELECT *
FROM   #B

SELECT *
FROM   #B
EXCEPT
SELECT *
FROM   #A

ROLLBACK 

【讨论】:

以上是关于sql查询上一行优化的主要内容,如果未能解决你的问题,请参考以下文章

从 SQL Server 查询优化器生成多个脚本

优化慢 SQL 查询

MySQL优化sql连接查询

优化平均值 SQL 查询的平均值

优化 SQL 查询

查询优化,(子查询)(sql-transact)