我有一个用户定义的函数将整数列表拆分成值表。我正在使用它来解析输入,为给定的一组类型或状态选择一组记录。
这有效:
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])
如果我使用返回varchars的不同函数并将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的修改版本)。 Split函数是相同的,最后没有int作为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
)
我可以使用convert-to-strings解决方案,但我真的很想知道为什么这首先失败了。
答案 0 :(得分: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):
我的JOIN
版本,其中费用已转移到您正在进行的扫描中(click to enlarge):
以下是运行时指标(当然基于少量行) - (click to enlarge):
转换错误似乎源于该功能。所以我替换了自己(下面)。即使在添加了我们最初不知道的外键之后,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
所以,在我看来,你似乎想要摆脱这个功能。
此外,这是使用连接的一种方法,仍然使param可选:
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);