为什么 SQL 服务器不让我将“21/04/17”存储为日期?

Why isn't SQL Server letting me store '21/04/17' as a date?

我有一个 table 当前所有列都存储为 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

return编辑:

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

当这些 return 像我预期的那样返回日期时,我会执行更新语句以在 table 中更改它们,然后转换列类型。但是,我不断收到一条错误消息,告诉我无法转换各种日期,例如:

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

有什么想法吗?谢谢!

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

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

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

否则,您可以使用 CONVERTstyle 代码:

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

但是,您的数据存在的问题是,您的值的格式为 dd/MM/yyyy 和整数值。 int(不是 varchar)值 42817 作为 SQL 服务器中的 datetime2017-03-25。另一方面,如果此数据来自 Excel,则值为 2017-03-23。我将 假设 数据来自 Excel,而不是 SQL 服务器(因为 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);

然后你可以改变你的table:

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

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

先将列转换为规范格式,然后转换:

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

ALTER TABLE Leavers ALTER COLUMN [Actual_Termination_Date] date;

update 将执行从日期到字符串的隐式转换。 alter应该可以"undo"那个隐式转换。

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

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

[Actual_Termination_Date] - 2

线索来自错误信息:

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

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

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

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

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

让我们稍微调查一下:

-- 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