简体   繁体   中英

Cast string as date and use it in comparison

I have a table as

NUM | TDATE   
1   | 200712    
2   | 200708    
3   | 200704    
4   | 20081210

where mytable is created as

 mytable
(
  num int,
  tdate char(8)  -- legacy 
);

The format of tdate is YYYYMMDD.. sometimes the date part is optional.
So a date such as "200712" can be interpreted as 2007-12-01.

I want to write query such that i can treat tdate as a Date column and apply date comparison.

like

select num, tdate from mytable where tdate  

between '2007-12-31 00:00:00' and '2007-05-01 00:00:00'

So far i tried this

select num, tdate,
CAST(LEFT(tdate,6) 
+ COALESCE(NULLIF(SUBSTRING(CAST(tdate AS VARCHAR(8)),7,8),''),'01') AS Date)
from mytable 

SQL Fiddle

How can I use the above converted date (3rd column ) for comparison? (needs a join?)

Also is there a better way to do this?

Edit: I have no control over the table scheme for now.. we have suggested the change to the DB team..for now have to stick with char(8) .

I think this a better way to get your fixed date:

SELECT CAST(LEFT(RTRIM(tdate) + '01',8) AS DATE)

You can create a subquery/cte with the date cast properly:

;WITH cte AS (select num, tdate,CAST(LEFT(RTRIM(tdate)+ '01',8) AS DATE)'FixedDate'
              from mytable )
select num, FixedDate
from cte
where FixedDate
between '2007-12-31' and '2007-05-01'

Or you can just use your fixed date in the query directly:

select num, tdate 
from mytable 
where CAST(LEFT(RTRIM(tdate)+ '01',8) AS DATE) between '2007-12-31' and '2007-05-01'

Ideally you would add the fixed date field to your table so that queries can benefit from indexing the date.

Note: Be wary of BETWEEN with DATETIME as the time portion can result in undesired results if you really only care about the DATE portion.

'2007-12-31 00:00:00' > '2007-05-01 00:00:00' , so your BETWEEN clause will never return any records.

This will work, with a subquery, and with the dates flipped:

select num, tdate, formattedDate
from 
(
  select num, tdate
  ,
  CAST(LEFT(tdate,6) + COALESCE(NULLIF(SUBSTRING(CAST(tdate AS VARCHAR(8)),7,8),''),'01') AS Date) as formattedDate

  from mytable 
) a
where formattedDate between '2007-05-01 00:00:00' and '2007-12-31 00:00:00'

sqlFiddle here

I think you should avoid storing date in string type fields. If that is something you have to live with try following solution.

Since you are having yyyymmdd or yyyymm format you can first get them all in yyyymmdd format which is Culture independent ISO format and then use style 112 to convert into Date for comparison:

--Culture independent solution
;with cte as (
  select num, tdate, 
         convert(date,left(rtrim(tdate) + '01',8),112) mydate --yyyymmdd format
  from mytable
)
select num,tdate,mydate
from cte
where mydate between convert(date,'20071231',112) and --Values are in yyyymmdd format
                     convert(date,'20070501',112) 

Yet another way to turn your string values into dates would be to use REPLACE :

SELECT num, tdate
FROM mytable
WHERE CAST(REPLACE(tdate, '  ', '01') AS date) BETWEEN @date1 AND @date2
;

If you really want to both return the converted date value and use it for filtering, you can employ CROSS APPLY to avoid repeating the logic:

SELECT t.num, t.tdate, x.date
FROM mytable AS t
CROSS APPLY (SELECT CAST(REPLACE(t.tdate, '  ', '01') AS date)) AS x (date)
WHERE x.date BETWEEN @date1 AND @date2
;

This method assumes that your char(8) strings are formatted as either YYYYMMDD or YYYYMM , although the method will work without any changes if you decide to start using values formatted as just YYYY in addition to the other two formats (to imply the beginning of a year, just like a YYYYMM implies the beginning of a month).

with date_cte(num,date)as
(select num,CAST(LEFT(tdate,6) 
+ COALESCE(NULLIF(SUBSTRING(CAST(tdate AS VARCHAR(8)),7,8),''),'01') AS Date)
from mytable)

select t1.num, t1.tdate,t2.date
from mytable t1 join date_cte t2 on t1.num=t2.num
where t2.date 
between '2007-12-31 00:00:00' and '2007-05-01 00:00:00'

I don't have the time to test right now, but something like this may work...

select num, tdate
from mytable 
WHERE CAST(LEFT(tdate,6) 
+ COALESCE(NULLIF(SUBSTRING(CAST(tdate AS VARCHAR(8)),7,8),''),'01') AS Date) BETWEEN CAST('2007-12-31 00:00:00' as smalldatetime) and CAST('2007-05-01 00:00:00' as smalldatetime)

My proposal would be to add a date field to your table. If your table is regularly updated, fill it from the legacy field through a stored proc on a regular schedule (either trigger or job).

You'll then be able to use the date as ... a date, without all these tricks, turnarounds, and other approximations which are all potential source for confusion, mistakes and questionable results.

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