【问题标题】:Correct way to convert Month Name and Year to complete date SQL Server将月份名称和年份转换为完整日期 SQL Server 的正确方法
【发布时间】:2018-12-11 06:58:31
【问题描述】:

我有以下格式的数据。

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,但也许还有更好的方法。

【问题讨论】:

标签: sql-server tsql sql-server-2014


【解决方案1】:

这里的危险来源是他:你依赖于你的系统文化。

文化相关的日期格式很危险(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_ 的替代调用不会返回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:

我知道我会选择什么...如果日期处理得当,你永远不会 不得不处理这样的问题。我们应该看到文本格式 仅输出。用户的输入应由应用程序处理 层。所以数据库永远不必考虑这样的问题 转换。这种需要存在的事实清楚地表明, 数据库/应用程序设计不正确。我们不应该添加更多 如果我们可以避免它的弱点

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

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

  • TRY_CONVERT 大约 600 毫秒
  • TRY_CAST 大约 550 毫秒
  • 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;

【讨论】:

  • 另外一个,对我的帖子很有用。
  • @SQL_M 老实说:在我看来,这不是对您的帖子有用的补充,而是唯一可靠的答案。您的方法可能会完美运行,通过所有内部测试,但仍可能在生产环境中出现意外、愚蠢和难以发现的错误...
  • 可靠的是。但请记住,如果您不在国际环境中工作,则此解决方案不可取。您的解决方案始终有效,但需要付出代价(性能会受到影响)。
  • 不错的测试,前段时间我自己做了一些测试,得到了类似的结果。正如您所说,差异比您预期的要大得多。
【解决方案2】:

我觉得就这么简单:

SELECT CAST ('January 2016' AS DATETIME)

或者对于 SQL2012+

SELECT TRY_CAST ('January 2016' AS DATETIME)

【讨论】:

  • try_convert() 或 try_cast() 考虑到字符串转换的风险 +1 会更安全
  • @JohnCappelletti 你是对的,但这取决于版本。感谢您的评论,更新了我的答案。
  • 这取决于您的系统默认语言为 english。一个非常危险的假设...您可能会阅读我的答案...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-04-09
  • 2018-12-24
  • 2016-03-09
  • 1970-01-01
  • 1970-01-01
  • 2014-04-26
  • 1970-01-01
相关资源
最近更新 更多