【问题标题】:ISDATE Function for different date formats in TSQLSQL 中不同日期格式的 ISDATE 函数
【发布时间】:2015-05-20 12:32:42
【问题描述】:

我需要将视图的多个列中的 VARCHAR 值转换为 DATETIME,以便在 SQL Server 2008 上的另一个应用程序中进行排序和格式化(以区域设置格式显示)。

目前有两个问题。

  1. VARCHAR 值的输入格式不同(但在 列级)
  2. 也可能存在错误值(例如:20..05.2015)

很遗憾,TRY_CONVERT 函数仅适用于 SQL Server 2012 及更高版本。

ISDATE 不起作用,因为视图包含不同的日期格式,我既不能在用户定义的函数中也不能在视图中设置语言,这会导致 ISDATE 使用例如德语日期格式。

我的问题有什么更简单的解决方案吗? 我的第一个想法是写一个类似的函数

FUNCTION TryConvertStringAsDatetime (   @value VARCHAR(MAX),
                                        @format INT
                                    )

它使用CONVERT 函数的格式编号,但手动检查每种可能的格式让我有点害怕。

示例:TryConvertStringAsDatetime('20.05.2015', 104)(带有一些伪代码)

SET @day =  character 1 and 2 
SET @month = character 4 and 5
SET @year = character 7, 8, 9 and 10
SET @dateODBCFormat = @year - @month - @day (concatenated with hyphen and not subtracted :)
IF ISDATE(@dateODBCFormat ) = 1
    RETURN CONVERT(DATETIME, @dateODBCFormat, 120)
ELSE
    RETURN CONVERT(DATETIME, 0) (does the job)

【问题讨论】:

  • 您会遇到哪些日期格式?
  • 没有简单的内置解决方案。您将不得不分析您的数据以了解您需要支持哪些转换规则并编写自定义代码来转换它。在前端应用程序或 CLR proc 中可能会更好。
  • 目前有104(德语)和120(ODBC)格式的值。

标签: sql-server sql-server-2008 tsql datetime format


【解决方案1】:

这是我现在想出的功能:

CREATE
FUNCTION TryConvertStringAsDatetime (   @value VARCHAR(MAX),
                                        @format INT
                                    )
RETURNS DATETIME
AS
/*
Tries to convert a given VARCHAR value to DATETIME.
Returns NULL if no value was specified or the value is not in the correct format.
*/
BEGIN

    DECLARE @length INT = LEN(@value)

    IF @length IS NULL OR @length < 10 OR @length > 23
        RETURN NULL

    DECLARE @day VARCHAR(2),
            @month VARCHAR(2),
            @year VARCHAR(4),
            @time VARCHAR(9)

    IF @format = 104 --dd.mm.yyyy hh:mi:ss(24h)
    BEGIN
        SET @day = SUBSTRING(@value, 1, 2)
        SET @month = SUBSTRING(@value, 4, 2)
        SET @year = SUBSTRING(@value, 7, 4) 
    END
    ELSE IF @format IN (120, 121) --yyyy-mm-dd hh:mi:ss(24h)
    BEGIN
        SET @year = SUBSTRING(@value, 1, 4)
        SET @month = SUBSTRING(@value, 6, 2)
        SET @day = SUBSTRING(@value, 9, 2)              
    END
    ELSE
        RETURN NULL -- currently only german and ODBC supported

    IF @length > 11
        SET @time = SUBSTRING(@value, 12, @length - 11)

    SET @value = @year + '-' + @month + '-' + @day + ISNULL(' ' + @time, '')

    IF ISDATE(@value) = 1
        RETURN CONVERT(DATETIME, @value, 121)

    RETURN NULL

END

【讨论】:

  • 不错!这可能比我的建议更好。
【解决方案2】:

我可能会选择这样的东西:

CREATE FUNCTION TryConvertToDate 
(
    @InputString varchar(20)  
)
RETURNS Datetime
BEGIN
    DECLARE @DateTime datetime = NULL
    SET @DateTime = 
       CASE 
           WHEN LEN(@InputString) = 10 AND PATINDEX('[0-9][0-9].[0-9][0-9].[0-9][0-9][0-9][0-9]', @InputString)=1 THEN
               CONVERT(DateTime, @InputString, 104) -- German
           WHEN LEN(@InputString) = 10 AND PATINDEX('[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]', @InputString)=1 THEN
               CONVERT(DateTime, @InputString, 120) -- ODBC
           ELSE
               NULL -- unsuported format
       END
   RETURN @DateTime        
END  

