2011-08-08 32 views
13

"文字列からuniqueidentifierに変換するときに変換に失敗しました"というエラーが発生しました。私はタクトでエラーを保ちながら、私の問題を可能な限り小さく絞りました。あなたが再現したい場合は、まずここからCSVスプリッタをインストールします。SQL Serverの文字列からuniqueidentifierエラーに変換するときに変換に失敗しました

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

をここでテストコードです。私は、SQL 2008R2ではなく、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 Itemがnullでなく、LEN(Item)> 1をsplit selectに追加すると、それでもエラーが出ますか? – hatchet

+0

"インターネット上でランダムなスクリプトが見つかっただけですが、どのように動作するのか理解できませんが、使用する予定です。誰かが私のためにデバッグできますか? –

+0

スプリット機能はOKのようです。私はあなたの削除が書かれたように動作しない理由を知りたいと思います。 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節から取り出してうまく動作します。私はまた、単にINにNOT INを変更することができますし、私はエラーを受け取りません(私は望む結果が得られません)。これは、UDF(ユーザー定義関数)がCTE(Common Table Expression)を返す方法または使用する方法に関係しているようです。テーブルでハードコーディングされた文字列を返すように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

これらはすべて有効です。 Select文を単独のwhere節から取り出しても問題ありません。私はまた、単にINにNOT INを変更することができますし、私はエラーを受け取りません(私は望む結果が得られません)。これは、UDF(ユーザー定義関数)がCTE(Common Table Expression)を返す方法または使用する方法に関係しているようです。ハードコードされた文字列を返すようにUDFを変更した場合、エラーは発生しません。 SQLのバグや機能のようです。 – CreativeJourney

+0

@CreativeJourney - これは、テンポラリテーブルなしで動作します。 – RichardTheKiwi

2

なぜあなたは別の方法でそれを行うことができますユニーク識別子にキャストアイテム。

代わりの

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

split関数がuniqueidentiferに変換できない文字列を吐き出している場合、何らかの形で文字列をマングリングしています。 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をお読みください。あなたはWHERE句満たしていない行に対して、UDFの予測値にNULLを強制することができます回避策として

:CASE式がCAST前に評価されることが保証されているので

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 

を( CASTの入力がCASEの出力であるため)、WHERE句の並べ替えは安全です。

+0

私はあなたのブログを誤読したので、間違った前提でLinkedInメッセージを送ってきました。私の謙虚な謝罪を無視してください。 –

関連する問題