使用CTE和CrossJoin运行查询的SQL性能问题

时间:2018-09-10 22:54:53

标签: sql sql-server

因此,最初的任务是创建一个报告,该报告确定给定5分钟间隔内的并发事务处理次数,并每周提供最多并发转氨酶的高水位标记。我已经解决了问题,但查询趋向于在3个月的数据中运行3-3.5小时。这不是我真正希望的。

在本练习中,我只关注两列:

Transaction_Data.DateTime (Start of Transaction: e.g. 2018-01-01 23:59:59.999)
Transaction_Data.Duration (Integer seconds: e.g. 272)

此查询的棘手部分是我需要获取DateTime + Duration,并将其分为5分钟间隔。因此,如果时间戳是10:02:00,而持续时间是715秒(以10:13:55结束),那么我需要以10:00、10:05和10:10的间隔来计数事务。

要实现此目的,我使用CTE填充一个临时表,该表具有在相关时间范围内的所有间隔,然后使用交叉联接查询将事务划分为各个间隔。

以下是查询:

DECLARE @Times Table (DateTime DateTime)
DECLARE @StartDate AS DATETIME
,       @EndDate AS DATETIME
SET     @StartDate = '2018-06-01'
SET     @EndDate = '2018-08-31 23:59:59.999';

WITH DateIntervalsCTE AS
(
 SELECT 0 i, @StartDate AS Date
 UNION ALL
 SELECT i + 5, DATEADD(minute, i, @StartDate )
 FROM DateIntervalsCTE 
 WHERE DATEADD(minute, i, @StartDate ) < @EndDate
)
INSERT INTO @Times (DateTime)
SELECT DISTINCT Date 
FROM DateIntervalsCTE
OPTION(MAXRECURSION 32767);

select  Convert(varchar(10),DateAdd(day,-DatePart(weekday,IntData.DateTime)+1,Convert(varchar(10),IntData.DateTime,101)),101) as 'WeekOf'
,       Max(Count) as 'MaxConcur'
from
    (select t.DateTime
    ,       count(t.DateTime) as 'Count'
    from    Transaction_Detail TD
    cross join @Times t
    where   t.DateTime 
            between         
            DateAdd(ss,-(((DatePart(mi,TD.DateTime)%5)*60)+DatePart(ss,TD.DateTime)),DateAdd(ms,-DatePart(ms,TD.DateTime),TD.DateTime))
            and DateAdd(ss,TD.Duration,TD.DateTime)
    group by 
            t.DateTime) as IntData
group by
        Convert(varchar(10),DateAdd(day,-DatePart(weekday,IntData.DateTime)+1,Convert(varchar(10),IntData.DateTime,101)),101)

关于Where子句中的DateAdd内容,我试图将开始时间向下舍入到最接近的5分钟间隔,以便CrossJoin将与开始间隔匹配。

关于数据,由于数据是由第三方应用程序生成的,因此我无法更改任何有关结构的信息。通常,我认为整个SQL Server都是只读的,因此通常避免在其上创建任何类型的静态表。由于我为各种客户端支持许多此类数据库,因此理想的做法是可以将代码简单地粘贴到SSMS窗口中并执行。

关于性能问题,CTE部分本身运行足够快,但是查询运行的方式我希望将5分钟间隔的26k(3个月)与325k交易记录进行比较是我问题的真正根源。朋友之间的85亿操作是什么?

出于全面披露的目的,尽管我编写了很多t-sql并且已经使用了很多年,但这是我第一次使用CTE和CrossJoins。我完全有可能搞砸了一些东西,但一直无法检测到它,但是从我所做的诊断工作来看,它似乎报告得很准确,尽管效果不佳。

我希望此请求的对象是具有更多T-SQL知识的人,然后我指出一种更好的方法来完成我要完成的工作,而这要用几分钟而不是几小时来完成。虽然我不会拒绝改写的解决方案,但我很高兴能直接指出一种更好的技术。

如果您已读过本文,谢谢您的时间。

-J.V。

样本输入

DateTime    Duration
2018-06-01 00:04:55.223 57
2018-06-01 00:04:56.223 58
2018-06-01 00:08:37.180 62
2018-06-01 00:08:37.180 62
2018-06-01 00:20:29.183 10
2018-06-01 00:28:38.423 0
2018-06-01 00:28:53.190 15
2018-06-01 00:31:52.690 195
2018-06-01 00:32:20.917 209
2018-06-01 00:32:54.690 756

注意:这只是输入外观的一个很小的例子。