注意: 测试长度和使用patindex 只能确保一般格式,因此您需要在try 块中调用此函数,以防日期和月份颠倒并导致转换错误.
另一方面,向该函数添加支持的格式非常简单 - 您只需添加一个带有正确 patindex 和长度以及正确转换样式的 when 子句。

另一个选项是确保字符串实际上可以转换为日期。
这将使您的函数更复杂,因此更难编写,但更容易使用,因为它会将引发转换错误的可能性降至最低:

CREATE FUNCTION TryConvertToDate 
(
    @InputString varchar(20)  
)
RETURNS Datetime
BEGIN


    DECLARE @DateValue date, @Days int, @Months int, @Years int

    IF LEN(@DateString) = 10 AND PATINDEX('[0-9][0-9].[0-9][0-9].[0-9][0-9][0-9][0-9]', @InputString)=1 -- German format
        BEGIN
            SELECT @Days = CAST(LEFT(@InputString, 2) As int),
                   @Months = CAST(SUBSTRING(@InputString, 4, 2) as int),
                   @Years = CAST(RIGHT(@InputString, 4) as int)
            -- NOTE: you will need to add a condition for leap years 
            IF (@Days < 31 AND @Months IN(4,6,9,12)) OR (@Days < 30 AND @Months = 2) 
               SET @DateValue = convert(date, @InputString, 104)
        END

    IF LEN(@InputString) = 10 AND PATINDEX('[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]', @InputString)=1 -- ODBC format
        BEGIN
            SELECT @Days = CAST(RIGHT(@InputString, 2) As int),
                   @Months = CAST(SUBSTRING(@InputString, 6, 2) as int),
                   @Years = CAST(LEFT(@InputString, 4) as int)
            -- NOTE: you will need to add a condition for leap years 
            IF (@Days < 31 AND @Months IN(4,6,9,12)) OR (@Days < 30 AND @Months = 2)
               SET @DateValue = convert(date, @InputString, 120)
        END

    RETURN @DateValue

END

【讨论】:

  • 据我所知并在互联网上阅读,UDF 和视图中不允许使用 TRY/CATCH。所以我仍然需要调用 ISDATE 函数。此外,我将使用上述格式的额外参数编写函数(ANSI 2 和德语 4 格式在数字分隔和长度方面完全相似)。非常感谢。
  • 是的,我注意到我的错误并编辑了 try catch 函数。但是,如果您可以使用 clr 函数,它可能会做得更好,并且更具可读性。
【解决方案3】:

在 SQLCLR 中执行此操作的速度和功能方面可能会更好(正如 @Tab 和 @Zohar 在各种 cmets 中所指出的那样)。

.NET/C# 代码:

using System;
using System.Data.SqlTypes;
using System.Globalization;
using Microsoft.SqlServer.Server;

public class TryConvertStuff
{
    [SqlFunction(IsDeterministic = true, IsPrecise = true)]
    public static SqlDateTime TryConvertDateTime([SqlFacet(MaxSize = 50)] SqlString
        StringDate, [SqlFacet(MaxSize = 10)] SqlString Culture)
    {
        CultureInfo _Culture = CultureInfo.CurrentCulture;
        if (!Culture.IsNull && Culture.Value.Trim() != String.Empty)
        {
            _Culture = CultureInfo.GetCultureInfo(Culture.Value);
        }

        DateTime _RealDate;
        if (DateTime.TryParse(StringDate.Value, _Culture,
                               DateTimeStyles.None, out _RealDate))
        {
            return _RealDate;
        };

        return SqlDateTime.Null;
    }
}

测试:

SELECT dbo.TryConvertDateTime(N'2019-04-20', N'en');  --  2019-04-20 00:00:00.000
SELECT dbo.TryConvertDateTime(N'2019-04-20f', N'en'); --  NULL
SELECT dbo.TryConvertDateTime(N'2019.04.20', N'en');  --  2019-04-20 00:00:00.000

SELECT dbo.TryConvertDateTime(N'20.04.2019', N'en');  --  NULL
SELECT dbo.TryConvertDateTime(N'20.04.2019', N'de');  --  2019-04-20 00:00:00.000
SELECT dbo.TryConvertDateTime(N'20.04.2019', NULL);   --  NULL

【讨论】:

  • 你说得对......我只是忘了提到我没有可能在我必须开发的地方使用 CLR(哎呀)。尽管如此,还是感谢您的 CLR 回答,我会期待当我可以使用它时。
  • @GoToXY 只是好奇,为什么不能使用 CLR?
  • 因为我们的编程指南告诉我不要使用它们(有人可能会争论)并且据我所知需要某些权限(这里的 DBA 是一些懒惰的帽子 :) .
猜你喜欢
  • 2022-08-18
  • 1970-01-01
  • 1970-01-01
  • 2015-07-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多