2011-08-08 212 views
13

我收到錯誤「從字符串轉換爲uniqueidentifier時發生轉換失敗」,並且終於出現在我的繩索末端。我將問題縮小到儘可能小,同時保持錯誤的機智。如果要複製從這裏首先安裝CSV分流:在SQL Server中將字符串轉換爲uniqueidentifier錯誤時轉換失敗

http://www.sqlservercentral.com/articles/Tally+Table/72993/

這裏的測試代碼。我對SQL 2008 R2,但在數據庫是SQL 2005兼容:

IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[ZZZTESTTABLE]') AND type in (N'U')) 
DROP TABLE [dbo].[ZZZTESTTABLE] 
GO 

CREATE TABLE [dbo].[ZZZTESTTABLE](
    [Col1] [uniqueidentifier] NOT NULL, 
CONSTRAINT [PK_ZZZTESTTABLE] PRIMARY KEY CLUSTERED 
(
    [Col1] ASC 
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 
) ON [PRIMARY] 

-- Test table that I would like to check my values against 
insert dbo.ZZZTESTTABLE(Col1) values('85B049B7-CDD0-4995-B582-5A74523039C0') 

-- Test string that will be split into table in the DelimitedSplit8k function 
declare @temp varchar(max) = '918E809E-EA7A-44B5-B230-776C42594D91,6F8DBB54-5159-4C22-9B0A-7842464360A5' 

-- I'm trying to delete all data in the ZZZTESTTABLE that is not in my string but I get the error 
delete dbo.ZZZTESTTABLE 
where Col1 not in 
(
-- ERROR OCCURS HERE 
    select cast(Item as uniqueidentifier) from dbo.DelimitedSplit8K(@temp, ',') 
) 

這裏的DelimitedSplit8K函數的源,所以你不必去尋找它:

CREATE FUNCTION dbo.DelimitedSplit8K 
--===== Define I/O parameters 
     (@pString VARCHAR(8000), @pDelimiter CHAR(1)) 
RETURNS TABLE WITH SCHEMABINDING AS 
RETURN 
--===== "Inline" CTE Driven "Tally Table" produces values from 0 up to 10,000... 
    -- enough to cover VARCHAR(8000) 
    WITH E1(N) AS (
       SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
       SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
       SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 
       ),       --10E+1 or 10 rows 
     E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows 
     E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max 
cteTally(N) AS (--==== This provides the "zero base" and limits the number of rows right up front 
        -- for both a performance gain and prevention of accidental "overruns" 
       SELECT 0 UNION ALL 
       SELECT TOP (DATALENGTH(ISNULL(@pString,1))) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4 
       ), 
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter) 
       SELECT t.N+1 
        FROM cteTally t 
        WHERE (SUBSTRING(@pString,t.N,1) = @pDelimiter OR t.N = 0) 
       ) 
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found. 
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY s.N1), 
     Item  = SUBSTRING(@pString,s.N1,ISNULL(NULLIF(CHARINDEX(@pDelimiter,@pString,s.N1),0)-s.N1,8000)) 
    FROM cteStart s 
; 
+0

我會懷疑分裂功能,因爲那是複雜性。如果您添加一個WHERE項目不是空和LEN(Item)> 1到分割選擇,它是否仍然會給出錯誤? – hatchet

+0

「我剛在網上找到一些隨機腳本,我不明白它是如何工作的,但我打算使用,有人可以爲我調試嗎?」 –

+0

分割功能似乎沒問題。我也想知道爲什麼你的刪除不能像寫入那樣工作。如果改寫爲EXISTS,它將以相同的方式失敗。 – hatchet

回答

5

不知道發生了什麼在這裏,但問題似乎並沒有成爲的GUID的格式或的輸出功能。執行此作品:

declare @temp varchar(max) = '918E809E-EA7A-44B5-B230-776C42594D91,6F8DBB54-5159-4C22-9B0A-7842464360A5'  
select cast(Item as uniqueidentifier) from dbo.DelimitedSplit8K(@temp, ',') 

也許查詢處理器看函數的返回模式和說,它不能被轉換爲uniqueidentifier?希望別人能夠提供具體的答案。

選擇拆分函數的輸出到一個臨時表將工作:

select cast(Item as uniqueidentifier) as Item into #temp from dbo.DelimitedSplit8K(@temp, ',') 

-- I'm trying to delete all data in the ZZZTESTTABLE that is not in my string but I get the error 
delete dbo.ZZZTESTTABLE 
where Col1 not in 
(
-- ERROR OCCURS HERE 
    --select cast(Item as uniqueidentifier) from dbo.DelimitedSplit8K(@temp, ',') 
    select Item from #temp 
) 
+1

