简体   繁体   中英

Convert nvarchar(MAX) to datetime

I have a table with couple of fields nvarchar(MAX) which occupies date in dd/mm/yyyy format.

I run this query:

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

And I get error:

The conversion of a nvarchar data type to a datetime data type resulted in an out-of-range value.

The weird thing is that when I run this query like:

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

It works but I don't want the TOP X .

My table contains only 36 records. so i don't think that there is bad data.

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

Can someone help me with that?

The quick, easy, lazy way that will let you keep using a bad data type and allow all kinds of garbage into your table

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;

Now, in this case it will return NULL for any column value where it does not contain a valid date in d/m/y format. If you want to exclude those rows instead of including them with NULL values, you can add a WHERE clause:

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;

The reason you can't do this...

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

...is because SQL Server might attempt the CONVERT before the filter, resulting in the same error you're getting now. The CASE expression (in most cases) allows you to control that evaluation order.

Now, of course, a date that is valid as d/m/y format does not necessarily mean it was what the user intended. If you had an American enter 07/04/2013 for July 4th, you're going to incorrectly perceive that as April 7th. This is why regional formats like d/m/y and m/d/y are no good.


The quick, easy, lazy way that will let you keep using a bad data type but prevent more garbage from getting into your table with a little bit more work

You should still follow the advice above and fix or remove any data that doesn't conform, and at the very least add a check constraint so that no more junk gets into your table:

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

So the following inserts will fail or succeed:

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

The failures prevent the bad inserts altogether, and only allow valid d/m/y strings into the table. The error message is:

Msg 241, Level 16, State 1, Line 1
Conversion failed when converting date and/or time from character string.


The right way

First, identify the bad data:

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

Now, fix that data, wherever it comes from, so that you can fix the data type. This might mean a combination of these types of statements:

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

Then add a DATE column to the source table:

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

Then update the data in that column:

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

Now drop the original column (you may need to adjust indexes that include this column or constraints that reference it):

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

Now you can either change the new column name:

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

Or change the view:

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

Or change your application to use the new, better column name. Don't forget to change all of your data type parameters to use the proper data type too, and when passing in string literals, STOP using regional formats like d/m/y . Like @Kaf, I prefer yyyymmdd , which can never be misinterpreted, but yyyy-mm-dd will always work for DATE - just not DATETIME , example:

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

Result:

Msg 242, Level 16, State 3, Line 2
La conversion d'un type de données varchar en type de données datetime a créé une valeur hors limites.


Some background

Please read this post:

As a final note

Please stop creating column names or aliases with spaces in them. If you need visual separation, use an underscore. These are both much better choices for column names, don't change the meaning in any way, and don't require ugly square brackets around every single reference:

Start_Date
StartDate

Saving Date in string type fields is not a good idea. But if that something you have to deal with at the moment, you could run this query with isdate() function to check any bad data and fix them:

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

Also, it would be better if you get the date in none culture specific ISO format before converting into Date type. If they are all in dd/mm/yyyy format, you could get them in ISO format ( 'yyyymmdd' ) as below.

SQL FIDDLE DEMO

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

So your actual query will be like:

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

This has nothing to do with TOP 40. It means you dont have problematic data in top 40 rows.

If you have less data in your table, you can simply increase the limit to find out where the error is occuring

Ex:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM