如何防止聚簇索引扫描?
How to prevent clustered index scan?
我操作的是SAP B1数据库,不允许修改数据库的结构。
我有一个有 4 列的 table。
Table name: HLD1
Column Name Type
1 HldCode nvarchar
2 StrDate datetime
3 EndDate datetime
4 Rmrks nvarchar
部分数据如下所示:
HldCode StrDate EndDate Rmrks
2016 Holidays 2016-09-05 00:00:00.000 2016-09-05 00:00:00.000 Labor Day
2016 Holidays 2016-11-24 00:00:00.000 2016-11-25 00:00:00.000 Thankgiving
2016 Holidays 2016-12-26 00:00:00.000 2016-12-26 00:00:00.000 Christmas
2017 Holidays 2017-01-02 00:00:00.000 2017-01-02 00:00:00.000 New Years Day
2017 Holidays 2017-05-29 00:00:00.000 2017-05-29 00:00:00.000 Memorial Day
2017 Holidays 2017-07-04 00:00:00.000 2017-07-04 00:00:00.000 Indepenance Day
注意这个table中没有主键。
我创建了一个函数来查找两个日期之间的天数,不包括节假日(由上面的 HLD1 table 提供)和周末。虽然该函数按预期工作,但它使用的每行也需要 ~.75 秒,我们正在尝试 return 50000 行稍后在 Crystal 报告中进行总结。
引用 HLD1 table(并导致执行计划中的聚簇索引)的函数片段如下所示:
CREATE FUNCTION [dbo].[dateDiffHolidays] (
declare @START DATE
declare @END DATE
)
RETURNS INT
AS
BEGIN
SELECT @AddDays =
(select sum(datediff(dd,strdate,enddate) + 1) from hld1
where strdate between @START and @END)
+
(SELECT
(DATEDIFF(wk, @Start, @End) * 2)
+(CASE WHEN DATENAME(dw, @Start) = 'Sunday' THEN 1 ELSE 0 END)
+(CASE WHEN DATENAME(dw, @End) = 'Saturday' THEN 1 ELSE 0 END))
RETURN @AddDays
END
GO
具体来说,第一部分。 @START
和 @END
是传递给函数的参数。
当我检查函数的执行计划时,一切看起来都很快,除了这一段。它给了我以下信息:
我在网上找到的关于如何防止或修复这种减速的所有资源都建议添加索引,而不是引用某些列等,但由于我无法修改数据库,我'我一直找不到任何方法来帮助解决我的情况。
有什么建议吗?
编辑 1:
添加了 Table 来自 SQL 管理的架构信息
编辑 2:
添加了函数的全文,以防万一:
CREATE FUNCTION [dbo].[dateDiffHolidays] (
@startdaytime DATETIME,
@enddaytime DATETIME
)
RETURNS INT
AS
BEGIN
DECLARE @answer INT, @START Date, @END Date, @AddDays int
SET @answer = 0
-- Strip Times
SELECT @START = dateadd(dd,0, datediff(dd,0,@StartDayTime)), @END =
dateadd(dd,0, datediff(dd,0,@EndDayTime))
SELECT @AddDays = (select sum(datediff(dd,strdate,enddatE) + 1) from hld1
where strdate between @START and @END
order by HldCode, StrDate, EndDate) + (
SELECT
(DATEDIFF(wk, @Start, @End) * 2)
+(CASE WHEN DATENAME(dw, @Start) = 'Sunday' THEN 1 ELSE 0 END)
+(CASE WHEN DATENAME(dw, @End) = 'Saturday' THEN 1 ELSE 0 END))
-- handle end conditions
DECLARE @firstWeekDayInRange datetime, @lastWeekDayInRange datetime;
SELECT @firstWeekDayInRange = @START, @lastWeekDayInRange = @END
WHILE @firstWeekDayInRange in (select cast( DATEADD(day, t.N - 1, StrDate)
as date) as ResultDate
from HLD1 s join cteTally t on t.N <= DATEDIFF(day, StrDate, EndDate) + 1)
or datepart(dw,@firstWeekDayInRange) in (1,7)
BEGIN
SELECT @firstWeekDayInRange =
CASE
WHEN @firstWeekDayInRange in (select cast( DATEADD(day, t.N - 1, StrDate) as
date) from HLD1 s join cteTally t on t.N <= DATEDIFF(day, StrDate, EndDate)
+ 1)
or datepart(dw,@firstWeekDayInRange) in (1,7)
THEN dateadd(DAY,1,@firstWeekDayInRange)
ELSE @firstWeekDayInRange
END
END
WHILE @lastWeekDayInRange in (select cast( DATEADD(day, t.N - 1, StrDate) as
date) as ResultDate
from HLD1 s join cteTally t on t.N <= DATEDIFF(day, StrDate, EndDate) + 1)
or datepart(dw,@lastWeekDayInRange) in (1,7)
BEGIN
SELECT @lastWeekDayInRange =
CASE
WHEN @lastWeekDayInRange in (select cast( DATEADD(day, t.N - 1, StrDate) as
date) from HLD1 s join cteTally t on t.N <= DATEDIFF(day, StrDate, EndDate)
+ 1)
or datepart(dw,@lastWeekDayInRange) in (1,7)
THEN dateadd(DAY,-1,@lastWeekDayInRange)
ELSE @lastWeekDayInRange
END
END
-- add one day to answer (to count Friday) if enddate was on a weekend
SELECT @answer = @answer +
CASE
-- triggered if start and end date are on same weekend
WHEN dateDiff(DAY,@firstWeekDayInRange,@lastWeekDayInRange) < 0 THEN
(@answer * -1)
-- otherwise count the days and substract 2 days per weekend in between dates
ELSE (DateDiff(DAY, @firstWeekDayInRange, @lastWeekDayInRange) - @AddDays)
END
RETURN @answer
END
GO
您可以尝试添加一个 ORDER BY
子句。
CREATE TABLE HLD1
(
HldCode nvarchar(20),
StrDate datetime,
EndDate datetime,
Rmrks nvarchar(50)
)
create unique index id_hld1 on HLD1 (HldCode, StrDate, EndDate);
GO
INSERT INTO HLD1
VALUES ('2016 Holidays', '2016-09-05 00:00:00.000', '2016-09-05 00:00:00.000', 'Labor Day'),
('2016 Holidays', '2016-11-24 00:00:00.000', '2016-11-25 00:00:00.000', 'Thanksgiving'),
('2016 Holidays', '2016-12-26 00:00:00.000', '2016-12-26 00:00:00.000', 'Christmas'),
('2017 Holidays', '2017-01-02 00:00:00.000', '2017-01-02 00:00:00.000', 'New Years Day'),
('2017 Holidays', '2017-05-29 00:00:00.000', '2017-05-29 00:00:00.000', 'Memorial Day'),
('2017 Holidays', '2017-07-04 00:00:00.000', '2017-07-04 00:00:00.000', 'Independence Day');
GO
6 rows affected
DECLARE @StrDate datetime = '2017-01-01';
DECLARE @EndDate datetime = '2018-01-01'
set statistics profile on;
SELECT HldCode, StrDate, EndDate, Rmrks
FROM HLD1
WHERE StrDate >= @StrDate
AND EndDate < @EndDate;
set statistics profile off;
GO
输出:
HldCode | StrDate | EndDate | Rmrks
:------------ | :------------------ | :------------------ | :--------------
2017 Holidays | 02/01/2017 00:00:00 | 02/01/2017 00:00:00 | New Years Day
2017 Holidays | 29/05/2017 00:00:00 | 29/05/2017 00:00:00 | Memorial Day
2017 Holidays | 04/07/2017 00:00:00 | 04/07/2017 00:00:00 | Indepenance Day
Rows | Executes | StmtText | StmtId | NodeId | Parent | PhysicalOp | LogicalOp | Argument | DefinedValues | EstimateRows | EstimateIO | EstimateCPU | AvgRowSize | TotalSubtreeCost | OutputList | Warnings | Type | Parallel | EstimateExecutions
> :--- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -----: | -----: | -----: | :--------- | :--------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------- | :--------- | :---------- | ---------: | :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------- | :------- | :------- | :-----------------
> 3 | 1 | SELECT HldCode, StrDate, EndDate, Rmrks<br>from HLD1<br>WHERE StrDate >= @StrDate<br>AND EndDate < @EndDate | 1 | 1 | 0 | <em>null</em> | <em>null</em> | <em>null</em> | <em>null</em> | 1 | <em>null</em> | <em>null</em> | <em>null</em> | 0.0032886 | <em>null</em> | <em>null</em> | SELECT | False | <em>null</em>
> 3 | 1 | |--Table Scan(OBJECT:([fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1]), WHERE:([fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[StrDate]>=[@StrDate] AND [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[EndDate]<[@EndDate])) | 1 | 2 | 1 | Table Scan | Table Scan | OBJECT:([fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1]), WHERE:([fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[StrDate]>=[@StrDate] AND [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[EndDate]<[@EndDate]) | [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[HldCode], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[StrDate], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[EndDate], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[Rmrks] | 1 | 0.003125 | 0.0001636 | 99 | 0.0032886 | [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[HldCode], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[StrDate], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[EndDate], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[Rmrks] | <em>null</em> | PLAN_ROW | False | 1
DECLARE @StrDate datetime = '2017-01-01';
DECLARE @EndDate datetime = '2018-01-01'
set statistics profile on;
SELECT HldCode, StrDate, EndDate, Rmrks
FROM HLD1
WHERE StrDate >= @StrDate
AND EndDate < @EndDate
ORDER BY HldCode, StrDate, EndDate;
set statistics profile off;
GO
输出:
HldCode | StrDate | EndDate | Rmrks
:------------ | :------------------ | :------------------ | :--------------
2017 Holidays | 02/01/2017 00:00:00 | 02/01/2017 00:00:00 | New Years Day
2017 Holidays | 29/05/2017 00:00:00 | 29/05/2017 00:00:00 | Memorial Day
2017 Holidays | 04/07/2017 00:00:00 | 04/07/2017 00:00:00 | Indepenance Day
Rows | Executes | StmtText | StmtId | NodeId | Parent | PhysicalOp | LogicalOp | Argument | DefinedValues | EstimateRows | EstimateIO | EstimateCPU | AvgRowSize | TotalSubtreeCost | OutputList | Warnings | Type | Parallel | EstimateExecutions
:--- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -----: | -----: | -----: | :----------- | :--------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------- | :--------- | :---------- | ---------: | :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------- | :------- | :------- | :-----------------
3 | 1 | SELECT HldCode, StrDate, EndDate, Rmrks<br>from HLD1<br>WHERE StrDate >= @StrDate<br>AND EndDate < @EndDate<br>ORDER BY HldCode, StrDate, EndDate | 1 | 1 | 0 | <em>null</em> | <em>null</em> | <em>null</em> | <em>null</em> | 1 | <em>null</em> | <em>null</em> | <em>null</em> | 0.00658116 | <em>null</em> | <em>null</em> | SELECT | False | <em>null</em>
3 | 1 | |--Nested Loops(Inner Join, OUTER REFERENCES:([Bmk1000])) | 1 | 2 | 1 | Nested Loops | Inner Join | OUTER REFERENCES:([Bmk1000]) | <em>null</em> | 1 | 0 | 4.18E-06 | 99 | 0.00658116 | [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[HldCode], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[StrDate], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[EndDate], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[Rmrks] | <em>null</em> | PLAN_ROW | False | 1
3 | 1 | |--Index Scan(OBJECT:([fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[id_hld1]), WHERE:([fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[StrDate]>=[@StrDate] AND [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[EndDate]<[@EndDate]) ORDERED FORWARD) | 1 | 3 | 2 | Index Scan | Index Scan | OBJECT:([fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[id_hld1]), WHERE:([fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[StrDate]>=[@StrDate] AND [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[EndDate]<[@EndDate]) ORDERED FORWARD | [Bmk1000], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[HldCode], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[StrDate], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[EndDate] | 1 | 0.003125 | 0.0001636 | 55 | 0.0032886 | [Bmk1000], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[HldCode], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[StrDate], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[EndDate] | <em>null</em> | PLAN_ROW | False | 1
3 | 3 | |--RID Lookup(OBJECT:([fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1]), SEEK:([Bmk1000]=[Bmk1000]) LOOKUP ORDERED FORWARD) | 1 | 5 | 2 | RID Lookup | RID Lookup | OBJECT:([fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1]), SEEK:([Bmk1000]=[Bmk1000]) LOOKUP ORDERED FORWARD | [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[Rmrks] | 1 | 0.003125 | 0.0001581 | 61 | 0.0032831 | [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[Rmrks] | <em>null</em> | PLAN_ROW | False | 1
dbfiddle here
更新
只要你需要存储过程,你都可以试试;
WITH (INDEX(IndexName))
CREATE FUNCTION [dbo].[dateDiffHolidays] (@START DATE, @END DATE)
RETURNS INT
AS
BEGIN
DECLARE @AddDays int;
SELECT @AddDays = (SELECT sum(datediff(dd, StrDate, EndDate) + 1)
FROM hld1 WITH (INDEX(HLD1_PRIMARY))
WHERE StrDate BETWEEN @START AND @END)
+
(SELECT (DATEDIFF(wk, @Start, @End) * 2)
+ (CASE WHEN DATENAME(dw, @Start) = 'Sunday' THEN 1 ELSE 0 END)
+ (CASE WHEN DATENAME(dw, @End) = 'Saturday' THEN 1 ELSE 0 END))
RETURN @AddDays
END
DECLARE @StrDate datetime = '2017-01-01';
DECLARE @EndDate datetime = '2018-01-01';
DECLARE @NumDays int = 0;
set statistics profile on;
EXEC @NumDays = [dbo].[dateDiffHolidays] @StrDate, @EndDate;
set statistics profile off;
SELECT @NumDays;
Rows | Executes | StmtText | StmtId | NodeId | Parent | PhysicalOp | LogicalOp | Argument | DefinedValues | EstimateRows | EstimateIO | EstimateCPU | AvgRowSize | TotalSubtreeCost | OutputList | Warnings | Type | Parallel | EstimateExecutions
:--- | :------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -----: | -----: | -----: | :--------------- | :------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------- | :--------- | :---------- | ---------: | :--------------- | :--------------------------------------------------------------------------------------------------------------------------------- | :------- | :------- | :------- | :-----------------
1 | 1 | SELECT @AddDays = (SELECT sum(datediff(dd, StrDate, EndDate) + 1) <br> FROM hld1 WITH (INDEX(HLD1_PRIMARY))<br> WHERE StrDate BETWEEN @START AND @END) <br> + <br> (SELECT (DATEDIFF(wk, @Start, @End) * 2)<br> + (CASE WHEN DATENAME(dw, @Start) = 'Sunday' THEN 1 ELSE 0 END)<br> + (CASE WHEN DATENAME(dw, @End) = 'Saturday' THEN 1 ELSE 0 END)) | 1 | 1 | 0 | null | null | null | null | 1 | null | null | null | 0.00329658 | null | null | SELECT | False | null
0 | 0 | |--Compute Scalar(DEFINE:([Expr1006]=[Expr1003]+(datediff(week,CONVERT_IMPLICIT(datetimeoffset(7),[@START],0),CONVERT_IMPLICIT(datetimeoffset(7),[@END],0))*(2)+CASE WHEN datename(weekday,[@START])=N'Sunday' THEN (1) ELSE (0) END+CASE WHEN datename(weekday,[@END])=N'Saturday' THEN (1) ELSE (0) END))) | 1 | 2 | 1 | Compute Scalar | Compute Scalar | DEFINE:([Expr1006]=[Expr1003]+(datediff(week,CONVERT_IMPLICIT(datetimeoffset(7),[@START],0),CONVERT_IMPLICIT(datetimeoffset(7),[@END],0))*(2)+CASE WHEN datename(weekday,[@START])=N'Sunday' THEN (1) ELSE (0) END+CASE WHEN datename(weekday,[@END])=N'Saturday' THEN (1) ELSE (0) END)) | [Expr1006]=[Expr1003]+(datediff(week,CONVERT_IMPLICIT(datetimeoffset(7),[@START],0),CONVERT_IMPLICIT(datetimeoffset(7),[@END],0))*(2)+CASE WHEN datename(weekday,[@START])=N'Sunday' THEN (1) ELSE (0) END+CASE WHEN datename(weekday,[@END])=N'Saturday' THEN (1) ELSE (0) END) | 1 | 0 | 1E-07 | 11 | 0.00329658 | [Expr1006] | null | PLAN_ROW | False | 1
0 | 0 | |--Compute Scalar(DEFINE:([Expr1003]=CASE WHEN [Expr1012]=(0) THEN NULL ELSE [Expr1013] END)) | 1 | 3 | 2 | Compute Scalar | Compute Scalar | DEFINE:([Expr1003]=CASE WHEN [Expr1012]=(0) THEN NULL ELSE [Expr1013] END) | [Expr1003]=CASE WHEN [Expr1012]=(0) THEN NULL ELSE [Expr1013] END | 1 | 0 | 0 | 11 | 0.00329648 | [Expr1003] | null | PLAN_ROW | False | 1
1 | 1 | |--Stream Aggregate(DEFINE:([Expr1012]=COUNT_BIG([Expr1007]), [Expr1013]=SUM([Expr1007]))) | 1 | 4 | 3 | Stream Aggregate | Aggregate | null | [Expr1012]=COUNT_BIG([Expr1007]), [Expr1013]=SUM([Expr1007]) | 1 | 0 | 2.3E-06 | 11 | 0.00329648 | [Expr1012], [Expr1013] | null | PLAN_ROW | False | 1
0 | 0 | |--Compute Scalar(DEFINE:([Expr1007]=datediff(day,[fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[StrDate],[fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[EndDate])+(1))) | 1 | 5 | 4 | Compute Scalar | Compute Scalar | DEFINE:([Expr1007]=datediff(day,[fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[StrDate],[fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[EndDate])+(1)) | [Expr1007]=datediff(day,[fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[StrDate],[fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[EndDate])+(1) | 3 | 0 | 3E-07 | 11 | 0.00329418 | [Expr1007] | null | PLAN_ROW | False | 1
3 | 1 | |--Index Scan(OBJECT:([fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[HLD1_PRIMARY]), WHERE:([fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[StrDate]>=[@START] AND [fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[StrDate]<=[@END])) | 1 | 6 | 5 | Index Scan | Index Scan | OBJECT:([fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[HLD1_PRIMARY]), WHERE:([fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[StrDate]>=[@START] AND [fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[StrDate]<=[@END]), FORCEDINDEX | [fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[StrDate], [fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[EndDate] | 3 | 0.003125 | 0.0001636 | 23 | 0.0032886 | [fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[StrDate], [fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[EndDate] | null | PLAN_ROW | False | 1
| (No column name) |
| ---------------: |
| 108 |
dbfiddle here
由于 HLD1_PRIMARY
索引包含以下列:HldCode, StrDate, EndDate
尝试将按 HldCode
过滤的条件添加到聚合 select.
例如,如果您只想包含 HldCode = '2016 Holidays'
和 HldCode = '2017 Holidays'
的行:
CREATE FUNCTION [dbo].[dateDiffHolidays] (
declare @START DATE
declare @END DATE
)
RETURNS INT
AS
BEGIN
SELECT @AddDays = (
select
sum(datediff(dd,strdate,enddate) + 1)
from
hld1
where
HldCode in ('2016 Holidays', '2017 Holidays')
and strdate between @START and @END
)
+
(
SELECT
(DATEDIFF(wk, @Start, @End) * 2)
+(CASE WHEN DATENAME(dw, @Start) = 'Sunday' THEN 1 ELSE 0 END)
+(CASE WHEN DATENAME(dw, @End) = 'Saturday' THEN 1 ELSE 0 END)
)
RETURN @AddDays
END
更新
对于该特定查询,没有比索引查找更好的了。
然后,在查看完整代码后,我怀疑问题不在于该查询,而在于用于计算 @firstWeekDayInRange
和 @lastWeekDayInRange
:
的 while 循环
WHILE @firstWeekDayInRange in (
select
cast(DATEADD(day, t.N - 1, StrDate) as date) as ResultDate
from
HLD1 s
join cteTally t
on t.N <= DATEDIFF(day, StrDate, EndDate) + 1
)
or datepart(dw, @firstWeekDayInRange) in (1,7)
BEGIN
SELECT @firstWeekDayInRange =
CASE
WHEN @firstWeekDayInRange in (
select
cast(DATEADD(day, t.N - 1, StrDate) as date)
from
HLD1 s
join cteTally t on
t.N <= DATEDIFF(day, StrDate, EndDate) + 1
)
or datepart(dw,@firstWeekDayInRange) in (1,7) THEN
dateadd(DAY,1,@firstWeekDayInRange)
ELSE
@firstWeekDayInRange
END
END
最好的情况是这些查询会导致聚集索引扫描,最坏的情况是 Table 扫描 HLD1 table for each 循环迭代,而且我们对 cteTally 了解不多,所以情况可能更糟。
一定要用函数吗?如何创建一个 table 类型的日期日历?
CREATE TABLE #Datecalendar
(DateId DATE PRIMARY KEY
, IsWeekend BIT DEFAULT 0
, IsHoliday BIT DEFAULT 0
);
CREATE TABLE #HLD1
(HldCode nvarchar(20)
, StrDate datetime
, EndDate datetime
, Rmrks nvarchar(50)
);
INSERT INTO #HLD1
VALUES ('2016 Holidays', '2016-09-05 00:00:00.000', '2016-09-05 00:00:00.000', 'Labor Day'),
('2016 Holidays', '2016-11-24 00:00:00.000', '2016-11-25 00:00:00.000', 'Thanksgiving'),
('2016 Holidays', '2016-12-26 00:00:00.000', '2016-12-26 00:00:00.000', 'Christmas'),
('2017 Holidays', '2017-01-02 00:00:00.000', '2017-01-02 00:00:00.000', 'New Years Day'),
('2017 Holidays', '2017-05-29 00:00:00.000', '2017-05-29 00:00:00.000', 'Memorial Day'),
('2017 Holidays', '2017-07-04 00:00:00.000', '2017-07-04 00:00:00.000', 'Independence Day');
WITH cte AS (SELECT CAST('2016-01-01' AS DATE) AS DateId
UNION ALL
SELECT DATEADD(dd, 1, DateId)
FROM cte
WHERE DATEADD(dd, 1, DateId) <= '2019-01-01'
)
INSERT INTO #Datecalendar
(DateId
, IsWeekend
, IsHoliday
)
SELECT DateId
,CASE WHEN DATEPART(WEEKDAY,DateId) =1 OR DATEPART(WEEKDAY,DateId) = 7 THEN 1 ELSE 0 END
,CASE WHEN h.HldCode IS NOT NULL THEN 1 ELSE 0 END
FROM cte cte
LEFT JOIN #HLD1 h
ON cte.DateId BETWEEN h.StrDate AND h.EndDate
OPTION (MAXRECURSION 0);
SELECT COUNT(*)
FROM #Datecalendar
WHERE DateId BETWEEN '2016-05-06' AND '2017-02-24'
AND IsWeekend = 0
AND IsHoliday = 0;
DROP TABLE #HLD1;
DROP TABLE #Datecalendar;
我操作的是SAP B1数据库,不允许修改数据库的结构。
我有一个有 4 列的 table。
Table name: HLD1
Column Name Type
1 HldCode nvarchar
2 StrDate datetime
3 EndDate datetime
4 Rmrks nvarchar
部分数据如下所示:
HldCode StrDate EndDate Rmrks
2016 Holidays 2016-09-05 00:00:00.000 2016-09-05 00:00:00.000 Labor Day
2016 Holidays 2016-11-24 00:00:00.000 2016-11-25 00:00:00.000 Thankgiving
2016 Holidays 2016-12-26 00:00:00.000 2016-12-26 00:00:00.000 Christmas
2017 Holidays 2017-01-02 00:00:00.000 2017-01-02 00:00:00.000 New Years Day
2017 Holidays 2017-05-29 00:00:00.000 2017-05-29 00:00:00.000 Memorial Day
2017 Holidays 2017-07-04 00:00:00.000 2017-07-04 00:00:00.000 Indepenance Day
注意这个table中没有主键。
我创建了一个函数来查找两个日期之间的天数,不包括节假日(由上面的 HLD1 table 提供)和周末。虽然该函数按预期工作,但它使用的每行也需要 ~.75 秒,我们正在尝试 return 50000 行稍后在 Crystal 报告中进行总结。
引用 HLD1 table(并导致执行计划中的聚簇索引)的函数片段如下所示:
CREATE FUNCTION [dbo].[dateDiffHolidays] (
declare @START DATE
declare @END DATE
)
RETURNS INT
AS
BEGIN
SELECT @AddDays =
(select sum(datediff(dd,strdate,enddate) + 1) from hld1
where strdate between @START and @END)
+
(SELECT
(DATEDIFF(wk, @Start, @End) * 2)
+(CASE WHEN DATENAME(dw, @Start) = 'Sunday' THEN 1 ELSE 0 END)
+(CASE WHEN DATENAME(dw, @End) = 'Saturday' THEN 1 ELSE 0 END))
RETURN @AddDays
END
GO
具体来说,第一部分。 @START
和 @END
是传递给函数的参数。
当我检查函数的执行计划时,一切看起来都很快,除了这一段。它给了我以下信息:
我在网上找到的关于如何防止或修复这种减速的所有资源都建议添加索引,而不是引用某些列等,但由于我无法修改数据库,我'我一直找不到任何方法来帮助解决我的情况。
有什么建议吗?
编辑 1:
添加了 Table 来自 SQL 管理的架构信息
编辑 2:
添加了函数的全文,以防万一:
CREATE FUNCTION [dbo].[dateDiffHolidays] (
@startdaytime DATETIME,
@enddaytime DATETIME
)
RETURNS INT
AS
BEGIN
DECLARE @answer INT, @START Date, @END Date, @AddDays int
SET @answer = 0
-- Strip Times
SELECT @START = dateadd(dd,0, datediff(dd,0,@StartDayTime)), @END =
dateadd(dd,0, datediff(dd,0,@EndDayTime))
SELECT @AddDays = (select sum(datediff(dd,strdate,enddatE) + 1) from hld1
where strdate between @START and @END
order by HldCode, StrDate, EndDate) + (
SELECT
(DATEDIFF(wk, @Start, @End) * 2)
+(CASE WHEN DATENAME(dw, @Start) = 'Sunday' THEN 1 ELSE 0 END)
+(CASE WHEN DATENAME(dw, @End) = 'Saturday' THEN 1 ELSE 0 END))
-- handle end conditions
DECLARE @firstWeekDayInRange datetime, @lastWeekDayInRange datetime;
SELECT @firstWeekDayInRange = @START, @lastWeekDayInRange = @END
WHILE @firstWeekDayInRange in (select cast( DATEADD(day, t.N - 1, StrDate)
as date) as ResultDate
from HLD1 s join cteTally t on t.N <= DATEDIFF(day, StrDate, EndDate) + 1)
or datepart(dw,@firstWeekDayInRange) in (1,7)
BEGIN
SELECT @firstWeekDayInRange =
CASE
WHEN @firstWeekDayInRange in (select cast( DATEADD(day, t.N - 1, StrDate) as
date) from HLD1 s join cteTally t on t.N <= DATEDIFF(day, StrDate, EndDate)
+ 1)
or datepart(dw,@firstWeekDayInRange) in (1,7)
THEN dateadd(DAY,1,@firstWeekDayInRange)
ELSE @firstWeekDayInRange
END
END
WHILE @lastWeekDayInRange in (select cast( DATEADD(day, t.N - 1, StrDate) as
date) as ResultDate
from HLD1 s join cteTally t on t.N <= DATEDIFF(day, StrDate, EndDate) + 1)
or datepart(dw,@lastWeekDayInRange) in (1,7)
BEGIN
SELECT @lastWeekDayInRange =
CASE
WHEN @lastWeekDayInRange in (select cast( DATEADD(day, t.N - 1, StrDate) as
date) from HLD1 s join cteTally t on t.N <= DATEDIFF(day, StrDate, EndDate)
+ 1)
or datepart(dw,@lastWeekDayInRange) in (1,7)
THEN dateadd(DAY,-1,@lastWeekDayInRange)
ELSE @lastWeekDayInRange
END
END
-- add one day to answer (to count Friday) if enddate was on a weekend
SELECT @answer = @answer +
CASE
-- triggered if start and end date are on same weekend
WHEN dateDiff(DAY,@firstWeekDayInRange,@lastWeekDayInRange) < 0 THEN
(@answer * -1)
-- otherwise count the days and substract 2 days per weekend in between dates
ELSE (DateDiff(DAY, @firstWeekDayInRange, @lastWeekDayInRange) - @AddDays)
END
RETURN @answer
END
GO
您可以尝试添加一个 ORDER BY
子句。
CREATE TABLE HLD1
(
HldCode nvarchar(20),
StrDate datetime,
EndDate datetime,
Rmrks nvarchar(50)
)
create unique index id_hld1 on HLD1 (HldCode, StrDate, EndDate);
GO
INSERT INTO HLD1
VALUES ('2016 Holidays', '2016-09-05 00:00:00.000', '2016-09-05 00:00:00.000', 'Labor Day'),
('2016 Holidays', '2016-11-24 00:00:00.000', '2016-11-25 00:00:00.000', 'Thanksgiving'),
('2016 Holidays', '2016-12-26 00:00:00.000', '2016-12-26 00:00:00.000', 'Christmas'),
('2017 Holidays', '2017-01-02 00:00:00.000', '2017-01-02 00:00:00.000', 'New Years Day'),
('2017 Holidays', '2017-05-29 00:00:00.000', '2017-05-29 00:00:00.000', 'Memorial Day'),
('2017 Holidays', '2017-07-04 00:00:00.000', '2017-07-04 00:00:00.000', 'Independence Day');
GO
6 rows affected
DECLARE @StrDate datetime = '2017-01-01';
DECLARE @EndDate datetime = '2018-01-01'
set statistics profile on;
SELECT HldCode, StrDate, EndDate, Rmrks
FROM HLD1
WHERE StrDate >= @StrDate
AND EndDate < @EndDate;
set statistics profile off;
GO
输出:
HldCode | StrDate | EndDate | Rmrks
:------------ | :------------------ | :------------------ | :--------------
2017 Holidays | 02/01/2017 00:00:00 | 02/01/2017 00:00:00 | New Years Day
2017 Holidays | 29/05/2017 00:00:00 | 29/05/2017 00:00:00 | Memorial Day
2017 Holidays | 04/07/2017 00:00:00 | 04/07/2017 00:00:00 | Indepenance Day
Rows | Executes | StmtText | StmtId | NodeId | Parent | PhysicalOp | LogicalOp | Argument | DefinedValues | EstimateRows | EstimateIO | EstimateCPU | AvgRowSize | TotalSubtreeCost | OutputList | Warnings | Type | Parallel | EstimateExecutions
> :--- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -----: | -----: | -----: | :--------- | :--------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------- | :--------- | :---------- | ---------: | :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------- | :------- | :------- | :-----------------
> 3 | 1 | SELECT HldCode, StrDate, EndDate, Rmrks<br>from HLD1<br>WHERE StrDate >= @StrDate<br>AND EndDate < @EndDate | 1 | 1 | 0 | <em>null</em> | <em>null</em> | <em>null</em> | <em>null</em> | 1 | <em>null</em> | <em>null</em> | <em>null</em> | 0.0032886 | <em>null</em> | <em>null</em> | SELECT | False | <em>null</em>
> 3 | 1 | |--Table Scan(OBJECT:([fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1]), WHERE:([fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[StrDate]>=[@StrDate] AND [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[EndDate]<[@EndDate])) | 1 | 2 | 1 | Table Scan | Table Scan | OBJECT:([fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1]), WHERE:([fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[StrDate]>=[@StrDate] AND [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[EndDate]<[@EndDate]) | [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[HldCode], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[StrDate], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[EndDate], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[Rmrks] | 1 | 0.003125 | 0.0001636 | 99 | 0.0032886 | [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[HldCode], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[StrDate], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[EndDate], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[Rmrks] | <em>null</em> | PLAN_ROW | False | 1
DECLARE @StrDate datetime = '2017-01-01';
DECLARE @EndDate datetime = '2018-01-01'
set statistics profile on;
SELECT HldCode, StrDate, EndDate, Rmrks
FROM HLD1
WHERE StrDate >= @StrDate
AND EndDate < @EndDate
ORDER BY HldCode, StrDate, EndDate;
set statistics profile off;
GO
输出:
HldCode | StrDate | EndDate | Rmrks
:------------ | :------------------ | :------------------ | :--------------
2017 Holidays | 02/01/2017 00:00:00 | 02/01/2017 00:00:00 | New Years Day
2017 Holidays | 29/05/2017 00:00:00 | 29/05/2017 00:00:00 | Memorial Day
2017 Holidays | 04/07/2017 00:00:00 | 04/07/2017 00:00:00 | Indepenance Day
Rows | Executes | StmtText | StmtId | NodeId | Parent | PhysicalOp | LogicalOp | Argument | DefinedValues | EstimateRows | EstimateIO | EstimateCPU | AvgRowSize | TotalSubtreeCost | OutputList | Warnings | Type | Parallel | EstimateExecutions
:--- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -----: | -----: | -----: | :----------- | :--------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------- | :--------- | :---------- | ---------: | :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------- | :------- | :------- | :-----------------
3 | 1 | SELECT HldCode, StrDate, EndDate, Rmrks<br>from HLD1<br>WHERE StrDate >= @StrDate<br>AND EndDate < @EndDate<br>ORDER BY HldCode, StrDate, EndDate | 1 | 1 | 0 | <em>null</em> | <em>null</em> | <em>null</em> | <em>null</em> | 1 | <em>null</em> | <em>null</em> | <em>null</em> | 0.00658116 | <em>null</em> | <em>null</em> | SELECT | False | <em>null</em>
3 | 1 | |--Nested Loops(Inner Join, OUTER REFERENCES:([Bmk1000])) | 1 | 2 | 1 | Nested Loops | Inner Join | OUTER REFERENCES:([Bmk1000]) | <em>null</em> | 1 | 0 | 4.18E-06 | 99 | 0.00658116 | [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[HldCode], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[StrDate], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[EndDate], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[Rmrks] | <em>null</em> | PLAN_ROW | False | 1
3 | 1 | |--Index Scan(OBJECT:([fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[id_hld1]), WHERE:([fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[StrDate]>=[@StrDate] AND [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[EndDate]<[@EndDate]) ORDERED FORWARD) | 1 | 3 | 2 | Index Scan | Index Scan | OBJECT:([fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[id_hld1]), WHERE:([fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[StrDate]>=[@StrDate] AND [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[EndDate]<[@EndDate]) ORDERED FORWARD | [Bmk1000], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[HldCode], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[StrDate], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[EndDate] | 1 | 0.003125 | 0.0001636 | 55 | 0.0032886 | [Bmk1000], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[HldCode], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[StrDate], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[EndDate] | <em>null</em> | PLAN_ROW | False | 1
3 | 3 | |--RID Lookup(OBJECT:([fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1]), SEEK:([Bmk1000]=[Bmk1000]) LOOKUP ORDERED FORWARD) | 1 | 5 | 2 | RID Lookup | RID Lookup | OBJECT:([fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1]), SEEK:([Bmk1000]=[Bmk1000]) LOOKUP ORDERED FORWARD | [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[Rmrks] | 1 | 0.003125 | 0.0001581 | 61 | 0.0032831 | [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[Rmrks] | <em>null</em> | PLAN_ROW | False | 1
dbfiddle here
更新
只要你需要存储过程,你都可以试试;
WITH (INDEX(IndexName))
CREATE FUNCTION [dbo].[dateDiffHolidays] (@START DATE, @END DATE) RETURNS INT AS BEGIN DECLARE @AddDays int; SELECT @AddDays = (SELECT sum(datediff(dd, StrDate, EndDate) + 1) FROM hld1 WITH (INDEX(HLD1_PRIMARY)) WHERE StrDate BETWEEN @START AND @END) + (SELECT (DATEDIFF(wk, @Start, @End) * 2) + (CASE WHEN DATENAME(dw, @Start) = 'Sunday' THEN 1 ELSE 0 END) + (CASE WHEN DATENAME(dw, @End) = 'Saturday' THEN 1 ELSE 0 END)) RETURN @AddDays END
DECLARE @StrDate datetime = '2017-01-01'; DECLARE @EndDate datetime = '2018-01-01'; DECLARE @NumDays int = 0; set statistics profile on; EXEC @NumDays = [dbo].[dateDiffHolidays] @StrDate, @EndDate; set statistics profile off; SELECT @NumDays;
Rows | Executes | StmtText | StmtId | NodeId | Parent | PhysicalOp | LogicalOp | Argument | DefinedValues | EstimateRows | EstimateIO | EstimateCPU | AvgRowSize | TotalSubtreeCost | OutputList | Warnings | Type | Parallel | EstimateExecutions :--- | :------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -----: | -----: | -----: | :--------------- | :------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------- | :--------- | :---------- | ---------: | :--------------- | :--------------------------------------------------------------------------------------------------------------------------------- | :------- | :------- | :------- | :----------------- 1 | 1 | SELECT @AddDays = (SELECT sum(datediff(dd, StrDate, EndDate) + 1) <br> FROM hld1 WITH (INDEX(HLD1_PRIMARY))<br> WHERE StrDate BETWEEN @START AND @END) <br> + <br> (SELECT (DATEDIFF(wk, @Start, @End) * 2)<br> + (CASE WHEN DATENAME(dw, @Start) = 'Sunday' THEN 1 ELSE 0 END)<br> + (CASE WHEN DATENAME(dw, @End) = 'Saturday' THEN 1 ELSE 0 END)) | 1 | 1 | 0 | null | null | null | null | 1 | null | null | null | 0.00329658 | null | null | SELECT | False | null 0 | 0 | |--Compute Scalar(DEFINE:([Expr1006]=[Expr1003]+(datediff(week,CONVERT_IMPLICIT(datetimeoffset(7),[@START],0),CONVERT_IMPLICIT(datetimeoffset(7),[@END],0))*(2)+CASE WHEN datename(weekday,[@START])=N'Sunday' THEN (1) ELSE (0) END+CASE WHEN datename(weekday,[@END])=N'Saturday' THEN (1) ELSE (0) END))) | 1 | 2 | 1 | Compute Scalar | Compute Scalar | DEFINE:([Expr1006]=[Expr1003]+(datediff(week,CONVERT_IMPLICIT(datetimeoffset(7),[@START],0),CONVERT_IMPLICIT(datetimeoffset(7),[@END],0))*(2)+CASE WHEN datename(weekday,[@START])=N'Sunday' THEN (1) ELSE (0) END+CASE WHEN datename(weekday,[@END])=N'Saturday' THEN (1) ELSE (0) END)) | [Expr1006]=[Expr1003]+(datediff(week,CONVERT_IMPLICIT(datetimeoffset(7),[@START],0),CONVERT_IMPLICIT(datetimeoffset(7),[@END],0))*(2)+CASE WHEN datename(weekday,[@START])=N'Sunday' THEN (1) ELSE (0) END+CASE WHEN datename(weekday,[@END])=N'Saturday' THEN (1) ELSE (0) END) | 1 | 0 | 1E-07 | 11 | 0.00329658 | [Expr1006] | null | PLAN_ROW | False | 1 0 | 0 | |--Compute Scalar(DEFINE:([Expr1003]=CASE WHEN [Expr1012]=(0) THEN NULL ELSE [Expr1013] END)) | 1 | 3 | 2 | Compute Scalar | Compute Scalar | DEFINE:([Expr1003]=CASE WHEN [Expr1012]=(0) THEN NULL ELSE [Expr1013] END) | [Expr1003]=CASE WHEN [Expr1012]=(0) THEN NULL ELSE [Expr1013] END | 1 | 0 | 0 | 11 | 0.00329648 | [Expr1003] | null | PLAN_ROW | False | 1 1 | 1 | |--Stream Aggregate(DEFINE:([Expr1012]=COUNT_BIG([Expr1007]), [Expr1013]=SUM([Expr1007]))) | 1 | 4 | 3 | Stream Aggregate | Aggregate | null | [Expr1012]=COUNT_BIG([Expr1007]), [Expr1013]=SUM([Expr1007]) | 1 | 0 | 2.3E-06 | 11 | 0.00329648 | [Expr1012], [Expr1013] | null | PLAN_ROW | False | 1 0 | 0 | |--Compute Scalar(DEFINE:([Expr1007]=datediff(day,[fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[StrDate],[fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[EndDate])+(1))) | 1 | 5 | 4 | Compute Scalar | Compute Scalar | DEFINE:([Expr1007]=datediff(day,[fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[StrDate],[fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[EndDate])+(1)) | [Expr1007]=datediff(day,[fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[StrDate],[fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[EndDate])+(1) | 3 | 0 | 3E-07 | 11 | 0.00329418 | [Expr1007] | null | PLAN_ROW | False | 1 3 | 1 | |--Index Scan(OBJECT:([fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[HLD1_PRIMARY]), WHERE:([fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[StrDate]>=[@START] AND [fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[StrDate]<=[@END])) | 1 | 6 | 5 | Index Scan | Index Scan | OBJECT:([fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[HLD1_PRIMARY]), WHERE:([fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[StrDate]>=[@START] AND [fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[StrDate]<=[@END]), FORCEDINDEX | [fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[StrDate], [fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[EndDate] | 3 | 0.003125 | 0.0001636 | 23 | 0.0032886 | [fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[StrDate], [fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[EndDate] | null | PLAN_ROW | False | 1 | (No column name) | | ---------------: | | 108 |
dbfiddle here
由于 HLD1_PRIMARY
索引包含以下列:HldCode, StrDate, EndDate
尝试将按 HldCode
过滤的条件添加到聚合 select.
例如,如果您只想包含 HldCode = '2016 Holidays'
和 HldCode = '2017 Holidays'
的行:
CREATE FUNCTION [dbo].[dateDiffHolidays] (
declare @START DATE
declare @END DATE
)
RETURNS INT
AS
BEGIN
SELECT @AddDays = (
select
sum(datediff(dd,strdate,enddate) + 1)
from
hld1
where
HldCode in ('2016 Holidays', '2017 Holidays')
and strdate between @START and @END
)
+
(
SELECT
(DATEDIFF(wk, @Start, @End) * 2)
+(CASE WHEN DATENAME(dw, @Start) = 'Sunday' THEN 1 ELSE 0 END)
+(CASE WHEN DATENAME(dw, @End) = 'Saturday' THEN 1 ELSE 0 END)
)
RETURN @AddDays
END
更新
对于该特定查询,没有比索引查找更好的了。
然后,在查看完整代码后,我怀疑问题不在于该查询,而在于用于计算 @firstWeekDayInRange
和 @lastWeekDayInRange
:
WHILE @firstWeekDayInRange in (
select
cast(DATEADD(day, t.N - 1, StrDate) as date) as ResultDate
from
HLD1 s
join cteTally t
on t.N <= DATEDIFF(day, StrDate, EndDate) + 1
)
or datepart(dw, @firstWeekDayInRange) in (1,7)
BEGIN
SELECT @firstWeekDayInRange =
CASE
WHEN @firstWeekDayInRange in (
select
cast(DATEADD(day, t.N - 1, StrDate) as date)
from
HLD1 s
join cteTally t on
t.N <= DATEDIFF(day, StrDate, EndDate) + 1
)
or datepart(dw,@firstWeekDayInRange) in (1,7) THEN
dateadd(DAY,1,@firstWeekDayInRange)
ELSE
@firstWeekDayInRange
END
END
最好的情况是这些查询会导致聚集索引扫描,最坏的情况是 Table 扫描 HLD1 table for each 循环迭代,而且我们对 cteTally 了解不多,所以情况可能更糟。
一定要用函数吗?如何创建一个 table 类型的日期日历?
CREATE TABLE #Datecalendar
(DateId DATE PRIMARY KEY
, IsWeekend BIT DEFAULT 0
, IsHoliday BIT DEFAULT 0
);
CREATE TABLE #HLD1
(HldCode nvarchar(20)
, StrDate datetime
, EndDate datetime
, Rmrks nvarchar(50)
);
INSERT INTO #HLD1
VALUES ('2016 Holidays', '2016-09-05 00:00:00.000', '2016-09-05 00:00:00.000', 'Labor Day'),
('2016 Holidays', '2016-11-24 00:00:00.000', '2016-11-25 00:00:00.000', 'Thanksgiving'),
('2016 Holidays', '2016-12-26 00:00:00.000', '2016-12-26 00:00:00.000', 'Christmas'),
('2017 Holidays', '2017-01-02 00:00:00.000', '2017-01-02 00:00:00.000', 'New Years Day'),
('2017 Holidays', '2017-05-29 00:00:00.000', '2017-05-29 00:00:00.000', 'Memorial Day'),
('2017 Holidays', '2017-07-04 00:00:00.000', '2017-07-04 00:00:00.000', 'Independence Day');
WITH cte AS (SELECT CAST('2016-01-01' AS DATE) AS DateId
UNION ALL
SELECT DATEADD(dd, 1, DateId)
FROM cte
WHERE DATEADD(dd, 1, DateId) <= '2019-01-01'
)
INSERT INTO #Datecalendar
(DateId
, IsWeekend
, IsHoliday
)
SELECT DateId
,CASE WHEN DATEPART(WEEKDAY,DateId) =1 OR DATEPART(WEEKDAY,DateId) = 7 THEN 1 ELSE 0 END
,CASE WHEN h.HldCode IS NOT NULL THEN 1 ELSE 0 END
FROM cte cte
LEFT JOIN #HLD1 h
ON cte.DateId BETWEEN h.StrDate AND h.EndDate
OPTION (MAXRECURSION 0);
SELECT COUNT(*)
FROM #Datecalendar
WHERE DateId BETWEEN '2016-05-06' AND '2017-02-24'
AND IsWeekend = 0
AND IsHoliday = 0;
DROP TABLE #HLD1;
DROP TABLE #Datecalendar;