我正在寻找有关SQL Server Management Studio的非常有用的信息。
我有一个包含varchar
类型列的表,用于存储日期,数字和字符串。
这些日期以下列格式存储:
dd/mm/aaaa
我有一个搜索表单匹配行的查询,一个要求是用户必须能够在日期(时间段)之间进行seacrh。
如果我只有日期,没有任何谜,我可以使用查询:
where convert(datetime,a.valor,103) between '01/01/2013' and '03/01/2013'
问题是当查询该值不是日期的行时,此查询失败。
执行该查询的有效方法是什么,因为可能有数千行要搜索?
答案 0 :(得分:3)
典型的答案是添加一个WHERE子句:
WHERE ISDATE(a.valor) = 1
然而,由于以下几个原因,这在您的情况下会出现问题:
ISDATE()
根据服务器的区域设置,用户的语言或日期格式选项等,不一定符合您的要求。例如:
SET DATEFORMAT dmy;
SELECT ISDATE('13/01/2012'); -- 1
SET DATEFORMAT mdy;
SELECT ISDATE('13/01/2012'); -- 0
您无法真正控制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
。
使用临时表:
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
数据类型,但概念保持不变。