SQL Server 2008将多值参数或参数数组传递给存储过程

时间:2014-10-14 08:49:58

标签: sql arrays sql-server-2008 stored-procedures parameters

我有两个表tbl_Properties和tbl_Locations

tbl_Properties有属性列表(包括FK Location_Id) tbl_Location有一个位置列表

我正在使用以下存储过程来搜索某个位置的属性:

spPropGetSearch

(
@Location_Id int
)

AS

SELECT
P.Prop_Id,
P.Prop_Title,
P.Prop_Bedrooms,
P.Prop_Price,
L.Location_Title

FROM 
tbl_Properties P
INNER JOIN tbl_Locations L ON L.Location_Id = P.Location_Id

WHERE
(P.Location_Id = @Location_Id OR @Location_Id = '0')
ORDER BY P.Prop_DateAdded DESC

我传递了location_id(例如' 1002')并且它工作正常并返回位于该位置/区域内的属性。 现在,我想传递多个location_Ids,例如' 1002',' 1005',' 1010'搜索位于所有这些区域的房产。

我该怎么做? 由于我不是数据库专家,我将非常感谢您的回复。

我找到了以下示例,它运行正常。请您查看并检查是否存在任何漏洞

USE AdventureWorks2012
GO
CREATE PROCEDURE usp_Employeelist
@Cities NVARCHAR(30)
AS
DECLARE @CitiesXML AS XML
SET @CitiesXML = cast(('<a>'+replace(@Cities,',' ,'</a><a>')+'</a>') AS XML)

SELECT
BusinessEntityID
, FirstName
, JobTitle
, City
FROM HumanResources.vEmployee
WHERE City IN
(
SELECT
A.value('.', 'varchar(max)')
FROM @CitiesXML.nodes('A') AS FN(A)
)
ORDER BY BusinessEntityID
GO

--Execute the stored procedure using multiple values
--through one parameter in this stored procedure
USE AdventureWorks2012
GO
EXEC usp_Employeelist
'Cambridge,Newport Hills,Berlin,Bordeaux'
GO 

3 个答案:

答案 0 :(得分:2)

使用table-valued parameters。第一步是创建您的类型:

CREATE TYPE dbo.ListOfInt AS TABLE (Value INT);

我倾向于使用通用名称来允许重用而不会对名称造成任何混淆(例如,如果将其命名为LocationIDs,那么在类型中存储属性列表会变得令人困惑)。

然后,您可以在存储过程中引用此类型:

CREATE PROCEDURE dbo.spPropGetSearch @LocationIDs dbo.ListOfInt READONLY
AS
BEGIN

    SELECT  P.Prop_Id,
            P.Prop_Title,
            P.Prop_Bedrooms,
            P.Prop_Price,
            L.Location_Title
    FROM    tbl_Properties P
            INNER JOIN tbl_Locations L 
                ON L.Location_Id = P.Location_Id

    WHERE   P.Location_Id IN (SELECT Value FROM @LocationIDs)
    OR      @Location_Id = 0
    ORDER BY P.Prop_DateAdded DESC;
END

然后您可以使用以下内容调用此方法:

DECLARE @LocationIDs dbo.ListOfInt;
INSERT @LocationIDs (Value)
VALUES (1002), (1005), (1010);
EXECUTE dbo.spPropGetSearch @LocationIDs;

修改

发现错误,就在这里:

OR      @Location_Id = 0

这引导我进入一个新的观点,看起来你希望有一个选项可以在0通过后返回所有内容。我会使用IF/ELSE

执行此操作
CREATE PROCEDURE dbo.spPropGetSearch @LocationIDs dbo.ListOfInt READONLY
AS
BEGIN
    IF EXISTS (SELECT 1 FROM @LocationIDs)
    BEGIN
        SELECT  P.Prop_Id,
                P.Prop_Title,
                P.Prop_Bedrooms,
                P.Prop_Price,
                L.Location_Title
        FROM    tbl_Properties P
                INNER JOIN tbl_Locations L 
                    ON L.Location_Id = P.Location_Id
        WHERE   P.Location_Id IN (SELECT Value FROM @LocationIDs)
        ORDER BY P.Prop_DateAdded DESC;
    END
    ELSE
    BEGIN
        SELECT  P.Prop_Id,
                P.Prop_Title,
                P.Prop_Bedrooms,
                P.Prop_Price,
                L.Location_Title
        FROM    tbl_Properties P
                INNER JOIN tbl_Locations L 
                    ON L.Location_Id = P.Location_Id
        ORDER BY P.Prop_DateAdded DESC;
    END
END
GO

因此,如果传递的表值参数为空,它将返回所有记录,如果它包含记录,它将只包含提供的location_ids。将OR放在这样的查询中使得SQL Server几乎不可能使用适当的索引。


<强>附录


回答评论

  

而不是使用:IF EXISTS(SELECT 1 FROM @LocationIDs)我们如何在WHERE子句中使用OR条件

我的回答是,我建议不要使用IF/ELSE作为理由,不要过于复杂化,而是要提高性能。当我说&#34;在这样的查询中放置OR使得SQL Server几乎不可能使用适当的索引时,我曾希望阻止你采用这种方法。&#34;

您可以按如下方式重写查询:

SELECT  P.Prop_Id,
        P.Prop_Title,
        P.Prop_Bedrooms,
        P.Prop_Price,
        L.Location_Title
FROM    tbl_Properties P
        INNER JOIN tbl_Locations L 
            ON L.Location_Id = P.Location_Id
WHERE   P.Location_Id IN (SELECT Value FROM @LocationIDs)
OR      NOT EXISTS (SELECT 1 FROM @LocationIDs)
ORDER BY P.Prop_DateAdded DESC;

这种方法的问题是,你在同一个查询中确实有两个选项,而这些选项可能需要两个不同的执行计划。如果您在p.Location_ID上有索引,并且您在@LocationIDs中有记录,则最佳查询计划是在tbl_Properties.Location_ID上使用索引搜索。如果@LocationIDs为空,则索引查找无意义,最佳计划是tbl_Properties上的聚簇索引扫描(表扫描)。由于SQL Server使用缓存计划,它只能缓存在另一个上,这意味着如果它存储索引查找选项,则每次传递空表时都会有一个次优计划,或者,如果它缓存表扫描计划,每次传递位置ID的值时,您都没有利用那里的索引。

一种解决方法是OPTION (RECOMPILE)

SELECT  P.Prop_Id,
        P.Prop_Title,
        P.Prop_Bedrooms,
        P.Prop_Price,
        L.Location_Title
FROM    tbl_Properties P
        INNER JOIN tbl_Locations L 
            ON L.Location_Id = P.Location_Id
WHERE   P.Location_Id IN (SELECT Value FROM @LocationIDs)
OR      NOT EXISTS (SELECT 1 FROM @LocationIDs)
ORDER BY P.Prop_DateAdded DESC
OPTION (RECOMPILE);

这会强制重新编译查询以便执行,以确保您拥有当前执行的最佳计划。但是因为你只有两个选项,所以这是很多不受欢迎的重新编译。所以最好的选择是有两个查询,每个查询都有自己的缓存执行计划,并使用IF/ELSE流操作符根据传递的内容@LocationIDs流向相应的查询

答案 1 :(得分:0)

     spPropGetSearch

        (
        @Location_Id varchar(500)
        )

        AS



DECLARE @strQuery varchar(MAX)
SET @strQuery='

        SELECT
        P.Prop_Id,
        P.Prop_Title,
        P.Prop_Bedrooms,
        P.Prop_Price,
        L.Location_Title

        FROM 
        tbl_Properties P
        INNER JOIN tbl_Locations L ON L.Location_Id = P.Location_Id

        WHERE
     P.Location_Id in ('+ @Location_Id+') OR @Location_Id = ''0''
        ORDER BY P.Prop_DateAdded DESC'

EXEC (@strQuery)

在存储过程中传递参数,如

EXEC spPropGetSearch '1002,1005,1010'

答案 2 :(得分:0)

使用动态qry。

create procedure  dropsp
@no varchar(100)
as
    begin 
        declare @nsql  nvarchar(1000)

        Set @nsql   = ' SELECT  *
                        FROM    db.dbo.tmptable
                        WHERE no in  (' + @no +')'

        exec  sp_executesql @nsql 
    end 
go
exec dropsp  'aaaaaaaa,bbbb'