为啥 SQL Server 不允许我将 '21/04/17' 存储为日期?

Posted

技术标签:

【中文标题】为啥 SQL Server 不允许我将 \'21/04/17\' 存储为日期?【英文标题】:Why isn't SQL Server letting me store '21/04/17' as a date?为什么 SQL Server 不允许我将 '21/04/17' 存储为日期? 【发布时间】:2020-07-07 01:00:19 【问题描述】:

我有一个表,当前所有列都存储为 nvarchar(max),因此我将所有数据类型转换为应有的数据类型。我有一列日期,但是当我运行它时:

ALTER TABLE Leavers ALTER COLUMN [Actual_Termination_Date] date;

我明白了

"Conversion failed when converting date and/or time from character string".

这是比较正常的,所以我做了以下调查:

SELECT DISTINCT TOP 20 [Actual_Termination_Date]
FROM LEAVERS
WHERE ISDATE([Actual_Termination_Date]) = 0

返回:

NULL
13/04/2017
14/04/2017
17/04/2017
19/04/2017
21/04/2017
23/04/2017
24/04/2017
26/04/2017
28/04/2017
29/03/2017
29/04/2017
30/04/2017
31/03/2017
42795
42797
42813
42817
42820
42825

null 和 excel 样式的日期格式(例如 42795)没有问题,但它是那些看起来完全正常的日期,我遇到了问题。我通常使用以下修复方法之一来解决此类问题:

SELECT  cast([Actual_Termination_Date] - 2 as datetime)
FROM LEAVERS
WHERE ISDATE([Actual_Termination_Date]) = 0

SELECT  cast(convert(nvarchar,[Actual_Termination_Date], 103) - 2 as datetime)
FROM LEAVERS
WHERE ISDATE([Actual_Termination_Date]) = 0

当这些返回我所期望的日期时,我会执行一个 UPDATE 语句来更改表中的日期,然后转换列类型。但是,我不断收到一条错误消息,告诉我无法转换各种日期,例如:

Conversion failed when converting the nvarchar value '21/04/2017' to data type int.

有什么想法吗?谢谢!

【问题讨论】:

使用 ISO 日期并忘记这些问题。他们采用 标准 格式 YYYY-MM-DD。 正如我在回答中警告的那样,@TheImpaler、yyyy-MM-dd 在 SQL Server 中并不安全。例如,在我的实例上,CONVERT(datetime, '2020-03-26') 将失败。 【参考方案1】:

可能是因为您的语言设置。要使'21/04/2017' 工作,您需要使用BRITISH 语言或使用dd/MM/yyyy 的其他语言。我怀疑您使用的是ENGLISH,它实际上是美国的。

美国人使用MM/dd/yyyy 表示'21/04/2017' 表示2017 年第21 个月的第4 天;显然这行不通。

最佳方法是使用明确的格式,而不管语言和数据类型如何。对于yyyyMMddyyyy-MM-ddThh:mm:ss.nnnnnnn 的SQL Server(yyyy-MM-ddyyyy-MM-dd hh:mm:ss.nnnnnnn 在使用旧的datetimesmalldatetime 数据类型时在SQL Server 中不是明确)。

否则,您可以将CONVERT 与style 代码一起使用:

SELECT CONVERT(date,'21/04/2017', 103)

但是,您的数据的问题在于您的值采用dd/MM/yyyy 格式和整数值。在 SQL Server 中,int(不是varchar)值42817 作为datetime2017-03-25。另一方面,如果此数据来自 Excel,则值为 2017-03-23。我将假设数据来自 Excel,而不是 SQL Server(因为 ACE 驱动程序有将日期读取为数字的习惯,因为它们不是“ace”)。

因此,您需要先将值转换为明确的格式,即yyyyMMdd。由于我们有 2 种不同类型的值,这有点困难,但仍有可能:

