将nvarchar(MAX)转换为datetime

时间:2013-03-12 10:18:14

标签: sql sql-server

我有一个包含多个字段nvarchar(MAX)的表格,其格式为dd/mm/yyyy格式。

我运行此查询:

SELECT CONVERT(datetime,[Start Date],103)
FROM [YellowCard_NewDesign].[dbo].[vw_All_Requests]

我得到错误:

  

将nvarchar数据类型转换为日期时间数据类型会导致超出范围的值。

奇怪的是,当我运行这样的查询时:

SELECT TOP 40 CONVERT(datetime,[Start Date],103)
FROM [YellowCard_NewDesign].[dbo].[vw_All_Requests]

它有效,但我不想要TOP X

我的表只包含36条记录。所以我不认为有不好的数据。

20/03/2013
20/03/2013
10/03/2013
10/03/2013
11/03/2013
06/03/2013
06/03/2013
21/03/2013
12/03/2013
03/03/2013
18/03/2013
04/03/2013
28/02/2013
28/02/2013
28/02/2013
28/02/2013
31/01/2013
15/01/2013
23/01/2013
23/01/2013
31/01/2013
23/01/2013
30/01/2013
31/01/2013
24/01/2013
24/01/2013
24/01/2013
24/01/2013
24/01/2013
30/01/2013
23/01/2013
22/01/2013
23/01/2013
23/01/2013
23/01/2013
23/01/2013

有人可以帮我吗?

3 个答案:

答案 0 :(得分:12)

快速,简单,懒惰的方式可让您继续使用不良数据类型,并允许各种垃圾进入您的表格

SET DATEFORMAT DMY;

SELECT 
  -- other columns, 
  [Start Date] = CONVERT(DATETIME, CASE WHEN ISDATE([Start Date]) = 1
  THEN [Start Date] END, 103)
FROM 
  dbo.vw_All_Requests;

现在,在这种情况下,对于任何不包含NULL格式的有效日期的列值,它将返回d/m/y。如果要排除这些行而不是将其包含NULL值,则可以添加WHERE子句:

SET DATEFORMAT DMY;

SELECT 
  -- other columns, 
  [Start Date] = CONVERT(DATETIME, CASE WHEN ISDATE([Start Date]) = 1
  THEN [Start Date] END, 103)
FROM 
  dbo.vw_All_Requests
WHERE ISDATE([Start Date]) = 1;

你不能这样做的原因......

SELECT CONVERT(DATETIME, [Start Date], 103)
FROM ...
WHERE ISDATE([Start Date]) = 1;

...是因为SQL Server可能会在过滤器之前尝试CONVERT,导致您现在遇到的错误相同。 CASE表达式(在大多数情况下)允许您控制该评估顺序。

现在,当然,有效为d/m/y格式的日期并不一定意味着它是用户的意图。如果你有一个美国人在7月4日输入07/04/2013,那么你将错误地认为是4月7日。这就是d/m/ym/d/y等区域格式不合适的原因。


快速,简单,懒惰的方式可以让您继续使用错误的数据类型可以防止更多的垃圾进入您的桌面而且需要更多的工作 < / p>

你仍然应该按照上面的建议和修复删除任何不符合的数据,并且至少添加一个检查约束,以便不再有垃圾进入你的桌子:

ALTER TABLE dbo.SourceTable 
  ADD CONSTRAINT CK_SillyMaxColumnIsValidDate
  CHECK (CONVERT(DATE, [Start Date], 103) >= '0001-01-01');

因此以下插入将失败或成功:

-- these succeed:
INSERT dbo.SourceTable([Start Date]) SELECT '01/01/2005';
INSERT dbo.SourceTable([Start Date]) SELECT '25/01/2005';
GO
-- fails:
INSERT dbo.SourceTable([Start Date]) SELECT '01/25/2005';
GO
-- fails:
INSERT dbo.SourceTable([Start Date]) SELECT 'garbage';

失败完全阻止了错误的插入,并且只允许有效的d/m/y字符串进入表中。错误消息是:

  

