2011-08-08 210 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消息。请恕我谦卑的道歉。 –

相关问题