示例输出

WeekOf      MaxConcur
05/27/2018  101
06/03/2018  169
06/10/2018  189
06/17/2018  148
06/24/2018  186
07/01/2018  218
07/08/2018  222
07/15/2018  210
07/22/2018  219
07/29/2018  225
08/05/2018  243
08/12/2018  231
08/19/2018  253
08/26/2018  220

最终解决方案

首先,感谢所有答复。这对我来说真是太棒了,我学到了一些有趣的想法来解决SQL问题。特别要感谢KumarHarsh使我足够接近以达到最终分辨率,从而在15秒内显示数据,这比我期望的要快得多。这是最终的查询(如果我没有正确执行此操作,很抱歉,但我认为需要共享最终答案):

 -- Set Up Time Range
declare     @minDate DateTime='2018-06-01 00:00:00.000'
declare     @maxDate DateTime='2018-08-31 23:59:59.999' 

-- Build Temporary Interval Table
create      table #TimesTable (
    [DateTime] DateTime not null 
,   [DateCol] Date not null
)

-- Populate Interval Table (5min Intervals)
insert      into #TimesTable
select      dateadd(minute,(RowNum-1)*5,@minDate) as 'DateTime'
,           cast(dateadd(minute,(RowNum-1)*5,@minDate) as Date) as 'Date'
from (
    select      ROW_NUMBER()over(order by (select null)) as 'RowNum'
    from        master..spt_values a
    CROSS JOIN  master..spt_values b
) as TT
where       cast(dateadd(minute,RowNum*5,@minDate) as DateTime) < @maxDate

-- Build Table Indexes
create      clustered index ix_datecol on #TimesTable ([DateTime],[DateCol])

-- Query the Data
select      Convert(varchar(10),DateAdd(day,-DatePart(weekday,IntData.DateTime)+1,Convert(varchar(10),IntData.DateTime,101)),101) as 'WeekOf'
,           Max(Count) as 'MaxConcur'
from (
    select      TData.DateTime
    ,           TData.[DateCol]
    ,           Count(TData.DateTime) as 'Count'
    from        dbo.Transaction_Detail TD
    outer apply (
        select      TT.DateTime
        ,           TT.[DateCol]
        from        #TimesTable as TT
        where       TT.DateTime 
                    between         
                    TD.DateTime and DateAdd(ss,TD.Duration,TD.DateTime)
    ) as TData
    group by    TData.DateTime,TData.[DateCol] 
) as IntData
where       Convert(varchar(10),DateAdd(day,-DatePart(weekday,IntData.DateTime)+1,Convert(varchar(10),IntData.DateTime,101)),101) is not null
group by    Convert(varchar(10),DateAdd(day,-DatePart(weekday,IntData.DateTime)+1,Convert(varchar(10),IntData.DateTime,101)),101)
order by    'WeekOf'

drop table  #TimesTable

从Kumar的例子中,我必须进行一些更改:

  1. 按照我最初的规定,我不想对数据库进行永久更改,因此我将他的示例切换到了“临时本地”表,这似乎就足够了。一切完成后,我放下了表,以便可以按不同的时间间隔重新运行,查询本身也已清除。
  2. 我真的很喜欢笛卡尔积的想法,因为它没有CTE方法的递归限制。我确实必须限制笛卡尔乘积,因为我希望这是紧凑的,而且我还是要删除临时表。
  3. 我摆脱了Interval Table一代中似乎没有做任何事情的一个值。
  4. 我重命名了一些东西。我发现重复使用相同的表别名会使理解机制更加困难。
  5. 出于某种原因,我从显示的结果集中过滤出了NULL行。

2 个答案:

答案 0 :(得分:1)

关于您的脚本,

  1. 为什么要在CTE中使用distinct,将其删除或您的CTE不好

  2. 不要使用表变量,请使用临时表

  3. 按“ Convert(varchar(10),DateAdd(day,-DatePart(weekday,IntData.DateTime)+1,Convert(varchar(10),IntData.DateTime,101)),101)”分组

分组依据可以替换为,注意(DatePart(weekday,getdate())+1)

周围的多余()
select DateAdd(day,-(DatePart(weekday,getdate())+1),Convert(varchar(10),getdate(),101))

select DateAdd(day,-(DatePart(weekday,getdate())+1),cast(getdate() as date))

我不确定,但它确实表明这部分是长而错误的。

仅执行此更改并检查。

  

我的方式

首先创建时间表表。这是一次时间表的创建。

