如何仅在包含有效日期时将varchar转换为日期?

时间:2013-01-20 03:56:11

标签: sql sql-server sql-server-2008

我正在寻找有关SQL Server Management Studio的非常有用的信息。

我有一个包含varchar类型列的表,用于存储日期,数字和字符串。

这些日期以下列格式存储:

dd/mm/aaaa

我有一个搜索表单匹配行的查询,一个要求是用户必须能够在日期(时间段)之间进行seacrh。

如果我只有日期,没有任何谜,我可以使用查询:

where convert(datetime,a.valor,103) between '01/01/2013' and '03/01/2013'

问题是当查询该值不是日期的行时,此查询失败。

执行该查询的有效方法是什么,因为可能有数千行要搜索?

2 个答案:

答案 0 :(得分:3)

典型的答案是添加一个WHERE子句:

WHERE ISDATE(a.valor) = 1

然而,由于以下几个原因,这在您的情况下会出现问题:

  1. ISDATE()根据服务器的区域设置,用户的语言或日期格式选项等,不一定符合您的要求。例如:

    SET DATEFORMAT dmy;
    SELECT ISDATE('13/01/2012'); -- 1
    
    SET DATEFORMAT mdy;
    SELECT ISDATE('13/01/2012'); -- 0
    
  2. 您无法真正控制SQL Server在过滤后尝试执行CONVERT

  3. 您甚至无法使用子查询或CTE来尝试将过滤器与CONVERT分离,因为SQL Server 可以以其认为更高效的顺序优化查询中的操作。

    例如,对于有限的样本,您可能会发现这样可行:

    SET DATEFORMAT dmy;
    
    SELECT valor, valor_date FROM (
      SELECT valor, valor_date = CONVERT(DATE, 
        CASE WHEN ISDATE(valor) = 1 THEN valor ELSE NULL END, 103)
      FROM dbo.mytable
      WHERE ISDATE(valor) = 1
    ) AS sub WHERE valor_date BETWEEN '01/01/2012' AND '01/03/2012';
    

    但我已经看到过这种结构的情况,其中SQL Server首先尝试评估过滤器,导致您当前获得的相同错误。


    一些更安全的解决方法:


    添加计算列,例如

    ALTER TABLE dbo.mytable ADD valor_date
      AS CONVERT(DATE, CASE WHEN ISDATE(valor) = 1 THEN valor 
        ELSE NULL END, 103);
    

    为了保护自己免受运行时可能的错误解释,您应该在发出引用计算列的查询之前指定dateformat,例如

    SET DATEFORMAT dmy;
    SELECT valor, valor_date FROM dbo.mytable WHERE ...;
    

    创建视图:

    CREATE VIEW dbo.myview
    AS
      SELECT valor, valor_date = CONVERT(DATE, 
        CASE WHEN ISDATE(valor) = 1 THEN valor ELSE NULL END, 103)
      FROM dbo.mytable
      WHERE ISDATE(valor) = 1;
    

    同样,您在查询视图时想要发出SET DATEFORMAT


    使用临时表:

    SELECT <cols>
    INTO #foo
    FROM dbo.mytable
    WHERE ISDATE(valor) = 1;
    
    SELECT <cols>, CONVERT(DATE, valor) FROM #foo WHERE ...;
    

    您可能仍希望使用DATEFORMAT来保护自己免受ISDATE与用户设置之间的冲突。


    不,你应该尝试使用字符串模式匹配将字符串验证为日期,如另一个(现已删除)答案所示:

    like '%__/%' or like '%/%'
    

    你必须在那里进行一些非常复杂和严厉的验证,以处理包括闰年在内的所有有效日期。

答案 1 :(得分:0)

您可以将表格与仅包含日期的表格进行比较。可能值得每天创建一个永久表,但你可以使用CTE(最多32767次递归,可以让你到1923年):

create table tmpT ( val nvarchar(255) )
go

insert into tmpT values ('01/01/2012')
insert into tmpT values ('jellybeans')
insert into tmpT values ('21/11/2002')
insert into tmpT values ('ice cream')
insert into tmpT values ('30/08/2012')
go
;

with dates  (d) as (
    select d = cast('1/19/2013' as datetime) -- quick way to drop hh:mm:ss
    union all
    select dateadd(dd, -1, d)
    from dates where d > '01/01/1990'
)
select *
From tmpt 
join dates 
    on convert(varchar, dates.d, 103) = tmpt.val
where d between '01/01/2013' and '01/03/2013'
option (maxrecursion 32767) -- max value: select datediff(dd, -32767, getdate()) = 1923 

ETA:是的我坐在sql 2005前面,所以我没有date数据类型,但概念保持不变。