【发布时间】:2017-09-30 07:44:27
【问题描述】:
我已经编写了以下查询...但是由于几个原因,我很难将其转换为函数...(1) 因为它包含动态 SQL,我不确定函数是否允许动态 SQL,以及 (2) 因为我使用临时表来存储输出数据。我尝试将其转换为表变量,但没有成功。查询的重点是获取任意两个日期时间范围和一个增量类型(月、日、年、小时等),并输出一个包含所提供范围的日期范围表。
查询的原因是因为我要为报表服务器写很多报表,而且很多时候报表请求要按小时、天、周、月等细分,而我必须不断地编写相同的一遍又一遍地查询以生成日期范围表。我使用这些表左连接,以便我的最终输出不会跳过任何日期范围,以防该范围没有数据,它仍然会填充零并正确绘制图表。
我不认为有任何方法可以使此代码在函数中工作,除非有完全不同的方式来编写它(编辑,请参阅下面的更新)...我敢肯定我可以走很长的路拥有某种长 case 语句的路线,而不是每个增量类型(秒、分钟、小时、日、周、月、季度、年),然后我可以消除动态 SQL。也许我可以使用递归 CTE 并消除 while 循环。但我对这些没有任何经验。
代码如下:
DECLARE @DateFrom DATETIME = '2017-01-01',
@DateTo DATETIME = '2017-02-02 23:59:59.997',
@Increment VARCHAR(20) = 'mm'
DECLARE @SQL NVARCHAR(MAX) = NULL
IF OBJECT_ID('tempdb..#DateRange') IS NOT NULL DROP TABLE #DateRange --SELECT * FROM #DateRange
CREATE TABLE #DateRange (BeginDate DATETIME, EndDate DATETIME)
SELECT @SQL = '
DECLARE @TargetDate DATETIME = ''' + CONVERT(VARCHAR, @DateFrom, 121) + '''
IF DATEDIFF('+ @Increment +', ''' + CONVERT(VARCHAR, @DateFrom, 121) + ''', ''' + CONVERT(VARCHAR, @DateTo, 121) + ''') > 2000 RETURN
WHILE (1=1)
BEGIN
INSERT INTO #DateRange
SELECT BeginDate = DATEADD('+ @Increment +', DATEDIFF('+ @Increment +', 0, @TargetDate) , 0)
, EndDate = DATEADD(ms, -3, DATEADD('+ @Increment +', DATEDIFF('+ @Increment +', 0, @TargetDate) + 1, 0))
SET @TargetDate = DATEADD('+ @Increment +', 1, @TargetDate)
IF @TargetDate > ''' + CONVERT(VARCHAR, @DateTo, 121) + ''' BREAK
END'
EXEC sp_executesql @SQL
SELECT * FROM #DateRange
编辑:这是一个修改版本,不确定这是否会被认为“更好”,但至少它消除了动态 SQL,并且我可以将它用作函数。
EDIT2:由于运行速度更快,我将限制更改为返回 5000 条记录。我还改变了它的运行方向,所以它从 DateTo 开始并向后工作。这样,当它达到极限时,它只会停止返回到目前为止。我还添加了一些安全性,例如检查 Increment (不确定还有什么可以调用它?)选项值。我讨厌那个巨大的案例陈述,但我看不出有任何其他方法可以做到这一点。
CREATE FUNCTION dbo.uf_DateRange (
@DateFrom DATETIME,
@DateTo DATETIME,
@Increment VARCHAR(20)
)
RETURNS @Return TABLE (
BeginDate DATETIME,
EndDate DATETIME
)
AS
BEGIN
IF @Increment NOT IN ('year','yy','yyyy','quarter','qq','q','month','mm','m','week','wk','ww','day','dd','d','hour','hh','minute','mi','n','second','ss','s') RETURN
DECLARE @TargetDate DATETIME = @DateTo,
@LoopLimit INT = 5000,
@Counter INT = 0
DECLARE @DateRange TABLE (BeginDate DATETIME, EndDate DATETIME)
WHILE (@Counter < @LoopLimit)
BEGIN
INSERT INTO @Return (BeginDate, EndDate)
SELECT BeginDate = CASE WHEN @Increment IN ('year' , 'yy', 'yyyy') THEN DATEADD(yy, DATEDIFF(yy, 0, @TargetDate), 0)
WHEN @Increment IN ('quarter', 'qq', 'q' ) THEN DATEADD(qq, DATEDIFF(qq, 0, @TargetDate), 0)
WHEN @Increment IN ('month' , 'mm', 'm' ) THEN DATEADD(mm, DATEDIFF(mm, 0, @TargetDate), 0)
WHEN @Increment IN ('week' , 'wk', 'ww' ) THEN DATEADD(ww, DATEDIFF(ww, 0, @TargetDate), 0)
WHEN @Increment IN ('day' , 'dd', 'd' ) THEN DATEADD(dd, DATEDIFF(dd, 0, @TargetDate), 0)
WHEN @Increment IN ('hour' , 'hh' ) THEN DATEADD(hh, DATEDIFF(hh, 0, @TargetDate), 0)
WHEN @Increment IN ('minute' , 'mi', 'n' ) THEN DATEADD(mi, DATEDIFF(mi, 0, @TargetDate), 0)
WHEN @Increment IN ('second' , 'ss', 's' ) THEN DATEADD(ss, DATEDIFF(ss, 0, @TargetDate), 0)
END
, EndDate = DATEADD(ms, -3,
CASE WHEN @Increment IN ('year' , 'yy', 'yyyy') THEN DATEADD(yy, DATEDIFF(yy, 0, @TargetDate) + 1, 0)
WHEN @Increment IN ('quarter', 'qq', 'q' ) THEN DATEADD(qq, DATEDIFF(qq, 0, @TargetDate) + 1, 0)
WHEN @Increment IN ('month' , 'mm', 'm' ) THEN DATEADD(mm, DATEDIFF(mm, 0, @TargetDate) + 1, 0)
WHEN @Increment IN ('week' , 'wk', 'ww' ) THEN DATEADD(ww, DATEDIFF(ww, 0, @TargetDate) + 1, 0)
WHEN @Increment IN ('day' , 'dd', 'd' ) THEN DATEADD(dd, DATEDIFF(dd, 0, @TargetDate) + 1, 0)
WHEN @Increment IN ('hour' , 'hh' ) THEN DATEADD(hh, DATEDIFF(hh, 0, @TargetDate) + 1, 0)
WHEN @Increment IN ('minute' , 'mi', 'n' ) THEN DATEADD(mi, DATEDIFF(mi, 0, @TargetDate) + 1, 0)
WHEN @Increment IN ('second' , 'ss', 's' ) THEN DATEADD(ss, DATEDIFF(ss, 0, @TargetDate) + 1, 0)
END)
SET @TargetDate = CASE WHEN @Increment IN ('year' , 'yy', 'yyyy') THEN DATEADD(yy, -1, @TargetDate)
WHEN @Increment IN ('quarter', 'qq', 'q' ) THEN DATEADD(qq, -1, @TargetDate)
WHEN @Increment IN ('month' , 'mm', 'm' ) THEN DATEADD(mm, -1, @TargetDate)
WHEN @Increment IN ('week' , 'wk', 'ww' ) THEN DATEADD(ww, -1, @TargetDate)
WHEN @Increment IN ('day' , 'dd', 'd' ) THEN DATEADD(dd, -1, @TargetDate)
WHEN @Increment IN ('hour' , 'hh' ) THEN DATEADD(hh, -1, @TargetDate)
WHEN @Increment IN ('minute' , 'mi', 'n' ) THEN DATEADD(mi, -1, @TargetDate)
WHEN @Increment IN ('second' , 'ss', 's' ) THEN DATEADD(ss, -1, @TargetDate)
END
IF @TargetDate > @DateTo BREAK
SET @Counter += 1
END
RETURN
END
GO
【问题讨论】: