典型的答案是添加一个 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
在查询视图时发出 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 '%/%'
您将不得不在那里进行一些非常复杂且繁重的验证来处理所有有效日期,包括闰年。