【问题标题】:stored proc - executing a query with NOT IN where clause存储过程 - 使用 NOT IN where 子句执行查询
【发布时间】:2011-07-06 06:45:17
【问题描述】:

我有一个存储过程

Create PROCEDURE abc      
  @sRemovePreviouslySelectedWhereClause nvarchar(max)
AS
BEGIN

SELECT * 
      FROM table 
     WHERE nId NOT IN (@sRemovePreviouslySelectedWhereClause)

END;

参数@sRemovePreviouslySelectedWhereClause 可以具有类似 0,1 的值。但这失败并显示错误消息:

将 nvarchar 值“0,1”转换为数据类型 int 时转换失败。

除了动态查询还有其他方法可以实现吗?

【问题讨论】:

  • 据我所知,没有任何供应商支持使用变量为 IN/NOT IN 子句提供逗号分隔列表的 SQL。使用动态 SQL,或将值列表转换为表值函数/等以进行比较。
  • 这是最常见的问题之一。您会在 SO 上找到许多重复项(或非常接近的问题)。

标签: sql sql-server tsql


【解决方案1】:

首先,创建一个拆分函数,将您的分隔字符串拆分为一个表格:

CREATE FUNCTION [dbo].[Split]
(   
 @String varchar(max)
,@Delimiter char
)
RETURNS @Results table
(
 Ordinal int
,StringValue varchar(max)
)
as
begin

    set @String = isnull(@String,'')
    set @Delimiter = isnull(@Delimiter,'')

    declare
     @TempString varchar(max) = @String
    ,@Ordinal int = 0
    ,@CharIndex int = 0

    set @CharIndex = charindex(@Delimiter, @TempString)
    while @CharIndex != 0 begin     
        set @Ordinal += 1       
        insert @Results values
        (
         @Ordinal
        ,substring(@TempString, 0, @CharIndex)
        )       
        set @TempString = substring(@TempString, @CharIndex + 1, len(@TempString) - @CharIndex)     
        set @CharIndex = charindex(@Delimiter, @TempString)
    end

    if @TempString != '' begin
        set @Ordinal += 1 
        insert @Results values
        (
         @Ordinal
        ,@TempString
        )
    end

    return
end

然后将 where 子句更改如下:

select
 t.*
from [yourTable] t
where t.[ID] not in (select cast([StringValue] as int) from dbo.Split(@sRemovePreviouslySelectedWhereClause,','))

【讨论】:

    【解决方案2】:
    Create FUNCTION [dbo].[fn_Split] (  
    @List nvarchar(2000),   @SplitOn nvarchar(5)
     )   
    RETURNS @RtnValue table  ( 
        Value nvarchar(100) ) 
     AS   
    BEGIN 
    While (Charindex(@SplitOn,@List)>0) 
    Begin  
    Insert Into @RtnValue (value)
     Select    Value = ltrim(rtrim(Substring(@List,1,Charindex(@SplitOn,@List)-1))) 
    Set @List = Substring(@List,Charindex(@SplitOn,@List)+len(@SplitOn),len(@List)) 
    End  
    Insert Into @RtnValue (Value)
        Select Value = ltrim(rtrim(@List))
    
        Return 
    END
    
    **********
    Create PROCEDURE abc      
      @sRemovePreviouslySelectedWhereClause nvarchar(max)
    AS
    BEGIN
    
    SELECT * 
          FROM Table 
         WHERE nId NOT IN (select * from dbo.fn_Split(@sRemovePreviouslySelectedWhereClause,','))
    
    END;
    

    【讨论】:

      【解决方案3】:

      您尝试的这种方法不起作用。但如果您使用的是 SQL Server 2008,则可以使用名为 Table Valued Parameters 的新功能。

      基本上,您创建一个用户定义的表类型

      CREATE TYPE dbo.nIdTable AS TABLE(nID INT)
      

      然后您可以从外部(例如来自 ADO.NET 等)在该 TVP 中传递多个值:

      CREATE PROCEDURE abc(@idValues dbo.nIdTable READONLY)
      

      并在存储过程中使用该表变量:

      SELECT * 
      FROM table 
      WHERE nId NOT IN (SELECT nID FROM @idValues)
      

      【讨论】:

      • 似乎有人否决了所有答案,包括问题本身。
      【解决方案4】:

      对于此类查询,您需要使用动态 sql。

      首先构造查询和

      SET @sql = 'select * from table 
      where nId not in (' + @sRemovePreviouslySelectedWhereClause+ ')'
      

      然后使用EXEC(@sql) 运行查询。

      【讨论】:

      • 这正是不是使用动态sql的地方。你不只是像那样连接 sql ......它很危险并且会受到 sql 注入的影响。请参阅我上面的解决方案。动态 sql 最好以参数化方式 ala sp_executesql 使用。不过,我并没有对你投反对票。
      【解决方案5】:

      您必须将@sRemovePreviousSelectedWhereClause 参数用',' 分开,并将结果值放在临时表中。然后你的选择看起来像

      select * from table where nId not in (select nId from #tempIds)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-03-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-09-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多