我有一些行,我试图转换为整数来按顺序获取最后一个数字。
这是我的原始查询。
SELECT
MAX(CAST(REPLACE(ItemName, 'CA', '') AS INT)) + 1
FROM InventoryItem ii
JOIN InventoryItemDepartment iid
ON ii.ItemCode = iid.ItemCode
WHERE iid.DepartmentCode = 'Filters'
AND ItemName LIKE 'CA4%____'
AND CAST(REPLACE(ItemName, 'CA', '') AS INT) < 41000
但是我收到一条错误消息:
Error (1,1): Conversion failed when converting the nvarchar value '41020-S' to data type int.
显然,我知道这条消息的含义。但是我很困惑,为什么它会抛出错误,因为我指定的WHERE
条款的唯一目的是排除可能导致失败的记录。
如果我修改查询只选择原始值,而不进行任何替换或转换......
SELECT
ItemName
FROM InventoryItem ii
JOIN InventoryItemDepartment iid
ON ii.ItemCode = iid.ItemCode
WHERE iid.DepartmentCode = 'Filters'
AND ItemName LIKE 'CA4%____'
这会返回一些数据:
CA40000
CA40001
CA40002
CA40003
CA40004
CA40005
.... etc
麻烦的价值&#39; 41020-S&#39; (原来&#39; CA41020-S&#39;替换后)并未包含在第二个调试结果集中,正如我预期的那样。
任何人都可以帮助我解释这种奇怪的行为,以及我怎么可能克服它?
答案 0 :(得分:2)
WHERE
子句 * 中的单个谓词的评估顺序有 no 保证。 (SQL Server也不保证不在SELECT
子句中计算应该由WHERE
子句过滤的值的表达式。
不幸的是,保证过滤器生效的最有效方法是将查询分成两个单独的查询 - 第一个查询执行所需的过滤并将其结果放入临时表/表变量,第二个查询构建自那和执行数据转换。 1
几乎总能运行的稍弱的方式,除了有时使用聚合它可能有点搞笑 2 是使用CASE
表达式代替:
SELECT
MAX(CASE WHEN iid.DepartmentCode = 'Filters'
AND ItemName LIKE 'CA4%____'
THEN CAST(REPLACE(ItemName, 'CA', '') AS INT)
ELSE 60000 END) + 1
FROM InventoryItem ii
JOIN InventoryItemDepartment iid
ON ii.ItemCode = iid.ItemCode
WHERE CASE WHEN iid.DepartmentCode = 'Filters'
AND ItemName LIKE 'CA4%____'
THEN CAST(REPLACE(ItemName, 'CA', '') AS INT)
ELSE 60000 END < 41000
1 这违反了构建单个大型查询的通常建议,并让优化器找到评估查询的最佳方式。不幸的是,优化器经常会出错,并且没有迹象表明微软计划解决这个问题,因为它是known issue for more than a decade + 。
请注意,任何旨在通过重新排列查询(例如将部件放入子查询)或添加其他保护条款来解决此问题的答案,可能表面上似乎可以解决问题意外强制优化器选择不同的计划。但是,您无法保证优化程序是否或何时将返回使用会再次生成错误消息的计划。
2 CASE
:&#34;在某些情况下,在CASE语句接收表达式的结果作为其输入之前计算表达式。评估这些表达式的错误是可能的。首先评估出现在CASE语句的WHEN参数中的聚合表达式,然后将其提供给CASE语句。&#34;
* 与其他一些编程语言不同,SQL不提供诸如从左到右评估的保证,也没有任何方式来影响它是否表现出任何短路行为。
+ 此问题最初在用户语音上报告。不幸的是,在它迁移到Azure反馈论坛时,很多细节已被压缩到单个Microsoft&#34;响应&#34;这让人难以阅读,而且,亲爱的&#34;也失去了之前收到的用户声音的大量选票。
答案 1 :(得分:1)
尝试此查询,更改where子句中的条件,以便仅在CA4
SELECT
MAX(CAST(REPLACE(ItemName, 'CA', '') AS INT)) + 1
FROM InventoryItem ii
JOIN InventoryItemDepartment iid
ON ii.ItemCode = iid.ItemCode
WHERE iid.DepartmentCode = 'Filters'
AND ItemName LIKE 'CA4____'
AND CAST(REPLACE(ItemName, 'CA', '') AS INT) < 41000
如果执行计划选择首先评估条件CAST(REPLACE(ItemName, 'CA', '') AS INT) < 41000
,则仍然高于查询可能会失败。为了安全起见,您可以使用以下查询。
SELECT
MAX(CAST(REPLACE(ItemName, 'CA', '') AS INT)) + 1
FROM
( SELECT ItemName
FROM InventoryItem ii
JOIN InventoryItemDepartment iid
ON ii.ItemCode = iid.ItemCode
WHERE iid.DepartmentCode = 'Filters'
AND ItemName LIKE 'CA4____'
) AS SubQ
WHERE CAST(REPLACE(ItemName, 'CA', '') AS INT) < 41000
答案 2 :(得分:1)
这里有两个学习点:
尽可能避免在CAST
表达式中使用CONVERT
和WHERE
- 它们会破坏查询并降低性能,因为会对要排除的行执行转换。
MAX()
也适用于字符串值。
假设您想要找到最大值的值在CA40000到CA40999的范围内并且数据格式正确,除了偶然的后缀,例如 41020-S 会破坏您的查询,你可以使用:
SELECT CAST(MAX(SUBSTRING(ItemName, 3, 5)) AS INT) + 1
FROM InventoryItem ii
INNER JOIN InventoryItemDepartment iid
ON ii.ItemCode = iid.ItemCode
WHERE iid.DepartmentCode = 'Filters'
AND ItemName LIKE 'CA40___%'
在一个非常大的表中,MAX(LEFT(ItemName, 7))
可能会更快,因为它可以直接使用ItemName上的索引,但这会使查询更复杂。
如果数据在CA40之后可能具有非数字值,则可以使用与LIKE的范围匹配来避免错误:LIKE 'CA40[0-9][0-9][0-9]%'
答案 3 :(得分:0)
使用此
MAX(CAST(REPLACE(ItemName, 'CA', '') AS INT)) + 1
取代
CAST(REPLACE(REPLACE(name, 'CA', ''),'-S','') AS INT)
而
CAST(REPLACE(ItemName, 'CA', '') AS INT)
到位
from os import environ
environ['SCIPY_PIL_IMAGE_VIEWER'] = {your image viewer pgm} # e.g. C:/IrfanView/i_view32.exe
答案 4 :(得分:0)
如果要删除任何包含意外值的行,可以使用ISNUMERIC
,例如其中包含未知字符。
SELECT
MAX(CAST(REPLACE(ItemName, 'CA', '') AS INT)) + 1
FROM InventoryItem ii
JOIN InventoryItemDepartment iid
ON ii.ItemCode = iid.ItemCode
WHERE iid.DepartmentCode = 'Filters'
AND ItemName LIKE 'CA4%____'
AND ISNUMERIC(CAST(REPLACE(ItemName, 'CA', '')) = 1 AND CAST(REPLACE(ItemName, 'CA', '') AS INT) < 41000.
注意:ISNUMERIC
并不完美。它会将某些字符视为数字。你可以阅读它here。