带有可选参数SQL的IN子句

时间:2012-11-25 23:17:39

标签: sql stored-procedures

我有一个存储过程,它根据2个输入参数返回一组数据。其中一个参数是可选的,所以我正在使用

WHERE 
(tbl_Process.ProjectID = @ProjectID)
AND 
(tbl_AnalysisLookup.AnalysisCodeID = 7)
AND
(tbl_ProcessSubStep.ProcessID = ISNULL(@ProcessID,tbl_ProcessSubStep.ProcessID))

@ProcessID是可选参数,因此用户可能/可能不提供它。 现在我需要更改我的存储过程以适应多个ProcessId,即用户现在可以选择多个ProcessId,Single ProcessID或No ProcessID的列表,并且存储过程应该处理所有这些场景。除非绝对必要,否则不使用动态查询即可实现此目的的最佳方法是什么。 简而言之,我希望我的存储过程处理具有多个值的可选参数(WHERE IN子句)。下面提供了我从中获得的网页的解决方案和相关链接。这是一篇非常好的文章,可以帮助您根据自己的要求选择合适的解决方案。

3 个答案:

答案 0 :(得分:1)

我终于想出了如何实现这一目标。有几种方法可以做到这一点,我现在使用的是一个基于分隔符拆分ProcessID的字符串然后将它们插入表中的函数。然后在我的存储过程中使用该表。这是代码和网页链接。

<强> http://www.codeproject.com/Articles/58780/Techniques-for-In-Clause-and-SQL-Server

CREATE FUNCTION [dbo].[ufnDelimitedBigIntToTable]
(
@List varchar(max), @Delimiter varchar(10)
)
RETURNS @Ids TABLE
(Id bigint) AS
BEGIN
DECLARE @list1 VARCHAR(MAX), @Pos INT, @rList VARCHAR(MAX)
SET @List = LTRIM(RTRIM(@List)) + @Delimiter
SET @pos = CHARINDEX(@Delimiter, @List, 1)
WHILE @pos > 0
    BEGIN
    SET @list1 = LTRIM(RTRIM(LEFT(@List, @pos - 1)))
    IF @list1 <> ''
        INSERT INTO @Ids(Id) VALUES (CAST(@list1 AS bigint))
    SET @List = SUBSTRING(@List, @pos+1, LEN(@List))
    SET @pos = CHARINDEX(@Delimiter, @list, 1)
END
RETURN 
END

一旦完成,表函数可用于查询:

 Collapse | Copy Code
CREATE PROCEDURE [dbo].[GetUsingDelimitedFunctionTable]
@Ids varchar(max)
AS
BEGIN
SET NOCOUNT ON
SELECT s.Id,s.SomeString 
FROM SomeString s (NOLOCK)
WHERE EXISTS ( SELECT *
               FROM ufnDelimitedBigIntToTable(@Ids,',') Ids
               WHERE s.Id = Ids.id )
END

Link还提供了更多实现此目的的方法。

答案 1 :(得分:0)

不是最好的,但一种方法是将双方转换为"varchar"并使用"Like"运算符进行比较。它不需要任何大的修改,只需将参数的数据类型更改为"varchar"即可。以下代码喜欢

'%[,]' + Convert(varchar(10), tbl_ProcessSubStep.ProcessID) + '[,]%' Like @ProcessIDs

希望它有所帮助。

答案 2 :(得分:0)

您没有在问题中指定数据库产品,但我将从您正在使用SQL Server的 @Pararemter 命名样式中猜测。

除了将空输入解释为“全部”的异常要求之外,这是对Arrays in SQL问题的重述,由Erland Sommarskog彻底探讨。阅读他关于这个主题的所有文章,以便对你可以使用的所有技术进行分析。

在这里,我将解释如何使用表值参数来解决您的问题。

一起执行以下脚本,以幂等方式设置测试环境。

创建示例解决方案

首先创建一个新的空测试数据库StackOverFlow13556628

USE master;
GO

IF DB_ID('StackOverFlow13556628') IS NOT NULL
BEGIN
  ALTER DATABASE StackOverFlow13556628 SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
  DROP DATABASE StackOverFlow13556628;
END;
GO

CREATE DATABASE StackOverFlow13556628;
GO

USE StackOverFlow13556628;
GO

接下来,创建一个用户定义的表格类型PrinciapalList,其中包含一列principal_id。此类型包含用于查询系统表sys.database_principals的输入值。

CREATE TYPE PrincipalList AS TABLE (
  principal_id INT NOT NULL PRIMARY KEY
);
GO

之后,创建存储过程GetPrincipals,它将PrincipalList表值参数作为输入,并从sys.database_principals返回结果集。

CREATE PROCEDURE GetPrincipals (
  @principal_ids PrincipalList READONLY
)
AS
BEGIN
  IF EXISTS(SELECT * FROM @principal_ids)
  BEGIN
    SELECT *
    FROM sys.database_principals
    WHERE principal_id IN (
      SELECT principal_id
      FROM @principal_ids
    );
  END
  ELSE
  BEGIN
    SELECT *
  FROM sys.database_principals;
  END;
END;
GO

如果表值参数包含行,则该过程返回sys.database_principals中具有匹配principal_id值的所有行。如果表值参数为空,则返回所有行。

测试解决方案

您可以像这样查询多个主体:

DECLARE @principals PrincipalList;

INSERT INTO @principals (principal_id) VALUES (1);
INSERT INTO @principals (principal_id) VALUES (2); 
INSERT INTO @principals (principal_id) VALUES (3);

EXECUTE GetPrincipals
  @principal_ids = @principals;
GO

结果:

principal_id    name
1   dbo
2   guest
3   INFORMATION_SCHEMA

您可以像这样查询单个主体:

DECLARE @principals PrincipalList;

INSERT INTO @principals (principal_id) VALUES (1);

EXECUTE GetPrincipals
  @principal_ids = @principals;
GO

结果:

principal_id    name
1   dbo

您可以查询所有这样的主体:

EXECUTE GetPrincipals;

结果:

principal_id    name
0   public
1   dbo
2   guest
3   INFORMATION_SCHEMA
4   sys
16384   db_owner
16385   db_accessadmin
16386   db_securityadmin
16387   db_ddladmin
16389   db_backupoperator
16390   db_datareader
16391   db_datawriter
16392   db_denydatareader
16393   db_denydatawriter

说明

此解决方案效率低下,因为您始终必须从表值参数中读取两次。实际上,除非你的表值参数有数百万行,否则它可能不是主要的瓶颈。

以这种方式使用空表值参数会感觉不直观。更明显的设计可能只是拥有两个存储过程 - 一个返回所有行,另一个只返回匹配id的行。调用应用程序可以选择调用哪一个。