T-SQL中日期的最小/最大函数

时间:2012-02-18 03:48:48

标签: sql-server-2008 tsql

我创建了以下函数来确定两个日期之间的MAX日期。在函数注释中运行其中一个SELECT语句大约需要00.030到00.050秒。

有更好的表现和清洁方式吗?

/* Returns the greater of two dates.

    SELECT dbo.fnMaxDate(NULL      , NULL)
    SELECT dbo.fnMaxDate('1/1/2011', NULL)
    SELECT dbo.fnMaxDate(NULL      , '1/1/2011')
    SELECT dbo.fnMaxDate('1/1/2011', '1/1/2011')
    SELECT dbo.fnMaxDate('1/2/2011', '1/1/2011')
    SELECT dbo.fnMaxDate('1/1/2011', '1/2/2011')

*/
ALTER FUNCTION dbo.fnMaxDate 
(
    @Date1 DATETIME,
    @Date2 DATETIME
)
RETURNS datetime
AS
BEGIN

    DECLARE @Result DATETIME

    IF @Date1 IS NULL AND @Date2 IS NULL

        SET @Result = NULL;

    ELSE IF @Date1 IS NULL

        SET @Result = @Date2

    ELSE IF @Date2 IS NULL

        SET @Result = @Date1

    ELSE
        IF @Date1 >= @Date2

            SET @Result = @Date1

        ELSE

            SET @Result = @Date2

    RETURN @Result

END

5 个答案:

答案 0 :(得分:5)

ALTER FUNCTION dbo.fnMaxDate 
(
    @Date1 DATETIME,
    @Date2 DATETIME
)
RETURNS datetime
AS
BEGIN

    RETURN
        CASE
            WHEN ISNULL(@Date1, @Date2) > ISNULL(@Date2, @Date1)
            THEN ISNULL(@Date1, @Date2)
            ELSE ISNULL(@Date2, @Date1)
        END
END

答案 1 :(得分:2)

我发现case结构的运行速度比函数调用快三倍。

declare @d table(d1 date, d2 date);
insert into @d values(null,null)
, ('2/19/2012',null)
, (null,'2/19/2012')
, ('2/1/2012','2/15/2012')
, ('2/1/2012','1/15/2012');


declare @md date
, @i int=1
, @ts datetime2
, @ms1 int
, @ms2 int;

-- function
select @ts=GETDATE();
while @i<10000 begin select @md=dbo.fnMaxDate(d1,d2) from @d; set @i+=1; end
select @ms1=DATEDIFF(ms,@ts,GETDATE());

-- recommended case structure
set @i=1;
select @ts=GETDATE();
while @i<10000 begin select @md=case when ISNULL(d1,d2)<ISNULL(d2,d1) then ISNULL(d2,d1) else ISNULL(d1,d2) end from @d; set @i+=1; end
select @ms2=DATEDIFF(ms,@ts,GETDATE());
select [Function Run Time (ms)]=@ms1, [Case Run Time (ms)]=@ms2
go

结果:

Function Run Time (ms) Case Run Time (ms)
---------------------- ------------------
940                    296

答案 2 :(得分:1)

    /* Returns the greater of two dates.

        SELECT dbo.fnMaxDate(NULL      , NULL)
        SELECT dbo.fnMaxDate('1/1/2011', NULL)
        SELECT dbo.fnMaxDate(NULL      , '1/1/2011')
        SELECT dbo.fnMaxDate('1/1/2011', '1/1/2011')
        SELECT dbo.fnMaxDate('1/2/2011', '1/1/2011')
        SELECT dbo.fnMaxDate('1/1/2011', '1/2/2011')

    */
ALTER FUNCTION dbo.fnMaxDate 
(
    @Date1 DATETIME,
    @Date2 DATETIME
)
RETURNS datetime
AS
BEGIN

   DECLARE @Result datetime

   SELECT TOP 1 @Result =  T.DateValue
   FROM
     (
     SELECT @Date1 DateValue
     UNION ALL
     SELECT @Date2 DateValue
     ) T
   ORDER BY
     T.DateValue DESC

    RETURN @Result

END

答案 3 :(得分:1)

我知道这是一个老问题,已经回答了,但是看到已经有一个测试脚本,我无法抗拒:)

