SQL 中不同日期格式的 ISDATE 函数
ISDATE Function for different date formats in TSQL
我需要将视图的多个列中的 VARCHAR
值转换为 DATETIME
,以便在 SQL Server 2008 上的另一个应用程序中进行排序和格式化(以语言环境格式显示)。
目前有两个问题。
VARCHAR
值的输入格式不同(但一致
列级别)
- 也可能存在错误值(例如: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)
我可能会选择这样的东西:
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 块中调用此函数以防日期和月份倒置,会导致转换错误。
另一方面,向此函数添加支持的格式非常容易 - 您所要做的就是添加一个 when
子句,其中包含正确的 patindex
和长度以及正确的转换样式。
另一个选择是确保字符串实际上可以转换为日期。
这将使您的函数更复杂,因此更难编写,但会更容易使用,因为它将引发转换错误的可能性降至最低:
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
这是我现在想出的功能:
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
在 SQLCLR 中执行此操作的速度和功能方面,您可能运气更好(正如@Tab 和@Zohar 在各种评论中指出的那样)。
.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
我需要将视图的多个列中的 VARCHAR
值转换为 DATETIME
,以便在 SQL Server 2008 上的另一个应用程序中进行排序和格式化(以语言环境格式显示)。
目前有两个问题。
VARCHAR
值的输入格式不同(但一致 列级别)- 也可能存在错误值(例如: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)
我可能会选择这样的东西:
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 块中调用此函数以防日期和月份倒置,会导致转换错误。
另一方面,向此函数添加支持的格式非常容易 - 您所要做的就是添加一个 when
子句,其中包含正确的 patindex
和长度以及正确的转换样式。
另一个选择是确保字符串实际上可以转换为日期。
这将使您的函数更复杂,因此更难编写,但会更容易使用,因为它将引发转换错误的可能性降至最低:
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
这是我现在想出的功能:
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
在 SQLCLR 中执行此操作的速度和功能方面,您可能运气更好(正如@Tab 和@Zohar 在各种评论中指出的那样)。
.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