Msg 241,Level 16,State 1,Line 1
  从字符串转换日期和/或时间时转换失败。


正确的方式

首先,识别不良数据:

SELECT [Start Date] 
  FROM dbo.vw_All_Requests
  WHERE ISDATE([Start Date]) = 0;

现在,修复数据,无论它来自哪里,以便您可以修复数据类型。这可能意味着这些类型的陈述的组合:

-- correct the date for specific bad values
UPDATE dbo.SourceTable 
  SET [Start Date] = '03/12/2012'
  WHERE [Start Date] = 'whatever';

-- remove the value altogether for specific bad values
UPDATE dbo.SourceTable 
  SET [Start Date] = NULL
  WHERE [Start Date] = 'whatever';

-- remove the row altogether for specific bad values
DELETE dbo.SourceTable 
  WHERE [Start Date] = 'whatever';

-- remove all rows with bad values
SET DATEFORMAT DMY;

DELETE dbo.SourceTable
  WHERE ISDATE([Start Date]) = 0;

然后在源表中添加DATE列:

ALTER TABLE dbo.SourceTable
  ADD StartDate -- no space!
  DATE;

然后更新该列中的数据:

UPDATE dbo.SourceTable
  SET StartDate = CONVERT(DATETIME, [Start Date], 103);

现在删除原始列(您可能需要调整包含此列的索引或引用它的约束):

ALTER TABLE dbo.SourceTable
  DROP COLUMN [Start Date];

现在您可以更改新列名称:

EXEC sp_rename N'dbo.SourceTable.StartDate', 'Start Date', 'COLUMN';

或者更改视图:

ALTER VIEW dbo.vw_All_Requests
AS
   SELECT 
    ...
    [Start Date] = StartDate

或者更改您的应用程序以使用新的更好的列名称。不要忘记更改所有数据类型参数以使用正确的数据类型,并且在使用d/m/y等区域格式传递字符串文字时,停止。就像@Kaf一样,我更喜欢yyyymmdd,它永远不会被误解,但yyyy-mm-dd将始终适用于DATE - 只是DATETIME,例如:

SET LANGUAGE FRENCH;
SELECT CONVERT(DATETIME, '2012-03-15');

结果:

  

Msg 242,Level 16,State 3,Line 2
  La conversion d'un typededonnéesvarcharen typededonnéesdatetimeacrééunevaleur hors limites。


一些背景

请阅读这篇文章:

最后一点

请停止创建包含空格的列名或别名。如果需要视觉分离,请使用下划线。对于列名称来说,这些都是更好的选择,不要以任何方式更改含义,并且不需要在每个引用周围使用丑陋的方括号:

Start_Date
StartDate

答案 1 :(得分:1)

在字符串类型字段中保存日期并不是一个好主意。但是,如果您目前需要处理这些问题,可以使用isdate()函数运行此查询以检查任何错误数据并修复它们:

SELECT [Start Date]
FROM [YellowCard_NewDesign].[dbo].[vw_All_Requests]
WHERE isdate([Start Date]) = 0

此外,如果您在转换为日期类型之前在 none culture specific ISO format中获取日期会更好。如果它们都是dd/mm/yyyy格式,您可以在ISO format'yyyymmdd')中获取它们,如下所示。

SQL FIDDLE DEMO

declare @d varchar(max) = '20/03/2013'
select convert(datetime,right(@d,4) + substring(@d,4,2) + left(@d,2))

所以你的实际查询将是:

SELECT CONVERT(datetime,right([Start Date],4) + 
                               substring([Start Date],4,2) + 
                               left([Start Date],2))
FROM [YellowCard_NewDesign].[dbo].[vw_All_Requests]

答案 2 :(得分:-4)

这与TOP 40无关。这意味着前40行没有问题数据。

如果表格中的数据较少,您只需增加限制即可找出错误发生的位置

例如:

SELECT TOP 100 CONVERT(datetime,[Start Date],103)
FROM [YellowCard_NewDesign].[dbo].[vw_All_Requests]