过滤掉的字符串仍会导致SQL语句出错

时间:2016-06-14 11:27:35

标签: sql sql-server-2008-r2

我使用的是SQL Server 2008 R2。

我正在编写零件编号生成器应用程序。我们的部件号由九位数字值组成,例如914602001.在创建新数字之前,如果已存在,则需要检查多个源。为了节省时间,我创建了一些这些来源的简单联合。工会如下:

SELECT DISTINCT ItemNumber
FROM dbo.EngPartNumbers
WHERE (ItemNumber NOT LIKE '%[^0-9]%')
UNION
SELECT DISTINCT ValueText COLLATE SQL_Latin1_General_CP1_CI_AS AS ItemNumber
FROM [PDMWE-Bel-ArtProductsDocManagement].dbo.VariableValue AS vv
WHERE (ValueText NOT LIKE '%[^0-9]%') AND (LEN(ValueText) = 9)

第一个表 EngPartNumbers 是导入SQL的Excel文件。它包含一列ItemNumber,并且是varchar数据类型。它必须是varchar,因为我们在命名约定中使用了字母。

第二个表正在寻找我们的EPDM,其中VariableValue是将所有值存储到变量中的表,它存放在Variables表中。 ValueText列是一个包含所有变量值的varchar。就我而言,我只关心9位数字值,所以我应用了最后一行:

WHERE (ValueText NOT LIKE '%[^0-9]%') AND (LEN(ValueText) = 9) 

工会的结果是我所期待的;只有数字:

Query results from vw_PDM_Union_Items

这就是我的问题所在。因为我想获得下一个可用的数字,我想使用int数据类型,而不是varchar。当我从视图中选择所有内容时,将列作为int进行CAST,并添加WHERE子句,如下所示:

SELECT ItemNumber 
FROM (
    SELECT CAST(ItemNumber AS int) AS ItemNumber 
    FROM vw_PDM_Union_Items
    ) AS x
WHERE ItemNumber < 800900000

我收到以下错误:

  

将nvarchar值“SW-Revision”转换为数据类型int。

时转换失败

经过研究,我注意到“SW-Revision”是指变量的值,它存储在VariableValue表的ValueText列中。对我而言,这应该不重要,因为我正在查看已经过滤掉这些不良数据的视图。我甚至尝试将我的视图包装在一个将列作为int进行CAST的select语句中,如下所示:

SELECT CAST(ItemNumber AS int) AS ItemNumber
FROM (SELECT DISTINCT ItemNumber
      FROM dbo.EngPartNumbers
      WHERE (ItemNumber NOT LIKE '%[^0-9]%')
      UNION
      SELECT DISTINCT ValueText 
          COLLATE SQL_Latin1_General_CP1_CI_AS AS ItemNumber
      FROM [PDMWE-Bel-ArtProductsDocManagement].dbo.VariableValue AS vv
      WHERE (ValueText NOT LIKE '%[^0-9]%') 
          AND (LEN(ValueText) = 9)) AS item
WHERE     (ItemNumber NOT LIKE '%[^0-9]%')

但我仍然得到同样的错误。为什么SQL这样做?后台发生了什么导致它查看原始表?如果有人能够阐明这种情况并给我一个更好的方法来实现这一点,我们将不胜感激。出于操作原因,我想使用int列,而不是varchar。

提前谢谢。

4 个答案:

答案 0 :(得分:1)

使用bigintdecimal

SELECT ItemNumber 
FROM (SELECT CAST(ItemNumber AS decimal(38)) AS ItemNumber 
      FROM vw_PDM_Union_Items
     ) x
WHERE ItemNumber < 800900000;  -- large values are treated as numeric/decimal

编辑:

您可能拥有比ItemNumber更大的值。使小数点大小可以解决问题吗?

您可以查看最大值:

SELECT TOP 1 ItemNumber
FROM vw_PDM_Union_Items
ORDER BY LENGTH(ItemNumber) DESC, ItemNumber DESC;

我还注意到子查询中有长度限制。由于SQL Server的工作方式,在尝试转换值后,可能会调用

您可以使用case

强制执行评估顺序
SELECT ItemNumber 
FROM (SELECT CAST(CASE WHEN ItemNumber NOT LIKE '%[^0-9]%'
                        THEN LEFT(ItemNumber, 9)
                   END) AS decimal(10)) AS ItemNumber
      FROM vw_PDM_Union_Items
     ) x
WHERE ItemNumber < 800900000;  -- large values are treated as numeric/decimal

我可能会建议您实际将此逻辑移到视图中。

答案 1 :(得分:1)