我创建的标量函数优于所有其他函数测试here,并且表值函数甚至比标量UDF更快。

可比较的结果(对AMissico的测试)

TVF = 0.022253313291
SVF = 0.04627526226

功能

/* Returns the greater of two dates.

    SELECT dbo.fnMaxDate(NULL      , NULL)
    SELECT dbo.fnMaxDate('1/1/2011', NULL)
    SELECT dbo.fnMaxDate(NULL      , '1/1/2011')
    SELECT dbo.fnMaxDate('1/1/2011', '1/1/2011')
    SELECT dbo.fnMaxDate('1/2/2011', '1/1/2011')
    SELECT dbo.fnMaxDate('1/1/2011', '1/2/2011')

*/
CREATE FUNCTION dbo.svfMaxDate 
(
    @Date1 DATETIME,
    @Date2 DATETIME
)
RETURNS datetime
AS
BEGIN

    RETURN coalesce(dateadd(dd, ((datediff(dd, 0, @Date1) + datediff(dd, 0, @Date2)) + abs(datediff(dd, 0, @Date1) - datediff(dd, 0, @Date2))) / 2, 0), @Date1, @Date2)

END
GO

CREATE FUNCTION dbo.tvfMaxDate
(
    @Date1 DATETIME,
    @Date2 DATETIME
)
RETURNS TABLE WITH SCHEMABINDING AS

    RETURN SELECT [MaxDate] = coalesce(dateadd(dd, ((datediff(dd, 0, @Date1) + datediff(dd, 0, @Date2)) + abs(datediff(dd, 0, @Date1) - datediff(dd, 0, @Date2))) / 2, 0), @Date1, @Date2)
    ;
GO

我还使用了AMissico的简单测试脚本来测试它,但只是考虑将我的函数版本和CASE语句用作参考点/基线:

测试结果

Case(ms)    svfMaxDate  tvfMaxDate
0.01343000  0.03907000  0.01837000

将测试结果标准化为AMissico的基线

Baseline Case(ms) / This Test Case(ms) = Normalisation Factor
0.01616 / 0.01334000 = 1.21139430

svfMaxDate * Normalisation Factor= Comparable Result
0.03820000 * 1.21139430 = 0.04627526226

tvfMaxDate * Normalisation Factor= Comparable Result
0.01837 * 1.21139430 = 0.022253313291

测试脚本

declare @d table(d1 date, d2 date); 
insert into @d values(null,null) 
, ('2/19/2012',null) 
, (null,'2/19/2012') 
, ('2/1/2012','2/15/2012') 
, ('2/1/2012','1/15/2012'); 


declare @md date 
, @i int=1 
, @ts datetime2 
, @ms0 int 
, @ms1 int 
, @ms2 int 
;

-- case structure 
set @i=1; 
select @ts=GETDATE(); 
while @i<100000 begin select @md=case when ISNULL(d1,d2)<ISNULL(d2,d1) then ISNULL(d2,d1) else ISNULL(d1,d2) end from @d; set @i+=1; end 
select @ms0=DATEDIFF(ms,@ts,GETDATE()); 

-- svfMaxDate, Arithmetic
set @i=1; 
select @ts=GETDATE(); 
while @i<100000 begin select @md=dbo.svfMaxDate(d1,d2) from @d; set @i+=1; end 
select @ms1=DATEDIFF(ms,@ts,GETDATE()); 

-- tvfMaxDate, Arithmetic in TVF with CROSS APPLY
set @i=1; 
select @ts=GETDATE(); 
while @i<100000 begin select @md = tf.MaxDate from @d cross apply dbo.tvfMaxDate(d1,d2) as tf; set @i+=1; end 
select @ms2=DATEDIFF(ms,@ts,GETDATE()); 

select [Case(ms)]=@ms0/100000.0, fnMaxDate=@ms1/100000.0, tvfMaxDate=@ms2/100000.0
go 

TVF的归功于Jeff Moden,以下链接被用作构建它的参考:How to Make Scalar UDFs Run Faster (SQL Spackle)

答案 4 :(得分:0)

根据我的简单性能测试,我将使用原始功能的略微修改版本(见下文)。

                     IS NULL    CASE        IS NOT NULL  UNION
