如何使用用户定义函数遍历列值

How to use a User Defined Function to iterate through column values

我有一个函数可以根据某个办公地点将 UTC 时间转换为本地时间。 该函数的两个参数是作为 datetime2 数据类型的 UTC 和作为 int 数据类型的 Office。

SELECT [fn].[ConvertFromUTC]('2021-03-14 07:00:00', 5740)

结果:2021-03-14 03:00:00

这里是 table,我想根据支持站点位置(办公室)将所有 UTC 时间转换为本地时间。

执行此操作的最佳方法是什么?我尝试使用类似的东西,但不确定如何遍历每个支持站点和 UTC 时间。建议?在这种情况下 Cursor 是理想的选择吗?

DECLARE @meh nvarchar(50)
DECLARE @x datetime2
DECLARE @y int

Set @x = '2021-03-14 07:00:00'
Set @y = 20608
        
EXEC    @meh = fn.ConvertFromUTC
            @DateTime = @x, 
            @Office = @y

SELECT  @meh

这是函数的代码。

ALTER Function [fn].[ConvertFromUTC]
/*This function converts a DateTime from UTC to local time at each office.*/
    
(
@DateTime DATETIME2(0),
@Office int
)

RETURNS DATETIME2(0)
AS

BEGIN
    DECLARE @TimeZone nvarchar(50)
    DECLARE @Result DATETIME2(0)
    
    IF @Office IN ('20608','5740')
        BEGIN
            SET @TimeZone = 'US Eastern Standard Time'
            SET @Result = @DateTime at time zone 'UTC' at time zone @TimeZone
        END 
    ELSE IF @Office = '597'
        BEGIN
            SET @TimeZone = 'W. Europe Standard Time'
            SET @Result = @DateTime at time zone 'UTC' at time zone @TimeZone
            SET @Result = DATEADD(HOUR,-1,@Result) /* 'AT TIME ZONE' functionality for Europe uses the wrong offset. This corrects it.*/
        END 
    ELSE IF @Office = '6179' /*Not using 'AT TIME ZONE' functionality because it doesn't recognize that Australia observes Daylight Savings Time*/
        BEGIN   
            /*DateTime is the end and beginning of Sydney daylight savings time in UTC. 
            Ends first Sunday in April at 2:00:00 and begins first Sunday in October at 3:00:00
            In UTC, Ends first Saturday in April at 16:00:00 and begins first Saturday in October 16:00:00*/
            IF @DateTime >= DATEADD(dd, (5-(DATEDIFF(dd,0,DATEADD(mm,(YEAR(@DateTime)-1900) * 12 + 3,0))%7)),DATEADD(mm,(YEAR(@DateTime)-1900) * 12 + 3,0))+'16:00:00'
            AND @DateTime < DATEADD(dd,0 + (5-(DATEDIFF(dd,0,DATEADD(mm,(YEAR(@DateTime)-1900) * 12 + 9,0))%7)),DATEADD(mm,(YEAR(@DateTime)-1900) * 12 + 9,0))+'16:00:00'
                BEGIN
                    SET @Result = DATEADD(hour,10,@DateTime)    
                END
            ELSE
                BEGIN
                    SET @Result = DATEADD(hour,11,@DateTime)    
                END
        END 

    RETURN @Result

END

首先,您目前拥有的是 标量用户定义函数 (UDF)

你可以像这样简单地使用它:

SELECT fn.ConvertFromUTC(t.YourDate, 5740)
FROM YourTable t;

标量 UDF 由于各种原因很慢,应该避免使用,尽管大多数都得到了 SQL Server 2019 的 UDF 内联的帮助,它是通常最好将其重写为 内联 Table 值函数 。这 returns 一个行集,有点像参数化视图。

要使其内联,它必须单个 RETURN (SELECT语句

CREATE OR ALTER Function [fn].[ConvertFromUTC]
/*This function converts a DateTime from UTC to local time at each office.*/
(
  @DateTime DATETIME2(0),
  @Office int
)

RETURNS TABLE
AS RETURN
(
    SELECT Result = CASE 
      WHEN @Office IN ('20608', '5740')
        THEN @DateTime AT TIME ZONE 'UTC' AT TIME ZONE 'US Eastern Standard Time'
      WHEN @Office = '597'
        THEN DATEADD(HOUR, -1, @DateTime AT TIME ZONE 'UTC' AT TIME ZONE 'W. Europe Standard Time')
      WHEN @Office = '6179'
        THEN 

            /*DateTime is the end and beginning of Sydney daylight savings time in UTC. 
            Ends first Sunday in April at 2:00:00 and begins first Sunday in October at 3:00:00
            In UTC, Ends first Saturday in April at 16:00:00 and begins first Saturday in October 16:00:00*/

            CASE WHEN @DateTime >= DATEADD(dd, (5-(DATEDIFF(dd, 0, DATEADD(mm, (YEAR(@DateTime) - 1900) * 12 + 3, 0)) %7)), DATEADD(mm,(YEAR(@DateTime) - 1900) * 12 + 3, 0)) + '16:00:00'
                AND @DateTime < DATEADD(dd, 0 + (5 - (DATEDIFF(dd, 0, DATEADD(mm, (YEAR(@DateTime)-1900) * 12 + 9, 0)) % 7)), DATEADD(mm,(YEAR(@DateTime) - 1900) * 12 + 9, 0)) + '16:00:00'
                THEN DATEADD(hour,10,@DateTime)    
                ELSE DATEADD(hour,11,@DateTime)    
            END
      END 
);

GO

你可以这样使用它:

SELECT utc.Result
FROM YourTable t
CROSS APPLY fn.ConvertFromUTC(t.YourDate, 5740) utc;

-- Because it's only one value you can also do this

SELECT
  (SELECT utc.Result FROM fn.ConvertFromUTC(t.YourDate, 5740))
FROM YourTable t;

我必须说,我对这个函数的原作者有异议,他明明知道AT TIME ZONE,但认为它不能正常工作。

            SET @TimeZone = 'W. Europe Standard Time'
            SET @Result = @DateTime at time zone 'UTC' at time zone @TimeZone
            SET @Result = DATEADD(HOUR,-1,@Result) /* 'AT TIME ZONE' functionality for Europe uses the wrong offset. This corrects it.*/

欧洲不是单一的,大概正确的时区应该是'GMT Standard Time'

    ELSE IF @Office = '6179' /*Not using 'AT TIME ZONE' functionality because it doesn't recognize that Australia observes Daylight Savings Time*/
        BEGIN   
            /*DateTime is the end and beginning of Sydney daylight savings time in UTC. 

同样,澳大利亚不是一个时区,我怀疑使用了 E. Australia Standard Time 而不是 'AUS Eastern Standard Time'

您可以通过select * from sys.time_zone_info查看服务器上所有可用的时区,您也可以通过Windows添加更多时区。

还有一点就是这个函数本该是converts a DateTime from UTC to local time,但是两次使用AT TIME ZONE只是在从一个时区转换到另一个时区的时候用到的,如果时间已经在 'UTC'.

,则只应执行一次

还有一点:我建议你在查询的table中实际存储正确的时区,然后你可以通过时区 而不是 @Office 并避免一堆 CASE 表达式。