Sql 连接大量CTE表(13000000行+)性能问题
我们有一个生产数据库,可以提前几年管理100家分支机构的人员预订,精确到分钟 该系统的一部分是突出差距的报告,即比较分行营业时间和员工预订情况,查看是否有任何分行在无人预订的情况下营业 它还同时检查重叠、重复预订等,基本上需要分钟级别的准确性 我们这样做的方式是使用整数理货表将开放时间和预订的开始和结束时间扩展为分钟:Sql 连接大量CTE表(13000000行+)性能问题,sql,sql-server,tsql,sql-server-2008-r2,common-table-expression,Sql,Sql Server,Tsql,Sql Server 2008 R2,Common Table Expression,我们有一个生产数据库,可以提前几年管理100家分支机构的人员预订,精确到分钟 该系统的一部分是突出差距的报告,即比较分行营业时间和员工预订情况,查看是否有任何分行在无人预订的情况下营业 它还同时检查重叠、重复预订等,基本上需要分钟级别的准确性 我们这样做的方式是使用整数理货表将开放时间和预订的开始和结束时间扩展为分钟: --===== Create and populate the Tally table on the fly SELECT TOP 16777216 IDENT
--===== Create and populate the Tally table on the fly
SELECT TOP 16777216
IDENTITY(INT,1,1) AS N
INTO dbo.Tally
FROM Master.dbo.SysColumns sc1,
Master.dbo.SysColumns sc2,
Master.dbo.SysColumns sc3
--===== Add a Primary Key to maximize performance
ALTER TABLE dbo.Tally
ADD CONSTRAINT PK_Tally_N
PRIMARY KEY CLUSTERED (N) WITH FILLFACTOR = 100
我们利用此静态索引理货表扩展开放时间和预订量,如下所示:
SELECT [BranchID] ,
[DayOfWeek] ,
DATEADD(MINUTE, N - 1, StartTime)
FROM OpeningHours
LEFT OUTER JOIN tally ON tally.N BETWEEN 0
AND DATEDIFF(MINUTE, OpeningHours.StartTime, OpeningHours.EndTime) + 1
问题是,一旦我们有了13000000开放分钟和预定分钟,我们就需要加入结果,看看覆盖了什么:
SELECT OpenDatesAndMinutes.[Date] ,
OpenDatesAndMinutes.[Time] ,
OpenDatesAndMinutes.[BranchID] ,
ISNULL(BookedMinutes.BookingCount, 0) AS BookingCount
FROM OpenDatesAndMinutes
LEFT OUTER JOIN BookedMinutes ON OpenDatesAndMinutes.BranchID = BookedMinutes.BranchID
AND OpenDatesAndMinutes.[Date] = BookedMinutes.[Date]
AND OpenDatesAndMinutes.[Time] = BookedMinutes.[Time]
正如您可以想象的那样,将13000000行存储在CTE表中的分支、日期和时间连接起来需要很长时间——运行一周并不太糟糕,大约10秒,但如果我们运行6个月,13000000分钟会膨胀到25分钟+
一旦我们将开放会议记录加入到预定会议记录中,我们就会将数据分组到孤岛上,并向用户展示:
CrossTabPrep ( [Date], [Time], [BranchID], [BookingCount], [Grp] )
AS ( SELECT [Date] ,
[Time] ,
[BranchID] ,
[BookingCount] ,
DATEPART(HOUR, Time) * 60 + DATEPART(MINUTE, Time) - ROW_NUMBER() OVER ( PARTITION BY [BranchID], Date, [BookingCount] ORDER BY Time ) AS [Grp]
FROM PreRender
),
FinalRender ( [BranchID], [Date], [Start Time], [End Time], [Duration], [EntryCount], [EntryColour] )
AS ( SELECT [BranchID] ,
[Date] ,
MIN([Time]) AS [Start Time] ,
MAX([Time]) AS [End Time] ,
ISNULL(DATEDIFF(MINUTE, MIN([Time]), MAX([Time])), 0) AS Duration ,
[BookingCount] AS EntryCount ,
CASE WHEN [BookingCount] = 0 THEN 'Red'
WHEN [BookingCount] = 1 THEN 'Green'
ELSE 'Yellow'
END AS EntryColour
FROM CrossTabPrep
GROUP BY [BranchID] ,
[Date] ,
[BookingCount] ,
[Grp]
)
很简单,我的方法有效吗?在保持分钟级精度的同时,是否有任何方法可以改进此方法?当处理像这样的大量CTE表时,将这些数据转储到索引的临时表中并连接它们会有什么好处
我考虑的另一件事是替换大连接使用的日期和时间0数据类型,如果我将这些数据类型转换为整数,效率会更高吗
以下是案例中的完整CTE,有助于:
WITH OpeningHours ( [BranchID], [DayOfWeek], [StartTime], [EndTime] )
AS ( SELECT BranchID ,
DayOfWeek ,
CONVERT(TIME(0), AM_open) ,
CONVERT(TIME(0), AM_close)
FROM db_BranchDetails.dbo.tbl_ShopOpeningTimes (NOLOCK)
INNER JOIN @tbl_Days Filter_Days ON db_BranchDetails.dbo.tbl_ShopOpeningTimes.DayOfWeek = Filter_Days.DayNumber
WHERE CONVERT(TIME(0), AM_open) <> CONVERT(TIME(0), '00:00:00')
UNION ALL
SELECT BranchID ,
DayOfWeek ,
CONVERT(TIME(0), PM_open) ,
CONVERT(TIME(0), PM_close)
FROM db_BranchDetails.dbo.tbl_ShopOpeningTimes (NOLOCK)
INNER JOIN @tbl_Days Filter_Days ON db_BranchDetails.dbo.tbl_ShopOpeningTimes.DayOfWeek = Filter_Days.DayNumber
WHERE CONVERT(TIME(0), PM_open) <> CONVERT(TIME(0), '00:00:00')
UNION ALL
SELECT BranchID ,
DayOfWeek ,
CONVERT(TIME(0), EVE_open) ,
CONVERT(TIME(0), EVE_close)
FROM db_BranchDetails.dbo.tbl_ShopOpeningTimes (NOLOCK)
INNER JOIN @tbl_Days Filter_Days ON db_BranchDetails.dbo.tbl_ShopOpeningTimes.DayOfWeek = Filter_Days.DayNumber
WHERE CONVERT(TIME(0), EVE_open) <> CONVERT(TIME(0), '00:00:00')
),
DateRange ( [Date], [DayOfWeek] )
AS ( SELECT CONVERT(DATE, DATEADD(DAY, N - 1, @StartDate)) ,
DATEPART(WEEKDAY, DATEADD(DAY, N - 1, @StartDate))
FROM tally (NOLOCK)
WHERE N <= DATEDIFF(DAY, @StartDate, @EndDate) + 1
),
OpenMinutes ( [BranchID], [DayOfWeek], [Time] )
AS ( SELECT [BranchID] ,
[DayOfWeek] ,
DATEADD(MINUTE, N - 1, StartTime)
FROM OpeningHours
LEFT OUTER JOIN tally ON tally.N BETWEEN 0
AND DATEDIFF(MINUTE, OpeningHours.StartTime, OpeningHours.EndTime) + 1
),
OpenDatesAndMinutes ( [Date], [Time], [BranchID] )
AS ( SELECT DateRange.[Date] ,
OpenMinutes.[Time] ,
OpenMinutes.BranchID
FROM DateRange
LEFT OUTER JOIN OpenMinutes ON DateRange.DayOfWeek = OpenMinutes.DayOfWeek
WHERE OpenMinutes.BranchID IS NOT NULL
),
WhiteListEmployees ( [DET_NUMBERA] )
AS ( SELECT DET_NUMBERA
FROM [dbo].[tbl_ChrisCache_WhiteList]
WHERE [TimeSheetV2_SecurityContext] = @TimeSheetV2_SecurityContext
),
BookedMinutesByRole ( [Date], [Time], [BranchID], BookingCount )
AS ( SELECT [BookingDate] ,
DATEADD(MINUTE, N - 1, StartTime) ,
BranchID ,
COUNT(BookingID) AS Bookings
FROM tbl_Booking (NOLOCK)
INNER JOIN tbl_BookingReason (NOLOCK) ON dbo.tbl_BookingReason.ReasonID = dbo.tbl_Booking.ReasonID
INNER JOIN tbl_ChrisCache (NOLOCK) ON dbo.tbl_Booking.DET_NUMBERA = dbo.tbl_ChrisCache.DET_NUMBERA
INNER JOIN @ValidPosCodes AS Filter_PostCodes ON dbo.tbl_ChrisCache.POS_NUMBERA = Filter_PostCodes.POSCODE
LEFT OUTER JOIN tally (NOLOCK) ON tally.N BETWEEN 0
AND DATEDIFF(MINUTE, tbl_Booking.StartTime, tbl_Booking.EndTime) + 1
WHERE ( Void = 0 )
AND tbl_BookingReason.CoverRequired = 0 --#### Only use bookings that dont require cover
AND tbl_booking.BranchID <> '023' --#### Branch 23 will always have messy data
AND ( dbo.tbl_Booking.BookingDate BETWEEN @StartDate
AND @EndDate )
GROUP BY [BookingDate] ,
BranchID ,
DATEADD(MINUTE, N - 1, StartTime)
),
BookedMinutesByWhiteList ( [Date], [Time], [BranchID], BookingCount )
AS ( SELECT [BookingDate] ,
DATEADD(MINUTE, N - 1, StartTime) ,
BranchID ,
COUNT(BookingID) AS Bookings
FROM tbl_Booking(NOLOCK)
INNER JOIN tbl_BookingReason (NOLOCK) ON dbo.tbl_BookingReason.ReasonID = dbo.tbl_Booking.ReasonID
INNER JOIN tbl_ChrisCache (NOLOCK) ON dbo.tbl_Booking.DET_NUMBERA = dbo.tbl_ChrisCache.DET_NUMBERA
INNER JOIN WhiteListEmployees Filter_WhiteList ON dbo.tbl_Booking.DET_NUMBERA = Filter_WhiteList.DET_NUMBERA
LEFT OUTER JOIN tally (NOLOCK) ON tally.N BETWEEN 0
AND DATEDIFF(MINUTE, tbl_Booking.StartTime, tbl_Booking.EndTime) + 1
WHERE ( Void = 0 )
AND tbl_BookingReason.CoverRequired = 0 --#### Only use bookings that dont require cover
AND tbl_booking.BranchID <> '023' --#### Branch 23 will always have messy data
AND ( dbo.tbl_Booking.BookingDate BETWEEN @StartDate
AND @EndDate )
GROUP BY [BookingDate] ,
BranchID ,
DATEADD(MINUTE, N - 1, StartTime)
),
BookedMinutes ( [Date], [Time], [BranchID], BookingCount )
AS ( SELECT [Date] ,
[Time] ,
[BranchID] ,
BookingCount
FROM BookedMinutesByRole
UNION
SELECT [Date] ,
[Time] ,
[BranchID] ,
BookingCount
FROM BookedMinutesByWhiteList
),
PreRender ( [Date], [Time], [BranchID], [BookingCount] )
AS ( SELECT OpenDatesAndMinutes.[Date] ,
OpenDatesAndMinutes.[Time] ,
OpenDatesAndMinutes.[BranchID] ,
ISNULL(BookedMinutes.BookingCount, 0) AS BookingCount
FROM OpenDatesAndMinutes
LEFT OUTER JOIN BookedMinutes ON OpenDatesAndMinutes.BranchID = BookedMinutes.BranchID
AND OpenDatesAndMinutes.[Date] = BookedMinutes.[Date]
AND OpenDatesAndMinutes.[Time] = BookedMinutes.[Time]
),
CrossTabPrep ( [Date], [Time], [BranchID], [BookingCount], [Grp] )
AS ( SELECT [Date] ,
[Time] ,
[BranchID] ,
[BookingCount] ,
DATEPART(HOUR, Time) * 60 + DATEPART(MINUTE, Time) - ROW_NUMBER() OVER ( PARTITION BY [BranchID], Date, [BookingCount] ORDER BY Time ) AS [Grp]
FROM PreRender
),
DeletedBranches ( [BranchID] )
AS ( SELECT [ShopNo]
FROM [dbo].[vw_BranchList]
WHERE [Branch_Deleted] = 1
),
FinalRender ( [BranchID], [Date], [Start Time], [End Time], [Duration], [EntryCount], [EntryColour] )
AS ( SELECT [BranchID] ,
[Date] ,
MIN([Time]) AS [Start Time] ,
MAX([Time]) AS [End Time] ,
ISNULL(DATEDIFF(MINUTE, MIN([Time]), MAX([Time])), 0) AS Duration ,
--dbo.format_timeV2(ISNULL(DATEDIFF(SECOND, MIN([Time]), MAX([Time])), 0)) AS DurationF ,
[BookingCount] AS EntryCount ,
CASE WHEN [BookingCount] = 0 THEN 'Red'
WHEN [BookingCount] = 1 THEN 'Green'
ELSE 'Yellow'
END AS EntryColour
FROM CrossTabPrep
GROUP BY [BranchID] ,
[Date] ,
[BookingCount] ,
[Grp]
)
SELECT [BranchID] ,
CONVERT(VARCHAR(10), DATEADD(DAY, 7, CONVERT(DATETIME, CONVERT(VARCHAR(10), DATEADD(day, -1 - ( DATEPART(dw, [Date]) + @@DATEFIRST - 2 ) % 7, [Date]), 103) + ' 23:59:59', 103)), 103) AS WeekEnding ,
[Date] ,
[Start Time] ,
[End Time] ,
[Duration] ,
CONVERT(VARCHAR, ( [Duration] * 60 ) / 3600) + 'h ' + CONVERT(VARCHAR, ROUND(( ( CONVERT(FLOAT, ( ( [Duration] * 60 ) % 3600 )) ) / 3600 ) * 60, 0)) + 'm' AS [DurationF] ,
[EntryCount] ,
[EntryColour] ,
CASE WHEN [EntryCount] = 0 THEN 'Red'
WHEN [EntryCount] >= 1 THEN 'Green'
END AS DurationColour ,
CASE WHEN [EntryCount] = 0 THEN 'This period of open-time isnt covered'
WHEN [EntryCount] >= 1 THEN 'This period of open-time is covered by ' + CONVERT(VARCHAR, [EntryCount]) + ' booking(s)'
END AS [DurationComment]
FROM FinalRender
WHERE FinalRender.BranchID NOT IN ( SELECT [BranchID]
FROM DeletedBranches )
这很有趣,因为你在最后用你的问题回答了你自己的问题。您只需尝试一下,但要总结一下: 实现CTE以获得更好的性能。您永远不知道SQL Server何时会多次评估CTE 您可以针对临时表构建indexex。 我不知道你是如何从[DayOfWeek]、DATEADDMINUTE、N-1、StartTime跳到另一个[Date]、[Time]的连接的,但是这里有两列是没有意义的。使用单个日期时间或表示历元秒数的bigint。UnixTimestamp在这里工作得很好。
我的建议不是基于您的数据,而是基于生成的测试数据,因此它可能不完全适用 建议:为了从性能的二次退化至少转变为线性退化,如果数据在批周期中平均分布,则可以使用批处理 在下面的示例中,以3天的批处理间隔处理2年的预订,每个分支机构每天需要2分30秒才能恢复免费时段 测试运行结果:
2 years - 2 minutes and 30 seconds
4 years - 4 minutes and 55 seconds.
6 years - 6 minutes and 41 seconds
它采用了与讨论中使用的相同的逻辑,即使用数字查找不匹配的分钟数
架构和测试数据创建:
IF OBJECT_ID('vwRandomNumber') IS NOT NULL
DROP VIEW vwRandomNumber
GO
IF OBJECT_ID('dbo.fnRandNumber') IS NOT NULL
DROP FUNCTION dbo.fnRandNumber
GO
IF OBJECT_ID('dbo.fnRandomInt') IS NOT NULL
DROP FUNCTION dbo.fnRandomInt
GO
IF OBJECT_ID('tblNumbers') IS NOT NULL
DROP TABLE dbo.tblNumbers
GO
IF OBJECT_ID('Branches') IS NOT NULL
DROP TABLE Branches
GO
IF OBJECT_ID('OpeningHours') IS NOT NULL
DROP TABLE OpeningHours
GO
IF OBJECT_ID('Bookings') IS NOT NULL
DROP TABLE Bookings
GO
CREATE VIEW vwRandomNumber
AS
SELECT Rand() RandomNumber;
GO
CREATE FUNCTION dbo.fnRandNumber()
RETURNS FLOAT
AS
BEGIN
RETURN (SELECT TOP 1 RandomNumber FROM vwRandomNumber)
END;
GO
CREATE FUNCTION dbo.fnRandomInt(@FromNumber INT, @ToNumber INT)
RETURNS INT
AS
BEGIN
RETURN (@FromNumber + ROUND(dbo.fnRandNumber()*(@ToNumber - @FromNumber),0))
END;
GO
CREATE TABLE tblNumbers
(
NumberID INT PRIMARY KEY
)
CREATE TABLE Branches
(
BranchID INT
,BranchName NVARCHAR(100)
);
GO
;WITH cteNumbers AS (
SELECT 1 N
UNION ALL
SELECT N+1 FROM cteNumbers WHERE N<100
)
INSERT INTO
Branches
SELECT N, CAST(NEWID() AS NVARCHAR(100)) FROM cteNumbers
OPTION(MAXRECURSION 0)
CREATE TABLE OpeningHours
(
BranchID INT
, Date DATETIME
, OpenFrom DATETIME
, OpenTo DATETIME
);
GO
CREATE CLUSTERED INDEX CIX_OpeningHours
ON OpeningHours ([Date], [BranchID])
GO
CREATE TABLE Bookings
(
BranchID INT
, BookingDate DATETIME
, BookingFrom DATETIME
, BookingTo DATETIME
)
CREATE CLUSTERED INDEX CIX_Bookings
ON Bookings ([BookingDate],[BranchID])
DECLARE @StartDate DATETIME = DATEADD(month,0,DATEADD(D,0,DATEDIFF(d,0,GETDATE())))
;WITH cteNumbers AS (
SELECT 1 N
UNION ALL
SELECT N+1 FROM cteNumbers WHERE N<2000
)
INSERT INTO
OpeningHours
(
BranchID
, Date
, OpenFrom
, OpenTo
)
SELECT
Branches.BranchID
, Dates.Day
, DATEADD(hour,7,Dates.Day)
, DATEADD(hour,19,Dates.Day)
FROM
(
SELECT
DATEADD(d,N,@StartDate) Day
FROM
cteNumbers
) Dates
CROSS JOIN
Branches
OPTION(MAXRECURSION 0);
INSERT INTO Bookings
SELECT
OpeningHours.BranchID
,OpeningHours.Date
,BookingHours.StartDate
,BookingHours.ToDate
FROM
OpeningHours
CROSS APPLY
(
SELECT DATEADD(hour, dbo.fnRandomInt(0,3), OpeningHours.OpenFrom) StartDate
,DATEADD(hour, dbo.fnRandomInt(4,9), OpeningHours.OpenFrom) ToDate UNION ALL
SELECT DATEADD(hour, dbo.fnRandomInt(1,5), OpeningHours.OpenFrom) StartDate
,DATEADD(hour, dbo.fnRandomInt(6,9), OpeningHours.OpenFrom) UNION ALL
SELECT DATEADD(hour, dbo.fnRandomInt(2,5), OpeningHours.OpenFrom) StartDate
,DATEADD(hour, dbo.fnRandomInt(5,8), OpeningHours.OpenFrom) TODate UNION ALL
SELECT DATEADD(hour, dbo.fnRandomInt(0,3), OpeningHours.OpenFrom) StartDate
,DATEADD(hour, dbo.fnRandomInt(4,9), OpeningHours.OpenFrom) ToDate UNION ALL
SELECT DATEADD(hour, dbo.fnRandomInt(1,5), OpeningHours.OpenFrom) StartDate
,DATEADD(hour, dbo.fnRandomInt(6,9), OpeningHours.OpenFrom) UNION ALL
SELECT DATEADD(hour, dbo.fnRandomInt(2,5), OpeningHours.OpenFrom) StartDate
,DATEADD(hour, dbo.fnRandomInt(5,8), OpeningHours.OpenFrom) TODate UNION ALL
SELECT DATEADD(hour, dbo.fnRandomInt(0,3), OpeningHours.OpenFrom) StartDate
,DATEADD(hour, dbo.fnRandomInt(4,9), OpeningHours.OpenFrom) ToDate UNION ALL
SELECT DATEADD(hour, dbo.fnRandomInt(1,5), OpeningHours.OpenFrom) StartDate
,DATEADD(hour, dbo.fnRandomInt(6,9), OpeningHours.OpenFrom) UNION ALL
SELECT DATEADD(hour, dbo.fnRandomInt(2,5), OpeningHours.OpenFrom) StartDate
,DATEADD(hour, dbo.fnRandomInt(5,8), OpeningHours.OpenFrom) TODate UNION ALL
SELECT DATEADD(hour, dbo.fnRandomInt(0,3), OpeningHours.OpenFrom) StartDate
,DATEADD(hour, dbo.fnRandomInt(4,9), OpeningHours.OpenFrom) ToDate UNION ALL
SELECT DATEADD(hour, dbo.fnRandomInt(1,5), OpeningHours.OpenFrom) StartDate
,DATEADD(hour, dbo.fnRandomInt(6,9), OpeningHours.OpenFrom) UNION ALL
SELECT DATEADD(hour, dbo.fnRandomInt(2,5), OpeningHours.OpenFrom) StartDate
,DATEADD(hour, dbo.fnRandomInt(5,8), OpeningHours.OpenFrom) TODate
) BookingHours;
;WITH cteNumbers AS (
SELECT 1 N
UNION ALL
SELECT N+1 FROM cteNumbers WHERE N<5000
)
INSERT INTO
tblNumbers
SELECT N FROM cteNumbers
OPTION(MAXRECURSION 0)
--SELECT COUNT(*) FROM Bookings WHERE
获取无预订时段的脚本:
SET NOCOUNT ON
IF OBJECT_ID('tblBranchFreePeriods') IS NOT NULL
DROP TABLE tblBranchFreePeriods
IF OBJECT_ID('tblFreeMinutes') IS NOT NULL
DROP TABLE tblFreeMinutes
CREATE TABLE tblBranchFreePeriods
(
BranchID INT
, Date DATETIME
, PeriodStartDate DATETIME
, PeriodEndDate DATETIME
)
CREATE TABLE tblFreeMinutes
(
BranchID INT
,Date DATETIME
,FreeMinute INT
)
IF OBJECT_ID('dbo.tblStartDates') IS NOT NULL
DROP TABLE tblStartDates
CREATE TABLE tblStartDates
(
BranchID INT
, Date DATETIME
, PeriodStartDate DATETIME
)
CREATE CLUSTERED INDEX CIX_tblStartDates
ON tblStartDates([BranchID],[Date])
IF OBJECT_ID('dbo.tblEndDates') IS NOT NULL
DROP TABLE tblEndDates
CREATE TABLE tblEndDates
(
BranchID INT
, Date DATETIME
, PeriodEndDate DATETIME
)
CREATE CLUSTERED INDEX CIX_tblEndDate
ON tblEndDates ([BranchID],[Date])
CREATE CLUSTERED INDEX CIX_tblFreeMinutes
ON tblFreeMinutes ([BranchID],[Date],FreeMinute)
DECLARE @ProcessFromDate DATETIME, @ProcessTo DATETIME
SELECT @ProcessFromDate = MIN(OpenFrom), @ProcessTo = DATEADD(year,2,@ProcessFromDate) FROM OpeningHours
DECLARE @BatchSize INT = 3
DECLARE @StartTime DATETIME = GETDATE()
WHILE (@ProcessFromDate <= @ProcessTo) BEGIN
TRUNCATE TABLE tblFreeMinutes
TRUNCATE TABLE tblStartDates
TRUNCATE TABLE tblEndDates
SET @StartTime = GETDATE()
DECLARE @DateFrom DATETIME = @ProcessFromDate, @DateTo DATETIME = DATEADD(d,@BatchSize,@ProcessFromDate)
PRINT 'Date From ' + CAST(@DateFrom AS NVARCHAR(50))
PRINT 'Date To ' + CAST(@DateTO AS NVARCHAR(50))
INSERT INTO
tblFreeMinutes
SELECT
OpeningHours.BranchID
,OpeningHours.Date
,tblOpeningHourMinutes.NumberID Minute
FROM
OpeningHours
INNER JOIN
tblNumbers tblOpeningHourMinutes
ON
NumberID
BETWEEN DATEDIFF(minute,OpeningHours.Date,OpeningHours.OpenFrom)
AND
DATEDIFF(minute,OpeningHours.Date,OpeningHours.OpenTo)
LEFT OUTER JOIN
Bookings
ON
Bookings.BookingDate = OpeningHours.Date
AND
Bookings.BranchID = OpeningHours.BranchID
AND
tblOpeningHourMinutes.NumberID
BETWEEN
DATEDIFF(minute,Bookings.BookingDate,Bookings.BookingFrom)
AND
DATEDIFF(minute,Bookings.BookingDAte,Bookings.BookingTo)
WHERE
OpeningHours.Date BETWEEN @DateFrom AND @DateTo
AND
Bookings.BookingDate IS NULL
OPTION ( FORCE ORDER )
PRINT 'Populate free minutes ' + CAST(DATEDIFF(millisecond,@StartTime,GETDATE()) AS NVARCHAR(50))
SET @StartTime = GETDATE()
INSERT INTO
tblStartDates
SELECT
tblFreeMinutes.BranchID
, tblFreeMinutes.Date
, DATEADD(minute,tblFreeMInutes.FreeMinute,tblFreeMinutes.Date)
FROM
tblFreeMinutes
LEFT OUTER JOIN
tblFreeMinutes tblFreeMinutesIn
ON
tblFreeMinutesIn.Date = tblFreeMinutes.Date
AND
tblFreeMinutesIn.BranchID = tblFreeMinutes.BranchID
AND
tblFreeMinutesIn.FreeMinute = tblFreeMinutes.FreeMinute-1
WHERE
tblFreeMinutesIn.BranchID IS NULL
PRINT 'Populate start dates ' + CAST(DATEDIFF(millisecond,@StartTime,GETDATE()) AS NVARCHAR(50))
SET @StartTime = GETDATE()
INSERT INTO
tblEndDates
SELECT
tblFreeMinutes.BranchID
, tblFreeMinutes.Date
, DATEADD(minute,tblFreeMInutes.FreeMinute,tblFreeMinutes.Date)
FROM
tblFreeMinutes
LEFT OUTER JOIN
tblFreeMinutes tblFreeMinutesIn
ON
tblFreeMinutesIn.Date = tblFreeMinutes.Date
AND
tblFreeMinutesIn.BranchID = tblFreeMinutes.BranchID
AND
tblFreeMinutesIn.FreeMinute = tblFreeMinutes.FreeMinute+1
WHERE
tblFreeMinutesIn.BranchID IS NULL
PRINT 'Populate end dates ' + CAST(DATEDIFF(millisecond,@StartTime,GETDATE()) AS NVARCHAR(50))
SET @StartTime = GETDATE()
INSERT INTO
tblBranchFreePeriods
SELECT
tblStartDates.BranchID
, tblStartDates.Date
, tblStartDates.PeriodStartDate
, tblEndDate.PeriodEndDate
FROM
tblStartDates
CROSS APPLY
(
SELECT TOP 1
*
FROM
tblEndDates
WHERE
tblEndDates.BranchID = tblStartDates.BranchID
AND
tblEndDates.Date = tblStartDates.Date
AND
tblEndDates.PeriodEndDate > tblStartDates.PeriodStartDate
ORDER BY
PeriodEndDate ASC
) tblEndDate
PRINT 'Return intervals ' + CAST(DATEDIFF(millisecond,@StartTime,GETDATE()) AS NVARCHAR(50))
SET @StartTime = GETDATE()
SET @ProcessFromDate = DATEADD(d,@BatchSize+1,@ProcessFromDate)
PRINT ''
PRINT ''
RAISERROR ('',0,0) WITH NOWAIT
--SELECT * FROM tblBranchFreePeriods
--BREAK
END
SELECT
*
FROM
tblBranchFreePeriods
ORDER BY
1,2,3
代码中的内联注释会很有帮助。太不清楚了。您有两个CTE,但不显示使用它们的查询。请参阅连接分支,但没有名为Branch的表。@Blam我不知道扩展的CTE的其余部分如何帮助解决连接13000000行的问题?但不管怎样,我已经编辑了我的问题,以包括完整的CTE。Materialize正在向表中写入。永久性或临时性。CTE只是语法,您无法真正控制它的求值次数。好的,有一些技巧可以使用CTE。使用临时表,您可以强制对其进行一次计算,更重要的是创建一个索引。不要等到最后才排除DeleteBranchs。尽快这样做。你能确认一下你所说的物化是什么意思吗?很可能他的意思是将CTE的结果存储在永久表中并对它们进行索引,而不是依赖内存。物化=创建一个临时表。你可以索引一个临时表,但不能索引一个CTE。看起来像是将CTE拆分成两个插入来填充两个索引的临时表,然后将它们合并起来,这绝对是一种方法-看起来我明天会忙得不可开交,为答案干杯