UPDATE dbo.Leavers
SET Actual_Termination_Date = CONVERT(varchar(8), ISNULL(TRY_CONVERT(date, Actual_Termination_Date, 103), DATEADD(DAY, TRY_CONVERT(int, Actual_Termination_Date),'18991230')), 112);

然后你可以改变你的桌子:

ALTER TABLE dbo.Leavers ALTER COLUMN [Actual_Termination_Date] date;

DB<>Fiddle 使用 Michał Turczyn 的 DML 语句。

【讨论】:

谁知道使用标准可以使互操作性更容易!? ;) 这是因为我假设数据来自 Excel,@PhilCollins。对于 recent 日期,整数值是 1899 年 12 月 30 日之后的许多天。 修复了!工作完美,谢谢。如果我完全理解解决方案,我会撒谎。我可以理解一切,但是我不能 100% 确定添加“18991230”在做什么。无论如何,它正在工作,谢谢。 你是对的。实际上它来自一个 oracle 数据库,进入一个 .xls 文件。转换为 .csv,然后作为平面文件导入 SSMS。此外,语言设置为美国英语。【参考方案2】:

先把列转成规范格式,再转换:

update leavers
    set Actual_Termination_Date = try_convert(date, [Actual_Termination_Date], 103);

ALTER TABLE Leavers ALTER COLUMN [Actual_Termination_Date] date;

update 会进行从日期到字符串的隐式转换。 alter 应该能够“撤消”该隐式转换。

在执行此操作之前备份表!您可能会发现某些日期无效 - 这几乎是您将日期存储为字符串时的规则,尽管在少数情况下,所有日期字符串实际上都是一致的格式。

【讨论】:

我试过这个,但它把我有问题的所有值都变成了 NULL 并且我丢失了数据。幸运的是,我确实备份了数据。【参考方案3】:

实际日期无关紧要。当您尝试从字符串中减去 2 时会发生错误:

[Actual_Termination_Date] - 2

线索来自错误信息:

将 nvarchar 值“21/04/2017”转换为数据类型 int 时转换失败。

要解决此问题,请在转换后使用DATEADD

SELECT  DATEADD(days, -2, convert(datetime, [Actual_Termination_Date], 103))

【讨论】:

【参考方案4】:

您的列中的日期格式不一致,这很糟糕。

错误的数据类型会导致它,这就是为什么在列上拥有正确的数据类型如此重要。

让我们稍微调查一下:

-- some test data
declare @tbl table (dt varchar(20));
insert into @tbl values 
(NULL),
('13/04/2017'),
('14/04/2017'),
('17/04/2017'),
('19/04/2017'),
('21/04/2017'),
('23/04/2017'),
('24/04/2017'),
('26/04/2017'),
('28/04/2017'),
('29/03/2017'),
('29/04/2017'),
('30/04/2017'),
('31/03/2017'),
('42795'),
('42797'),
('42813'),
('42817'),
('42820'),
('42825');
-- here we handle one format
select convert(date, dt, 103) from @tbl
where len(dt) > 5
or dt is null
-- here we handle excel like format
select dateadd(day, cast(dt as int), '1900-01-01') from @tbl
where len(dt) = 5

因此,正如您所见,您必须为这项任务应用不同的方法。 CASE WHEN 声明应该很适合这里,见下文SELECT

select case when len(dt) = 5 then
         dateadd(day, cast(dt as int), '1900-01-01')
       else convert(date, dt, 103) end
from @tbl

【讨论】:

以上是关于为啥 SQL Server 不允许我将 '21/04/17' 存储为日期?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 SQL Server GEOGRAPHY 允许 -15069° 和 +15069° 之间的经度?为啥是±15069°?

为啥sql server身份验证失败

sqlserver 插入完整的数据后,为啥查询出来某个字段为只显示一半

为啥 CloudKit 不允许我将任何记录保存到默认容器的公共数据库中?

为啥 MobileFirst Server 配置工具不允许我使用 libertyAdminUser 和 libertyAdminPassword 进行部署?

为啥 SQL Server 在 equals 语句中忽略表情符号?