0

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

我有一个表,其中有一列varchar存储日期、数字和字符串的类型。

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

dd/mm/aaaa

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

如果我只有约会就没有问题了,我可以使用查询:

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

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

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

4

2 回答 2

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过滤器之后的操作。

您甚至不能使用子查询或 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在查询视图时发出 a 。


使用临时表:

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

SELECT <cols>, CONVERT(DATE, valor) FROM #foo WHERE ...;

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


不,您不应该尝试使用另一个(现已删除)答案中建议的字符串模式匹配来验证您的字符串作为日期:

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

您将不得不在那里进行一些非常复杂且繁重的验证来处理所有有效日期,包括闰年。

于 2013-01-20T04:08:44.987 回答
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我没有数据类型,但概念保持不变。

于 2013-01-20T04:54:13.880 回答