2013-03-26 80 views
0

我有一个用户定义的函数来将整数列表拆分成一个值表。我正在使用它来解析输入以为给定的一组类型或状态选择一组记录。SQL服务器 - 转换失败错误,但不是真的

这工作:

select * from RequestStatus 
where RequestStatusUID in (select [value] from dbo.SplitIDs('1,2,3', ',')) 

这不:

select * from Requests 
where RequestStatusUID in (select [value] from dbo.SplitIDs('1,2,3', ',')) 

的请求的查询将返回错误“转换为varchar值 '1,2,3' 到的数据类型时转换失败int。“两个表上的RequestStatusUID都是int列。这两个解释计划对我来说都是一样的。该函数在无关查询中以完全相同的方式工作。据我所知,只有Requests表有问题。

CREATE TABLE [dbo].[Requests] ( 
[RequestUID]  int IDENTITY(1,1) NOT NULL, 
[UserUID]   int NOT NULL, 
[LocationUID]  int NOT NULL, 
[DateOpened]  date NULL, 
[DateClosed]  date NULL, 
[RequestStatusUID] int NOT NULL, 
[DiscussionUID]  int NULL, 
[RequestTypeUID] int NOT NULL, 
[RequestNo]   varchar(16) NOT NULL, 
[LastUpdateUID]  int NOT NULL, 
[LastUpdated]  date NOT NULL, 
CONSTRAINT [PK_Requests] PRIMARY KEY NONCLUSTERED([RequestUID]) 

如果我使用不同的函数返回VARCHAR处理它的工作和我的RequestStatusUID列转换为VARCHAR,以及:

select * from Requests 
where cast(RequestStatusUID as varchar(4)) in (select [value] from dbo.Split('1,2,3', ',')) 

作为参考,SplitIDs功能我使用(一Arnold Fribble's solution的修改版本)。分割功能没有投为INT末相同:

ALTER FUNCTION [dbo].[SplitIDs] (@str VARCHAR(MAX), @delim char(1)=',') 
RETURNS TABLE 
AS 
RETURN 
(
    with cte as (
     select 0 a, 1 b 
     union all 
     select b, cast(charindex(@delim, @str, b) + 1 as int) 
     from cte 
     where b > a 
    ) 
    select cast(substring(@str,a, 
    case when b > 1 then b-a-1 else len(@str) - a + 1 end) as int) [value]  
    from cte where a >0 
) 

我可以使用转换到字符串解决方案,但我真的很想知道这是为什么摆在首位失败。

+0

此外,选择*。这只是我需要有一个变量,而不是字符串文字。 – ERR 2013-03-26 20:01:46

+0

即使我在函数结尾处取消了显式演员,我也无法重现。你能在sqlfiddle.com上设置一个完整的repro吗?还有,你是否尝试过任何返回INT的其他* TVF,或者你是否结婚了这个特定的函数?由于观察到性能问题,我倾向于远离TVF内的递归CTE方法。 – 2013-03-26 20:04:42

+0

我无法在sqlfiddle.com上重现它,直到我添加了外键约束... [链接](http://sqlfiddle.com/#!3/ab4a9/1) – ERR 2013-03-26 20:25:10

回答

2

我想你会发现,这句法执行好了很多:

SELECT r.* FROM dbo.Requests AS r 
INNER JOIN dbo.SplitIDs('1,2,3', ',') AS s 
ON r.RequestStatusUID = s.value; 

谓词仍然有一堆隐含转换的,由于你的功能选择,但加入消除了昂贵的表阀芯。如果您使用正确的列列表,则仅限于实际需要的列,而不是使用SELECT *You should change this even if you do need all of the columns

IN()查询,用昂贵的表阀芯(click to enlarge):

enter image description here

JOIN版本,其中的成本转移到你正在做反正扫描(click to enlarge):

enter image description here

这里是运行时间度量标准(当然基于少量的行) - (click to enlarge):

enter image description here

的转换错误似乎是从功能而产生。所以我用我自己的(下面)来代替。即使在添加我们最初并不知道的外键之后,也可以使用I was unable to reproduce the error。我不确定原始函数到底出了什么问题,但是它所创建的所有这些隐式转换似乎都会导致优化器在某个时刻出现问题。所以我建议这个改为:

CREATE FUNCTION dbo.SplitInts 
(
    @List  VARCHAR(MAX), 
    @Delimiter VARCHAR(255) = ',' 
) 
RETURNS TABLE 
WITH SCHEMABINDING 
AS 
    RETURN 
    ( 
     SELECT [value] = y.i.value('(./text())[1]', 'int') 
     FROM 
     ( 
     SELECT x = CONVERT(XML, '<i>' 
      + REPLACE(@List, @Delimiter, '</i><i>') 
      + '</i>').query('.') 
    ) AS a CROSS APPLY x.nodes('i') AS y(i) 
    ); 
GO 

所以,在我看来,你想摆脱这个功能。

而且,这里是使用加入,仍然让帕拉姆可选的一种方法:从采购,其中RequestStatusUID在(1,2,3)的作品就好了

DECLARE @param VARCHAR(MAX) = NULL;-- also try = '1,2,3'; 

SELECT r.* 
FROM dbo.Requests AS r 
LEFT OUTER JOIN dbo.SplitInts(@param, default) AS s 
ON r.RequestStatusUID = s.value 
WHERE (r.RequestStatusUID = s.value OR @param IS NULL);