您可以按照任意方式创建

declare @minDate Datetime='2005-01-01'
create table TimesTable ([DateTime] DateTime not null ,[DateCol] Date not null)

insert into TimesTable
select dateadd(minute,rn*5,@minDate),cast(dateadd(minute,rn*5,@minDate) as date)
from
(
select a.number, ROW_NUMBER()over(order by (select null))rn 
from master..spt_values a
CROSS JOIN master..spt_values b
)t4

Create clustered index ix_datecol on TimesTable ([DateTime],[DateCol])

-其中“ 2005-01-01”是任何最小值,请根据您的要求选择最小值

如果索引不起作用并且脚本显示一些改进,则可以更改。

DECLARE @StartDate AS DATETIME
,       @EndDate AS DATETIME
SET     @StartDate = '2018-06-01'
SET     @EndDate = '2018-08-31 23:59:59.999';



select  Convert(varchar(10),DateAdd(day,-DatePart(weekday,IntData.DateTime)+1,Convert(varchar(10),IntData.DateTime,101)),101) as 'WeekOf'
,       Max(Count) as 'MaxConcur'
from
    (select t.DateTime,t.[DateCol]
    ,       count(t.DateTime) as 'Count'
    from    dbo.Transaction_Detail TD
    outer apply(select t.DateTime,t.[DateCol] from TimesTable t
    where   t.DateTime 
            between         
            DateAdd(ss,-(((DatePart(mi,TD.DateTime)%5)*60)+DatePart(ss,TD.DateTime)),DateAdd(ms,-DatePart(ms,TD.DateTime),TD.DateTime))
            and DateAdd(ss,TD.Duration,TD.DateTime)
            )t
    group by 
            t.DateTime,t.[DateCol] ) as IntData
group by
        t.[DateCol]

    -- In place of cross join ,try OUTER APPLY once

我的脚本在开始时会抛出错误或给出错误的输出。但是我确信它可以纠正。

了解t。[DateCol]背后的想法,并相应地调整查询。

让我知道性能是否正常。

答案 1 :(得分:0)

方法:

  1. 尝试预先计算CTE中的所有内容。例如[WeekOf]select Convert(varchar(10),DateAdd...应该来自CTE /表变量

  2. GROUP BYWHERE中避免/最小化计算,例如where t.DateTime between DateAdd ....应该只是一个简单的范围条件。

  3. 您不需要CROSS JOIN

结果:

DECLARE @Times Table (IntervalStart DateTime, IntervalEnd DateTime, [WeekOf] DATETIME)
DECLARE @StartDate AS DATETIME
,       @EndDate AS DATETIME
SET     @StartDate = '2018-06-01'
SET     @EndDate = '2018-08-31 23:59:59.999';

WITH DateIntervalsCTE AS
(
 SELECT 0 i, @StartDate AS Date
 UNION ALL
 SELECT i + 5, DATEADD(minute, i, @StartDate )
 FROM DateIntervalsCTE 
 WHERE DATEADD(minute, i, @StartDate ) < @EndDate
)
INSERT INTO @Times (IntervalStart, IntervalEnd, [WeekOf])
SELECT Date, DATEADD(minute, 5, Date ), Convert(varchar(10),DateAdd(day,-DatePart(weekday,Date)+1,Convert(varchar(10),Date,101)),101)
FROM DateIntervalsCTE
OPTION(MAXRECURSION 32767);

SELECT [WeekOf], MAX( [Count] ) AS 'MaxConcur'
FROM(
    SELECT t.IntervalStart, COUNT(t.IntervalStart) AS [Count], [WeekOf]
    FROM Transaction_Detail AS TD
    INNER join @Times AS t ON t.IntervalStart <= TD.DateTime  AND DATEADD( ss, TD.Duration, TD.DateTime ) < t.IntervalEnd
    GROUP BY [WeekOf], t.IntervalStart ) AS IntData
GROUP BY [WeekOf]

说明:

我添加了PeriodEnd列以简化连接条件(请参阅第2点。)

我已将WeekOf列计算添加到CTE(请参阅第1点)。

可能的改进

如果Transaction_Data.DateTime上有索引,您可以尝试在嵌套的WHERE中添加SELECT子句,例如WHERE @StartDate <= TD.DateTime AND TD.DateTime <= @EndDate减少搜索到的交易记录的数量。

结论

以我的经验,使用合理的硬件,此查询的时间不应超过20分钟。

尝试使用较小范围的@StartDate@EndDate进行测试