将月份名称和年份转换为完成日期的正确方法 SQL 服务器

Correct way to convert Month Name and Year to complete date SQL Server

我有以下格式的数据。

January 2016
August 2017
November 2018
January 2018
August 2018
November 2018

我现在想将其转换为有效的日期时间值,因此我将该列用作报告中的正确日期。

我的计划是只添加该月的第一天(或更好)的最后一天。 我试过下面的代码,它似乎可以工作,但我想知道是否有更好的方法。

DATEADD(dd, -Day(b.MonthYear) + 1, b.MonthYear)

使用上面的代码,August 20117 给了我 2017-08-01 00:00:00.000 但也许有更好的方法。

我觉得就这么简单:

SELECT CAST ('January 2016' AS DATETIME)

或 SQL2012+

SELECT TRY_CAST ('January 2016' AS DATETIME)

危险的根源在这里:你依赖于你系统的文化。

依赖于文化的日期格式很危险(什么是 10/09/2017?是 10 月 9 日还是 9 月 10 日?)。但更糟糕的是依赖于语言的格式(十月在我的国家是十月)。

试试这个

SET LANGUAGE ENGLISH;
SELECT TRY_CAST('January 2017' AS DATE),TRY_CONVERT(DATE,'January 2017')

SET LANGUAGE GERMAN;
SELECT TRY_CAST('January 2017' AS DATE),TRY_CONVERT(DATE,'January 2017')

没有 TRY_ 的替代调用不会 return NULL 但会抛出错误。

但是你说的是 [sql-server-2014]。这意味着 you can use TRY_PARSE() (v2012+)。最大的优势:您可以指定底层文化。

SET LANGUAGE GERMAN
SELECT TRY_PARSE('January 2017' AS DATE USING 'en-us')

如果有最小的机会,你可能会在国际环境中编写代码运行,你应该永远不要依赖默认文化...

更新性能与稳定性...

我在评论中回复@SQL_M:

I know what I'd choose... If dates are properly handled, you'd never have to deal with such a question. We should see textual formats in output only. The user's input should be handled by the application layer. So the database should never have to think about such conversions. The fact, that this need exists, shows clearly, that the database/application was not designed properly. We should not add more weakness if we can avoid it

这让我很好奇,我做了一些测试。

我必须承认:性能差异是不可否认的,而且比预期的要大得多。在日期为 Jan 1 1900 12:11AM 的一百万行中(默认为 CONVERT,参数 100)我得到:

  • TRY_CONVERT约600ms
  • TRY_CAST约550ms
  • TRY_PARSE 约 45.000 毫秒

所以是的,确实值得了解性能影响 (~x100)。但上面的说法仍然成立。

测试代码-如果感兴趣:

USE master;
GO
CREATE DATABASE testDB
GO
USE testDB
GO
CREATE TABLE testTbl(ID INT IDENTITY,DateString VARCHAR(100));
GO
WITH Tally(Nmbr) AS (SELECT TOP 1000000 ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) FROM master..spt_values v1 CROSS JOIN master..spt_values v2 CROSS JOIN master..spt_values)
INSERT INTO testTbl(DateString)
SELECT CONVERT(DATETIME,DATEADD(MINUTE,t.Nmbr,'19000101'),100)
FROM Tally t;
GO
CHECKPOINT;
GO
DBCC DROPCLEANBUFFERS
GO
DECLARE @d DATETIME2=SYSUTCDATETIME();
SELECT TRY_CONVERT(DATETIME,DateString) AS d INTO t1 FROM testTbl;
SELECT 'TRY_CONVERT', DATEDIFF(MILLISECOND,@d,SYSUTCDATETIME());
GO
CHECKPOINT;
GO
DBCC DROPCLEANBUFFERS
GO
DECLARE @d DATETIME2=SYSUTCDATETIME();
SELECT TRY_CONVERT(DATETIME,DateString,100) AS d INTO t2 FROM testTbl;
SELECT 'TRY_CONVERT with 3rd param', DATEDIFF(MILLISECOND,@d,SYSUTCDATETIME());
GO
CHECKPOINT;
GO
DBCC DROPCLEANBUFFERS
GO
DECLARE @d DATETIME2=SYSUTCDATETIME();
SELECT TRY_PARSE(DateString AS DATETIME) AS d INTO t3 FROM testTbl;
SELECT 'TRY PARSE',DATEDIFF(MILLISECOND,@d,SYSUTCDATETIME());
GO
CHECKPOINT;
GO
DBCC DROPCLEANBUFFERS
GO
DECLARE @d DATETIME2=SYSUTCDATETIME();
SELECT TRY_PARSE(DateString AS DATETIME USING 'de-de') AS d INTO t4 FROM testTbl;
SELECT 'TRY PARSE with culture',DATEDIFF(MILLISECOND,@d,SYSUTCDATETIME());
GO
CHECKPOINT;
GO
DBCC DROPCLEANBUFFERS
GO
DECLARE @d DATETIME2=SYSUTCDATETIME();
SELECT TRY_CAST(DateString AS DATETIME) AS d INTO t5 FROM testTbl;
SELECT 'TRY_CAST',DATEDIFF(MILLISECOND,@d,SYSUTCDATETIME());

GO
USE master;
GO
DROP DATABASE testDB;