【问题标题】:SQL Server 2008 Passing Multi-value Parameters or Parameter Array to a Stored ProcedureSQL Server 2008 将多值参数或参数数组传递给存储过程
【发布时间】:2014-10-14 08:49:57
【问题描述】:

我有两个表 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_Id,例如“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 

【问题讨论】:

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


    【解决方案1】:

    使用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 是有原因的,不是为了使事情过于复杂,而是为了提高性能。当我说“在这样的查询中放置 OR 使得 SQL Server 几乎不可能使用适当的索引”时,我曾希望阻止您使用这种方法。

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

    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 传递的内容流向适当的查询。

    【讨论】:

    • 嗨,当我创建 SP 时出现以下错误:必须声明标量变量“@LocationIDs”。
    • 嗨,当我执行 SP 时出现错误:操作数类型冲突:nvarchar 与 ListOfInt 不兼容
    • Location_Id的数据类型是int
    • 不知道为什么你会收到这个错误。 It works on SQL Fiddle。您是否准确地复制了查询?
    • 如何从 ASP.NET 调用 SP?
    【解决方案2】:

    使用动态查询。

    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'
    

    【讨论】:

    • 这不是一个好方法,即使您发布的示例也会导致错误(类似于invalid column name 'aaaaaaaa')。它也不太可能使用缓存的执行计划,因为查询需要针对传递的每组不同的值进行编译,它也容易受到恶意 SQL(SQL 注入)的攻击。
    【解决方案3】:
         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)
    

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

    EXEC spPropGetSearch '1002,1005,1010'
    

    【讨论】:

    • 将 varchar 值 '1002,1005,1010' 转换为数据类型 int 时转换失败。
    • 嗨 Sunil Naudiyal,同样的问题:必须声明标量变量“@Location_Id”。
    • 这不是一个好方法。这样做的缺点是它不能使用缓存计划,因此在大多数情况下,每次执行都必须编译查询。还有格式错误的 SQL 的范围,例如EXECUTE dbo.spPropGetSearch 'Error';,它将编译,但会导致无效的列名错误。或者更糟糕的是,可能会传递恶意 SQL,例如EXECUTE dbo.spPropGetSearch '1); DROP TABLE tbl_Properties; --' 将返回数据,但也会删除一个表(如果用户有足够的权限)。
    • 实际上我被伪造为使用单个 '' 和 OR @Location_Id = ''0'' 现在使用。
    • 我同意 GarethD,不能用这个,可能注入攻击,
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-07-12
    • 2015-12-20
    • 2013-07-06
    • 1970-01-01
    • 2010-11-07
    • 2017-02-19
    相关资源
    最近更新 更多