我使用的是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)
工会的结果是我所期待的;只有数字:
这就是我的问题所在。因为我想获得下一个可用的数字,我想使用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。
提前谢谢。
答案 0 :(得分:1)
使用bigint
或decimal
:
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;
提供以下执行计划:
您可以看到,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