謝謝,這是我們提出的一個解決方案,也可能是我們將要使用的解決方案之一。我注意到它可能涉及到的一些事情是:我可以將Select語句從where子句中自行取出,並且工作正常。我也可以將NOT IN更改爲IN,並且不會出錯(儘管我沒有得到我想要的結果)。它似乎與UDF(用戶定義函數)如何返回或使用CTE(公用表表達式)有關。如果我將UDF更改爲在表中返回一個硬編碼的字符串,我將不會收到錯誤。我認爲是一個SQL錯誤或功能。非常感謝您對此進行調查! – CreativeJourney

1

外貌就像我第一次誤解了這個問題。做好產生重現錯誤的測試腳本。以下作品適用於我:

delete dbo.ZZZTESTTABLE 
WHERE Col1 in 
(
    select Z.Col1 
    from dbo.ZZZTESTTABLE Z 
    LEFT JOIN dbo.DelimitedSplit8K(@temp, ',') S on S.Item = Z.Col1 
    where S.Item is null 
) 
OPTION (force order) 
+0

它們都是有效的。我可以從where子句中自行選擇Select語句,並且工作正常。我也可以將NOT IN更改爲IN,並且不會出錯(儘管我沒有得到我想要的結果)。它似乎與UDF(用戶定義函數)如何返回或使用CTE(公用表表達式)有關。如果我改變UDF返回一個硬編碼的字符串,我不會得到錯誤。看起來像是SQL bug或功能。 – CreativeJourney

+0

@CreativeJourney - 如果分割函數正在吐出不能轉換爲uniqueidentifer的字符串,那麼它將在沒有臨時表的情況下工作 – RichardTheKiwi

2

爲什麼投射Item到uniqueidentifier時,您可以通過其他方式做到這一點。

而不是

where Col1 not in 
(
-- ERROR OCCURS HERE 
    select cast(Item as uniqueidentifier) from dbo.DelimitedSplit8K(@temp, ',') 
) 

你可以試試這個:

where cast(Col1 as varchar(64)) not in 
(
    select Item 
    from dbo.DelimitedSplit8K(@temp, ',') 
) 
+0

,那麼它會以某種方式破壞字符串。避免使用CAST會消除錯誤,但結果仍然不正確(即某些東西會被刪除,不應該反過來)。 – hatchet

+0

是一個樂觀主義者,我只是假設函數DelimitedSplit8K做的很好。測試應該是非常簡單的,即運行SELECT * FROM dbo.DelimitedSplit8K('918E809E-EA7A-44B5-B230-776C42594D91,6F8DBB54-5159-4C22-9B0A-7842464360A5',',') – Skorpioh

+0

split函數不會拆分字符串好的,你的解決方案確實可以解決任何奇怪的問題,導致在原始代碼中失敗。 – hatchet

8

使用該UDF的確實是作出有關執行順序程序的假設。它假定在​​UDF內的WHERE子句將在,cast(item as uniqueidentifier)之前被評估爲。這個假設是錯誤的,因爲優化器可以自由地更改將演算WHERE子句移動到演員上方的計劃,並且淨效果是要求演員將部分令牌轉換爲guid(即像18E809E-EA7A-44B5-B230-776C42594D91這樣的字符串)。

如需更詳細的解答,請閱讀T-SQL functions do no imply a certain order of execution

作爲一種變通方法,您可以強制NULL到UDF的使用行的預測值不符合WHERE子句:

CREATE FUNCTION dbo.DelimitedSplit8K 
... 
cteStart(N1, nullify) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter) 
       SELECT t.N+1, 
        case when (SUBSTRING(@pString,t.N,1) = @pDelimiter OR t.N = 0) then 1 else 0 end 
        FROM cteTally t 
        WHERE (SUBSTRING(@pString,t.N,1) = @pDelimiter OR t.N = 0) 
       ) 
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found. 
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY s.N1), 
     Item  = case s.nullify 
      when 1 then SUBSTRING(@pString,s.N1,ISNULL(NULLIF(CHARINDEX(@pDelimiter,@pString,s.N1),0)-s.N1,8000)) 
      else null 
      end 
    FROM cteStart s; 
go 

因爲CASE表達式是保證科協之前評估(由於CAST的輸入是CASE的輸出)因此WHERE子句的重新排序是安全的。

+0

由於我誤讀了您的博客,因此我向您發送了帶有錯誤假設的LinkedIn消息。請恕我謙卑的道歉。 –

相關問題