在非常大的表中使用CTE vs WHILE循环的SQL查询性能

时间:2015-01-16 02:44:48

标签: sql-server performance tsql

我试图在下面的场景中找出性能最佳的查询。

下表非常大,超过500万行。每天都会发生一些负载,尽管确切的变化次数。每个负载由load_id(聚集索引的一部分)标识,load_datetimestamp是适用于该负载的时间戳。每个载荷插入约30,000行。

CREATE TABLE [VeryLargeTable](
        [load_id]               [int]           NOT NULL, 
        [acct_cd]               [varchar](20)   NOT NULL,
        [acct_num]              [varchar](255)  NULL,
        [prod_id]               [varchar](50)   NOT NULL,
        [domestic_foreign_cd]   [varchar](3)    NOT NULL,
        [vendor_prod_id]        [varchar](15)   NULL,
        [prod_name]             [varchar](100)  NULL,
        [currency_cd]           [varchar](3)    NULL,
        [total_Qty]             [int]           NULL,
        [mkt_price]             [float]         NULL,
        [load_datetimestamp]    [datetime]      NULL,
CONSTRAINT [pk_VeryLargeTable] PRIMARY KEY CLUSTERED (       
        [load_id] ASC, 
        [acct_cd] ASC, 
        [prod_id] ASC,
        [domestic_foreign_cd] ASC )
)

在任何一个晚上,我想从今天的第一次加载获得所有行。在上面的DDL中,查询必须尽可能高效。显然,你不想开始使用" WHERE datediff(day,load_datetimestamp,getDate())= 0"在整个桌子上。

我写了这两个查询。有人比另一个好吗?我知道两者都会返回相同的结果。你能否建议一个比这两个中的任何一个更好的?

查询1

With 
T1 as (select
    load_id,
    load_datetimestamp
    from dbo.VeryLargeTable
    group by 
    load_id,load_datetimestamp),
T2 as (select
    load_id,
    load_datetimestamp
    from T1
    where
    datediff(day,load_datetimestamp,getDate())=0),
T3 as (select min(load_id) as loadID from T2)

select * from dbo.VeryLargeTable
where load_id = (select loadID from T3)

查询2

declare @found tinyint;
declare @loadID int;
declare @dateTimeStamp datetime;

-- Get max value of load id
select @loadID = max(load_id) from [dbo].[VeryLargeTable];

-- Keep looping until previous day is found or minimum load_id is reached
set @found = 0;
WHILE (@found=0)
    BEGIN
        select @dateTimeStamp = load_datetimestamp from [dbo].[VeryLargeTable] where load_id=@loadID;
        if (@loadID=0) SET @found=1
        else 
            BEGIN
                if (DATEPART(day, @dateTimeStamp) = DATEPART(day, GetDate())) SET @loadID = @loadID - 1;
                else SET @found=1;              
            END
    END

SELECT * from [dbo].[VeryLargeTable] where load_id=(@loadID + 1);

3 个答案:

答案 0 :(得分:3)

1)正如评论中提到的那样,不要使用像datediff(day,load_datetimestamp,getDate())=0)这样的表达式。此表达式不能使用列load_datetimestamp上的索引。

使用load_datetimestamp >= CAST(CAST(GETDATE() AS date) AS datetime)

2)在列load_datetimestamp上创建索引。

3)找到今天加载的第一行并从中获取load_id

SELECT TOP(1) load_id
FROM dbo.VeryLargeTable
WHERE load_datetimestamp >= CAST(CAST(GETDATE() AS date) AS datetime)
ORDER BY load_datetimestamp

此查询应该是load_datetimestamp上的索引的即时搜索。检查执行计划。

4)在load_id列上创建索引。

5)使用找到的load_id返回该加载的所有行:

WITH
CTE_FirstLoad
AS
(
    SELECT TOP(1) load_id
    FROM dbo.VeryLargeTable
    WHERE load_datetimestamp >= CAST(CAST(GETDATE() AS date) AS datetime)
    ORDER BY load_datetimestamp
)
SELECT * 
FROM dbo.VeryLargeTable
WHERE load_id IN (SELECT load_id FROM CTE_FirstLoad)
;

这应该使用load_id上的索引。

我已经编辑了答案,并将IN代替=放在最后WHERE中。使用IN即使今天没有任何负载,查询也会起作用。 =最有可能出现错误。

修改

现在我们知道您无法在此表上创建新索引。我们知道每天的负载数量很少(大约5)。

有了这个限制,很可能你的第二个带有显式循环的查询将是最快的。无论如何,你必须尝试真实系统上的所有建议并测量它们的性能。

我们可以假设每个负载在同一天开始和结束吗?换句话说,对于每个load_idload_datetimestamp的日期是相同的?

然后,您可以尝试对您的循环进行一些调整。它们可能不会改变或根本不改变性能,但你必须衡量。

而不是:

select @loadID = max(load_id) from [dbo].[VeryLargeTable];

尝试:

SET @loadID = 
    (select TOP(1) load_id from [dbo].[VeryLargeTable] ORDER BY load_id DESC);

在循环中以相同的方式(我怀疑当前行实际上是有效的,因为你有相同的load_id的许多行)而不是:

select @dateTimeStamp = load_datetimestamp from [dbo].[VeryLargeTable] where load_id=@loadID;

尝试:

SET @dateTimeStamp = 
    (select TOP(1) load_datetimestamp from [dbo].[VeryLargeTable] where load_id=@loadID);

如果您被允许创建另一个表

我假设负载ID会随着时间的推移而增加,即ID=N的负载比load_datetimestamp的负载少ID=N+1

使用Loads上的主要唯一聚集索引列load_id创建一个单独的表load_id

此表格包含每天最后load_id行的一行。它会比主表小得多。

此表的目的是快速找到“今天的第一个load_id”而不是扫描大表。

每天晚上运行报告时,这个小Loads表格会包含前几天的加载行,但不会包含今天的行。

您能否说,当您在晚上运行报告时,当天所有装货都已完成?如果是的话:

从小表中获取前几天的最后一次加载:

SET @PrevLoadID = 
(SELECT TOP(1) load_id FROM Loads ORDER BY load_id DESC)

从大表中获取今天的第一个负载:

SET @TodayFirstLoadID = 
(SELECT TOP(1) load_id 
FROM VeryLargeTable 
WHERE VeryLargeTable.load_id >  @PrevLoadID
ORDER BY load_id ASC)

从大表中获取今天的最后一次加载:

SET @TodayLastLoadID = 
(SELECT TOP(1) load_id 
FROM VeryLargeTable 
ORDER BY load_id DESC)

INSERT@TodayLastLoadID加入Loads

使用@TodayFirstLoadID运行主报告:

SELECT * 
FROM VeryLargeTable 
WHERE load_id = @TodayFirstLoadID

答案 1 :(得分:1)

这里你不需要3个CTE。您可以通过简单地选择满足过滤条件的Load ID和Load DateTimeStamp的所有不同组合来替换前2个CTE。然后,您可以通过将该检查直接移动到最终选择中的子查询来摆脱第三个CTE。生成的查询如下所示:

;With 
T2 as 
(select distinct
 load_id,
 load_datetimestamp
 from dbo.VeryLargeTable
 where datediff(day,load_datetimestamp,getDate())=0)

select * from dbo.VeryLargeTable
where load_id = (select min(load_ID) from T2)

答案 2 :(得分:0)

您需要在执行计划中检查它,但如果我是您,我会使用临时表而不是使用3个表表达式并将其与原始表连接。

同样如前所述,最好不要在WHERE子句中使用函数。相反,您可以使用date BETWEEN start AND end