您可以非常轻松地重新创建此错误

SELECT  *
FROM    (   SELECT  ValueText
            FROM    (VALUES ('A'), ('1')) t (ValueText)
            WHERE   t.ValueText NOT LIKE '%[^0-9]%'
        ) t
WHERE   ValueText < 10;
  

Msg 245,Level 16,State 1,Line 1

     

将varchar值'A'转换为数据类型int时,转换失败。

原因是无论您使用的是视图还是普通查询,都无法控制SQL Server应用WHERE谓词的顺序。

由于该错误,我们无法通过检查执行计划来查看SQL服务器正在执行的操作,而是快速更改查询(SQL Server 2012 +):

SELECT  *
FROM    (   SELECT  ValueText
            FROM    (VALUES ('A'), ('1')) t (ValueText)
            WHERE   t.ValueText NOT LIKE '%[^0-9]%'
        ) t
WHERE   TRY_CONVERT(INT, ValueText) < 10;

提供以下执行计划:

enter image description here

您可以看到,SQL Server有效地简化了查询:

SELECT  ValueText
FROM    (VALUES ('A'), ('1')) t (ValueText)
WHERE   TRY_CONVERT(INT, ValueText) < 10;
AND     t.ValueText NOT LIKE '%[^0-9]%';

隐式转换也是如此,所以在初始查询中你只是执行:

SELECT  ValueText
FROM    (VALUES ('A'), ('1')) t (ValueText)
WHERE   ValueText < 10;
AND     t.ValueText NOT LIKE '%[^0-9]%';

因此,在评估A < 10时会出现错误,因为SQL Server会尝试将A隐式转换为整数,以便将其与10进行比较。

您需要解决的是中间实现,也就是说,强制SQL Server首先评估子查询,存储结果,然后应用外部谓词。说起来容易做起来难。有a connect item可以请求此作为查询提示,但是,目前有两个主要的解决方法。

<强> 1。使用临时表/表变量/多步TVF来实现结果。

DECLARE @T TABLE (ValueText INT)
INSERT @T (ValueText)
SELECT  ValueText
FROM    (VALUES ('A'), ('1')) t (ValueText)
WHERE   t.ValueText NOT LIKE '%[^0-9]%';

SELECT  *
FROM    @T
WHERE   ValueText < 10;

由于您想使用视图,这显然不适合您。

<强> 2。使用TOP 2147483647(大规模黑客)

SELECT  ValueText
FROM    (   SELECT  TOP 2147483647 ValueText
            FROM    (VALUES ('A'), ('1')) t (ValueText)
            WHERE   t.ValueText NOT LIKE '%[^0-9]%'
        ) t
WHERE   ValueText < 10;

这是一个黑客,并不能保证工作(虽然在大多数情况下我必须使用它),但使用TOP和一个比你需要的更大的数字通常会迫使中间实现结果

第3。使用CASE表达式删除记录

SELECT  *
FROM    (   SELECT  ValueText = CASE WHEN ValueText NOT LIKE '%[^0-9]%' THEN ValueText END
            FROM    (VALUES ('A'), ('1')) t (ValueText)
            WHERE   t.ValueText NOT LIKE '%[^0-9]%'
        ) t
WHERE   ValueText < 10;

同样,这是可行的,但是我看不出它能保证工作,没有理由未来的更新不会识别where谓词和case谓词是相同的并且优化case表达式。

答案 2 :(得分:0)

现在已经复制了它。

select * 
from (
    SELECT CAST(valuetext AS int)  AS ItemNumber
    FROM (
          select valuetext='SW-Revision' 
          union 
          select '123456789'
         ) AS vv
    WHERE (ValueText NOT LIKE '%[^0-9]%') 
) wrapper
where ItemNumber > 0;

我想这是由于谓词推动。作为解决方法

SELECT * 
FROM (
    SELECT CAST(CASE WHEN valuetext NOT LIKE '%[^0-9]%' THEN valuetext END AS int)  AS ItemNumber
    FROM (
          select valuetext='SW-Revision' 
          union 
          select '123456789'
         ) AS vv
    WHERE (ValueText NOT LIKE '%[^0-9]%') 
) wrapper
WHERE ItemNumber > 0

答案 3 :(得分:0)

我很好奇这是否有用......

SELECT ItemNumber 
FROM (
    SELECT case isnumeric(ItemNumber) 
        when 1 then CAST(ItemNumber AS int) 
        else -999999999
        end
        AS ItemNumber
    FROM vw_PDM_Union_Items
) AS x
WHERE ItemNumber < 800900000 
    and ItemNumber>-999999999