Search code examples
sqldatetimecastingsql-server-2012

Best way to create new DateTime column from VARCHAR column


I have a table with 1M rows and a CREATED_AT varchar column. The formatting of the date string in this column:

  • 2021-10-13 05:03:42.638+00
  • 2021-10-18 21:28:49.98+00
  • 2021-12-08 02:09:03.17+00

I want to cast this string into a new datetime column (called CREATED_DT).

Attempts

select 
    cast([CREATED_AT] as datetime) 
from
    [mydb].[dbo].[mytable]

Error:

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

select 
    convert(datetime, [CREATED_AT]) 
from 
    [mydb].[dbo].[mytable]

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

select 
    try_convert(DateTime, [CREATED_AT]) 
from
    [mydb].[dbo].[mytable]

Query executes, but returns all nulls.

select 
    try_convert(DateTime, [CREATED_AT], 108) 
from
    [mydb].[dbo].[mytable]

Query executes, but returns all nulls.

select 
    try_cast([CREATED_AT] as datetime) 
from
    [mydb].[dbo].[mytable]

Query executes, but returns all nulls.


Solution

  • Assuming you never have to care about the useless +00 at the end (either it's always +00 or you don't care if it's ever something else), and you're ok losing a tiny bit of precision, you can take the left 22 characters and try to convert the values with a safe style number (in your case, 120):

    DECLARE @d table(CREATED_AT varchar(32));
    
    INSERT @d(CREATED_AT) VALUES
    ('2021-10-13 05:03:42.638+00'),
    ('2021-10-18 21:28:49.98+00'),
    ('2021-12-08 02:09:03.17+00');
    
    SELECT CREATED_AT, 
      as_datetime = TRY_CONVERT(datetime, LEFT(CREATED_AT, 22), 120) 
    FROM @d;
    

    If you don't want to lose the precision (you can't keep .638 as datetime, anyway, for example), or if some value might have 1 or 0 decimal places, or some values might not contain the +xx at all, you can do something similar but use some string tricks to truncate the value where the + appears (and also handles if it doesn't):

    SELECT CREATED_AT,
      as_datetime2 = TRY_CONVERT(datetime2(3), 
        LEFT(CREATED_AT, COALESCE(NULLIF(CHARINDEX('+', 
        CREATED_AT), 0), 32)-1), 120) 
    FROM @d;
    

    And to demonstrate why using the 120 style number is important here, see this db<>fiddle.