簡體   English   中英

將Nvarchar轉換為Int失敗的SQL Server 2008

[英]Cast Nvarchar to Int Failing SQL Server 2008

我有一些行要轉換為整數,以獲取序列中的最后一個數字。

這是我的原始查詢。

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

正如我期望的那樣,麻煩的值“ 41020-S”(最初是“替換”之后的“ CA41020-S”)未包含在第二個調試結果集中。

誰能幫助我解釋這種奇怪的行為,以及我可能如何克服它?

WHERE子句*中 不能保證單個謂詞的評估順序。 (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這與建立單個大型查詢並讓優化器找到評估查詢的最佳方法的通常建議背道而馳。 不幸的是,優化往往得到此一子錯,而且也沒有跡象表明,因為它是一個微軟的計划來解決這個已知問題超過十年 +。

請注意,任何試圖通過重新安排查詢(例如將零件放入子查詢中)或添加其他保護子句來解決此問題的答案,表面看來都是通過無意間迫使優化器選擇了不同的計划來解決的。 但是,您無法保證優化程序是否或何時返回使用再次生成錯誤消息的計划。

2 CASE :“在某些情況下,在CASE語句接收表達式的結果作為輸入之前先對表達式求值。評估這些表達式時可能會出錯。首先對出現在CASE語句的WHEN參數中的聚合表達式進行求值,然后再求值提供給CASE語句。”

*與某些其他編程語言不同,SQL不提供從左到右的評估之類的保證,也不提供任何方法來影響它是否表現出任何短路行為。

+此問題最初是在“用戶語音”上報告的。 不幸的是,在遷移到Azure反饋論壇時,很多細節被壓縮到單個Microsoft“響應”中,這使得它很難閱讀,“哦,親愛的”也失去了以前在User Voise上獲得的大量票數。

嘗試此查詢,更改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

這里有兩個學習要點:

  1. 避免使用CASTCONVERTWHERE表達式盡可能-他們fragilize查詢並降低性能,因為在其將被排除在外行執行轉換。

  2. 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]%'

用這個

MAX(CAST(REPLACE(REPLACE(name, 'CA', ''),'-S','') AS INT)) + 1

代替

MAX(CAST(REPLACE(ItemName, 'CA', '') AS INT)) + 1

而對於

CAST(REPLACE(REPLACE(name, 'CA', ''),'-S','') AS INT)

到位

CAST(REPLACE(ItemName, 'CA', '') AS INT)

如果要刪除任何帶有意外值(例如,其中包含未知字符)的行,則可以使用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並不完美。 它將某些字符也視為數字。 您可以在這里閱讀。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM