简体   繁体   English

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

[英]SQL Server 2008 Passing Multi-value Parameters or Parameter Array to a Stored Procedure

I have two tables tbl_Properties and tbl_Locations 我有两个表tbl_Properties和tbl_Locations

tbl_Properties has list of properties (including FK Location_Id) tbl_Location has a list of locations tbl_Properties具有属性列表(包括FK Location_Id)tbl_Location具有位置列表

I am using following stored procedure to search properties in a 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

I pass location_id (such as '1002') and it works just fine and returns properties located within that location / area. 我通过location_id(例如'1002'),它可以正常工作并返回位于该位置/区域内的属性。 Now, I want to pass multiple location_Ids such as '1002', '1005', '1010' to search properties located in all of those areas. 现在,我想传递多个location_Id,例如“ 1002”,“ 1005”,“ 1010”,以搜索位于所有这些区域中的属性。

How would I do that? 我该怎么做? I will appreciate a detailed reply as I am not a database expert. 由于我不是数据库专家,所以请您提供详细的答复。

I found the following example and it's working fine. 我发现以下示例,并且运行正常。 Please can you look into and check if there's any vulnerability 请您调查一下是否存在任何漏洞

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 

Use table-valued parameters . 使用表值参数 The first step is to create your type: 第一步是创建您的类型:

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

I tend to use generic names for these to allow for reuse without any confustion as to names (eg if you named it LocationIDs it would then become confusing to store a list of properties in the type). 我倾向于使用通用名称来允许重用而不会混淆名称(例如,如果您将其命名为LocationIDs ,那么在类型中存储属性列表将变得混乱)。

Then you can reference this type in your stored procedure: 然后,您可以在存储过程中引用此类型:

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

You can then call this using something like: 然后,您可以使用类似以下的命令来调用它:

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

EDIT 编辑

Found the error, it was here: 发现错误,它在这里:

OR      @Location_Id = 0

Which leads me on to a new point, it looks like you want to have an option to return everything if 0 is passed. 这引出了我一个新的观点,似乎您希望有一个如果传递0则返回所有内容的选项。 I would do this using IF/ELSE : 我会使用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

So if the table valued parameter passed is empty, it will return all records, if it contains records it will only contain the location_ids supplied. 因此,如果传递的表值参数为空,则它将返回所有记录,如果包含记录,则将仅包含提供的location_id。 Putting OR in queries like this makes it almost impossible for SQL Server to use an appropriate index. 在这样的查询中放置OR使得SQL Server几乎不可能使用适当的索引。


ADDENDUM 附录


To answer the comment 回答评论

instead of using: IF EXISTS (SELECT 1 FROM @LocationIDs) how can we use OR condition in WHERE clause 而不是使用:IF EXISTS(SELECT 1 FROM @LocationIDs)我们如何在WHERE子句中使用OR条件

My answer would be don't I suggested using IF/ELSE for a reason, not to over complicate things, but to improve performance. 我的回答是,我不是出于某种原因而不建议使用IF/ELSE ,不是为了使事情复杂化,而是为了提高性能。 I had hoped to deter you from this approach when I said "Putting OR in queries like this makes it almost impossible for SQL Server to use an appropriate index." 当我说“在这样的查询中放置OR使得SQL Server几乎不可能使用适当的索引”时,我曾希望阻止您使用这种方法 .

You could rewrite the query as follows: 您可以按以下方式重写查询:

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;

The problem with this approach is, you really have two options in the same query, and these options are likely to need two different exection plans. 这种方法的问题是,同一查询中确实有两个选项,而这些选项可能需要两个不同的执行计划。 If you have an index on p.Location_ID , and you have records in @LocationIDs , then the best query plan is to use an index seek on tbl_Properties.Location_ID . 如果在p.Location_ID上有索引,并且在@LocationIDs有记录,那么最佳的查询计划是在tbl_Properties.Location_ID上使用索引查找。 If @LocationIDs is empty, then the index seek is pointless and the best plan is a clustered index scan (table scan) on tbl_Properties . 如果@LocationIDs为空,则索引查找是毫无意义的,最好的计划是对tbl_Properties进行聚簇索引扫描(表扫描)。 Since SQL Server uses cached plans, it can only cache on or the other, which means that if it stores the index seek option, every time you pass an empty table you have a sub-optimal plan, or alternatively, if it caches the table scan plan, every time you pass values for location ID you are not taking advantage of the index that is there. 由于SQL Server使用缓存的计划,因此它只能缓存另一个,这意味着,如果它存储索引查找选项,则每次传递一个空表时,您的计划都是次优的;或者,如果它缓存了该表,扫描计划,每次您传递位置ID的值时,您都不会利用那里的索引。

One workaround is OPTION (RECOMPILE) : 一种解决方法是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);

This forces the query to be recompiled for ever execution to ensure you have the optimal plan for the current execution. 这将强制查询重新编译以执行,以确保您具有当前执行的最佳计划。 But since you only really have two options, this is a lot of unnessecary recompilation. 但是由于您实际上只有两种选择,因此这是许多不必要的重新编译。 So the best option is to have two queries, each with it's own cached execution plan, and using the IF/ELSE flow operator to flow to the appropriate query based on what has been passed as @LocationIDs . 因此,最好的选择是使用两个查询,每个查询都有自己的缓存执行计划,并使用IF/ELSE流运算符根据作为@LocationIDs传递的内容流到适当的查询。

     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)

pass parameter in stored Procedure like as 在存储过程中传递参数,例如

EXEC spPropGetSearch '1002,1005,1010'

Use dynamic qry. 使用动态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'

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM