0

我有一个存储过程,它根据 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 Clause)。下面提供了我从中获得的网页的解决方案和相关链接。这是一篇非常好的文章,将帮助您根据您的要求选择正确的解决方案。

4

3 回答 3

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 还提供了更多实现此目的的方法。

于 2012-11-26T23:26:07.993 回答
0

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

除了将空输入解释为“全部”的特殊要求外,这是对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

之后,创建GetPrincipalsPrincipalList表值参数为输入的存储过程,并从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 的行。由调用应用程序选择调用哪个应用程序。

于 2012-11-26T23:51:48.073 回答
0

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

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

希望能帮助到你。

于 2012-11-25T23:23:26.113 回答