Case(ms)  Empty Func fnMaxDate1 fnMaxDate2  fnMaxDate3   fnMaxDate4
0.01616   0.0446     0.0518     0.04934     0.05036      0.06177

最快的函数方法是CASE语句,但只有大约。 0.003ms最慢的功能是使用“Pittsburgh DBA”的SELECT UNION。我改变了我的函数的顺序,首先测试最常见的结果,这将测试两个参数为IS NOT NULL。这种逻辑变化使性能与CASE功能相当。

因此,为了清晰显示IS NOT NULL功能,我放弃了0.001ms的性能(见下文)。

使用以下脚本:

declare @d table(d1 date, d2 date); 
insert into @d values(null,null) 
, ('2/19/2012',null) 
, (null,'2/19/2012') 
, ('2/1/2012','2/15/2012') 
, ('2/1/2012','1/15/2012'); 


declare @md date 
, @i int=1 
, @ts datetime2 
, @ms0 int 
, @ms1 int 
, @ms3 int 
, @ms2 int 
, @ms4 int 
, @ms5 int 
;

-- case structure 

set @i=1; 
select @ts=GETDATE(); 
while @i<100000 begin select @md=case when ISNULL(d1,d2)<ISNULL(d2,d1) then ISNULL(d2,d1) else ISNULL(d1,d2) end from @d; set @i+=1; end 
select @ms0=DATEDIFF(ms,@ts,GETDATE()); 

-- fnMaxDate1, IF IS NULL
set @i=1; 
select @ts=GETDATE(); 
while @i<100000 begin select @md=dbo.fnMaxDate1(d1,d2) from @d; set @i+=1; end 
select @ms1=DATEDIFF(ms,@ts,GETDATE()); 

-- fnMaxDate2, CASE
set @i=1; 
select @ts=GETDATE(); 
while @i<100000 begin select @md=dbo.fnMaxDate2(d1,d2) from @d; set @i+=1; end 
select @ms2=DATEDIFF(ms,@ts,GETDATE()); 

-- fnMaxDate3, IF IS NOT NULL
set @i=1; 
select @ts=GETDATE(); 
while @i<100000 begin select @md=dbo.fnMaxDate3(d1,d2) from @d; set @i+=1; end 
select @ms3=DATEDIFF(ms,@ts,GETDATE()); 

-- fnMaxDate4, SELECT UNION
set @i=1; 
select @ts=GETDATE(); 
while @i<100000 begin select @md=dbo.fnMaxDate4(d1,d2) from @d; set @i+=1; end 
select @ms4=DATEDIFF(ms,@ts,GETDATE()); 

-- fnMaxDate5, empty function call
set @i=1; 
select @ts=GETDATE(); 
while @i<100000 begin select @md=dbo.fnMaxDate5(d1,d2) from @d; set @i+=1; end 
select @ms5=DATEDIFF(ms,@ts,GETDATE()); 

select [Case(ms)]=@ms0/100000.0, [fnMaxDate5 (empty function call)]=@ms5/100000.0, fnMaxDate1=@ms1/100000.0, fnMaxDate2=@ms2/100000.0, fnMaxDate3 = @ms3/100000.0, fnMaxDate4=@ms4/100000.0
go 

最后,这是函数的最终版本:

/* Returns the greater of two dates.

    SELECT dbo.fnMaxDate(NULL      , NULL)
    SELECT dbo.fnMaxDate('1/1/2011', NULL)
    SELECT dbo.fnMaxDate(NULL      , '1/1/2011')
    SELECT dbo.fnMaxDate('1/1/2011', '1/1/2011')
    SELECT dbo.fnMaxDate('1/2/2011', '1/1/2011')
    SELECT dbo.fnMaxDate('1/1/2011', '1/2/2011')

*/
ALTER FUNCTION [dbo].[fnMaxDate] 
(
    @Date1 DATETIME,
    @Date2 DATETIME
)
RETURNS DATETIME
AS
BEGIN

    DECLARE @Result DATETIME

    IF @Date1 IS NOT NULL AND @Date2 IS NOT NULL

        IF @Date1 >= @Date2

            SET @Result = @Date1

        ELSE

            SET @Result = @Date2

    ELSE IF @Date1 IS NULL

        SET @Result = @Date2

    ELSE IF @Date2 IS NULL

        SET @Result = @Date1

    RETURN @Result

END