【问题标题】:SQL server - Conversion failed error but not reallySQL server - 转换失败错误但不是真的
【发布时间】:2013-03-27 05:38:08
【问题描述】:

我有一个用户定义的函数,可以将整数列表拆分为值表。我正在使用它来解析输入以选择一组给定类型或状态的记录。

这行得通:

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', ','))

Requests 查询返回错误“将 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 相同:

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
)

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

【问题讨论】:

  • 另外,从 Requests 中选择 *,其中 (1,2,3) 中的 RequestStatusUID 可以正常工作。只是我需要在那里有一个变量而不是字符串文字。
  • 我无法重现,即使我在函数末尾去掉了显式转换。你能在 sqlfiddle.com 上设置一个完整的重现吗?您是否也尝试过任何返回 INT 的 其他 TVF,或者您是否与此特定功能结婚?由于观察到的性能问题,我倾向于远离 TVF 中的递归 CTE 方法。
  • 在添加外键约束之前,我无法在 sqlfiddle.com 上重现它...link
  • 看到了吗?丢失的信息。什么外键约束。
  • 到状态表。但是,我再次删除它后,问题仍然存在。 sqlfiddle

标签: sql-server-2008 split type-conversion


【解决方案1】:

我想你会发现这种语法的性能要好很多:

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

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

另外,这是使用连接的一种方法,并且仍然使参数可选:

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);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-09-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-13
    • 1970-01-01
    • 2017-11-26
    • 2014-01-17
    相关资源
    最近更新 更多