验证NVarchar字段中的日期值

时间:2012-03-20 19:24:18

标签: sql-server tsql

您好我有针对某些第三方软件编写的报告应用程序。不幸的是,它将所有值存储为nvarchar,并且不验证客户端上的数据条目,因此我在

时收到以下错误
"Conversion failed when converting date and/or time from character string"  
System.Data.SqlClient.SqlException was unhandled by user code

或者如果我尝试在SSMS中执行代码:

Msg 241, Level 16, State 1, Procedure settlement_list, Line 10
Conversion failed when converting date and/or time from character string.

我认为这是有人在数据字段中输入文本值的结果,所以我尝试使用此ISDATE代码来查找错误值:

   SELECT mat3_02_01, CONVERT(datetime, mat3_04_02), mat3_04_02 FROM lntmu11.matter3
   WHERE ISDATE(mat3_04_02) <> 1
   AND Coalesce(mat3_04_02, '') <> ''
   order by mat3_04_02 desc

我得到零行返回...我也手动筛选数据(它的几十万行所以它的那种硬,看不出坏的值???

有人有任何建议吗?

编辑---

这是存储过程(我知道where子句很难看)

SELECT mat_no, 'index'=matter.mat1_01_06,
  'insurance'=Replace(Replace(matter.mat1_03_01, 'INSURANCE COMPANY', ' '), 'COMPANY', ''), 
  matter.[status], 'casestage'=mat1_04_01, 'injured'=matter.MAT1_01_07, matter.client,
 'terms'=mat3_04_06, 'ClmAmt'=matter.mat1_07_01, 
  'ClmBal'=matter.mat1_07_03, 'SetTot'=matter3.MAT3_04_09, 'By'=mat3_03_02,
  'DtSttld'=mat3_04_02, 'SettlStg'=(MAT3_06_08 + ' / ' + MAT3_06_05)
FROM [lntmu11].matter3 inner join 
[lntmu11].matter ON [lntmu11].matter.sysid = [lntmu11].matter3.sysid
WHERE
(DateDiff(month, convert(datetime, MAT3_04_02, 101), GETDATE()) = @range 
  and mat3_03_02 like @by)
or
(mat3_04_06 like @by2 
  and DateDiff(month, convert(datetime, MAT3_04_02, 101),  GETDATE()) = @range) 
ORDER BY MAT3_03_02

2 个答案:

答案 0 :(得分:2)

如果没有先将ISDATE() = 1行转储到#temp表中,您就无法强制查询引擎尝试处理语句的顺序。您无法保证处理顺序或短路,即使有人会建议使用CTE或子查询首先过滤掉坏行。所以有些人可能会建议:

;WITH x AS 
(
  SELECT mat3_02_01, mat3_04_02
  FROM Intmu11.matter3
  WHERE ISDATE(mat3_04_02) = 1
  AND mat3_04_02 IS NOT NULL -- edited!
)
SELECT mat3_02_01, CONVERT(DATETIME, mat3_04_02), mat3_04_02
FROM x
ORDER BY mat3_04_02 DESC;

今天,这似乎有效。但从长远来看,确保此处理顺序的唯一方法 - 在当前版本的SQL Server中 - 是:

SELECT mat3_02_01, mat3_04_02
  INTO #x
  FROM Intmu11.matter3
  WHERE ISDATE(mat3_04_02) = 1
  AND mat3_04_02 IS NOT NULL; -- edited!

SELECT mat3_02_01, CONVERT(DATETIME, mat3_04_02), mat3_04_02
  FROM #x
  ORDER BY mat3_04_02 DESC;

您是否考虑过验证输入值?例如,您可以在应用程序输入无效日期时将其发送到手腕上,而不是惩罚选择其错误数据的人,从而更改此错误在应用程序中出现的位置。如果您通过存储过程控制更新/插入,则可以说:

IF ISDATE(@mat3_04_02) = 0
BEGIN
    RAISERROR('Please enter a valid date.', 11, 1);
    RETURN;
END

如果您不通过存储过程控制数据操作,则可以向表中添加检查约束(在清除现有错误数据之后)。

UPDATE Intmu11.matter3 SET mat3_04_02 = NULL
  WHERE ISDATE(mat3_04_02) = 0;

ALTER TABLE Intmu11 WITH NOCHECK 
  ADD CONSTRAINT mat3_04_02_valid_date CHECK (ISDATE(mat3_04_02)=1);

这样当错误消息冒泡到用户时,他们将看到约束名称,并希望能够将其映射到前端失败的数据入口点:

  

Msg 547,Level 16,State 0,Line 1
INSERT语句冲突   使用CHECK约束“mat3_04_02_valid_date”。冲突   发生在数据库“your_db”,表“Intmu11.matter3”,列中   'mat3_04_02'。
声明已经终止。

或者更好的是,首先使用正确的数据类型!同样,在将现有错误数据更新为NULL之后,您可以说:

ALTER TABLE Intmu11.matter3 ALTER COLUMN mat3_04_02 DATETIME;

现在当有人试图输入非日期时,他们会在用户尝试选择错误数据时遇到相同的错误:

  

Msg 241,Level 16,State 1,Line 1
当转换失败   从字符串转换日期和/或时间。

在SQL Server 2012中,您可以使用TRY_CONVERT()解决此问题,但您仍应尝试从头开始获取数据类型。

答案 1 :(得分:2)

检查查询位置

  ISDATE(mat3_04_02) = 1 
  AND 
  Coalesce(mat3_04_02, '') = ''

要成为一个日期,它必须有一个值。 但如果它没有值,则只匹配第二个条件。 这两个条件的交集(和)始终为false。

如果您正在寻找null,那么“mat3_04_02为空”但它仍将返回0行。

尝试

   SELECT mat3_02_01, CONVERT(datetime, mat3_04_02), mat3_04_02 
   FROM lntmu11.matter3
   WHERE ISDATE(mat3_04_02) = 1
   order by CONVERT(datetime, mat3_04_02) desc  

我认为您希望日期排序而不是字符串排序

问题始于找到有效日期,并变为查找无效日期

   SELECT mat3_02_01, mat3_04_02 
   FROM lntmu11.matter3
   WHERE ISDATE(mat3_04_02) = 0 
   AND mat3_04_02 is not null
   order by mat3_04_02) desc