【问题标题】:How do I dynamically build a like clause in an executable sql stored procedure that uses EXEC sp_executesql?如何在使用 EXEC sp_executesql 的可执行 sql 存储过程中动态构建 like 子句?
【发布时间】:2010-06-23 21:13:28
【问题描述】:

当我传入@NameSubstring 参数时,以下存储过程可以正常运行。我知道我没有正确动态地构建 like 子句。当这个参数也需要在靠近过程底部的 EXEC sp_executesql 调用中作为参数传递时,如何构建 like 子句?

ALTER PROCEDURE [dbo].[spGetAutoCompleteList]
( 
    @AutoCompleteID int,
    @StatusFlag int, 
    @NameSubstring varchar(100),
    @CompanyID int,
    @ReturnMappings bit,
    @ReturnData bit
)

AS


DECLARE @ErrorCode int,
        @GetMappings nvarchar(500),
        @Debug bit,
        @Select AS NVARCHAR(4000),
        @From AS NVARCHAR(4000),
        @Where AS NVARCHAR(4000),
        @Sql AS NVARCHAR(4000),
        @Parms AS NVARCHAR(4000)

SET @ErrorCode = 0
SET @Debug = 1

BEGIN TRAN
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

    IF @AutoCompleteID IS NOT NULL OR @StatusFlag IS NOT NULL OR @NameSubstring IS NOT NULL
        BEGIN
            SET @Select = '
            SELECT ac.AutoCompleteID, 
                   ac.AutoCompleteName, 
                   ac.CompanyID, 
                   ac.StatusFlag, 
                   ac.OwnerOperID, 
                   ac.CreateDT, 
                   ac.CreateOperID, 
                   ac.UpdateDT, 
                   ac.UpdateOperID, 
                   ac.SubmitOperID, 
                   ac.SubmitDT, 
                   ac.ReviewComments'

            SET @GetMappings = '
            Select ac.AutoCompleteID'

            IF @ReturnData = 1
                BEGIN
                    SET @Select =  @Select + '
                        , ac.AutoCompleteData'
                END

            SET @From = '      
            FROM tbAutoComplete ac'

            SET @Where = '
            WHERE 1=1'

            IF @AutoCompleteID IS NOT NULL
                BEGIN
                    SET @Where = @Where + '
                        AND ac.AutoCompleteID = CAST(@AutoCompleteID AS nvarchar)'
                END

            IF @StatusFlag IS NOT NULL
                BEGIN
                    SET @Where = @Where + '
                        AND ac.StatusFlag = CAST(@StatusFlag AS nvarchar)'
                END

            IF @NameSubstring IS NOT NULL
                BEGIN
                    SET @Where = @Where + '
                        AND ac.AutoCompleteName like @NameSubstring' + '%'
                END

            SET @Where = @Where + '
                    AND ac.CompanyID =  + CAST(@CompanyID AS nvarchar)'

            SET @Sql = @Select + @From + @Where

            SET @Parms = '
                @AutoCompleteID int,
                @StatusFlag int,
                @NameSubstring varchar(100),
                @CompanyID int'

            EXEC sp_executesql @Sql,
                               @Parms, 
                               @AutoCompleteID,
                               @StatusFlag,
                               @NameSubstring,
                               @CompanyID

            IF @ReturnMappings = 1
                BEGIN
                    SET @GetMappings = 'Select * FROM tbAutoCompleteMap acm WHERE acm.AutoCompleteID IN(' + @GetMappings + @From + @Where + ')'
                    --EXEC sp_executesql @GetMappings
                END 

            IF @Debug = 1
                BEGIN
                    PRINT @GetMappings
                    PRINT @Sql
                END 
        END

SELECT @ErrorCode = @ErrorCode + @@ERROR

IF @ErrorCode <> 0 
    BEGIN
        SELECT '<FaultClass>1</FaultClass><FaultCode>1</FaultCode>' 
                + '<FaultDesc>Internal Database Error.</FaultDesc>'
                + '<FaultDebugInfo>(spGetAutoCompleteList):  There was an error while trying to SELECT from tbAutoComplete.</FaultDebugInfo>'
        ROLLBACK TRAN
        RETURN
    END

COMMIT TRAN

【问题讨论】:

    标签: sql-server


    【解决方案1】:

    @NameString 需要在引号之外。要将 @NameString% 括在引号中,请使用两个单引号将引号字符转义为文字。

            SET @Where = @Where + '
                AND ac.AutoCompleteName like ''' + @NameSubstring + '%'''
    

    【讨论】:

    • @Joe Stefaneli,这对 SQL 注入开放吗?我想试试这个,但想确保这一点。谢谢
    【解决方案2】:

    为避免 SQL 注入,在将参数添加到 SQL 语句时不要使用连接。我强烈建议您使用这种格式:

    IF @NameSubstring IS NOT NULL BEGIN
        SET @Where += 'AND ac.AutoCompleteName LIKE @NameSubstring + char(37)'
    END
    

    通过使用char(37) 而不是'%',您可以避免转义字符串文字周围的撇号

    如果你想在任一侧放置一个通配符,那么你会使用

    IF @NameSubstring IS NOT NULL BEGIN
        SET @Where += 'AND ac.AutoCompleteName LIKE char(37) + @NameSubstring + char(37)'
    END 
    

    ----------------------------------------------- ------------------------------

    如果有人认为我错了,这里有证据证明串联是有风险的。

    IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[TestInjection]') AND type in (N'U')) BEGIN
        create table TestInjection(ID int, Value nvarchar(10))
    
        insert into TestInjection (ID,Value) 
        Values
        (1,'Tom'),
        (2,'Fred'),
        (3,'Betty'),
        (4,'Betty2'),
        (5,'Betty3'),
        (6,'George')
    END 
    
    declare @NameSubstring nvarchar(1000) = 'Bet'
    --declare @NameSubstring nvarchar(1000) = 'Bet%'';delete from TestInjection;select * from TestInjection where value = ''x'
    
    declare @ID int = 2
    
    Declare @sql nvarchar(1000) = 'select * from TestInjection where ID > @ID '
    SET @sql +=' AND [Value] like ''' + @NameSubstring + '%'''
    
    Declare @params nvarchar(100) = '@ID int'
    
    exec sp_executesql @sql, @params, @ID            
    
    select * from TestInjection
    

    第一次运行,你会得到一个包含 3 条记录的结果集,另一个包含全部 6 条记录的结果集。

    现在将@NameSubstring 的声明交换为替代项,然后重新运行。表中的所有数据均已删除。

    另一方面,如果您编写如下代码:

    declare @NameSubstring nvarchar(1000) = 'Bet'
    --declare @NameSubstring nvarchar(1000) = 'Bet%'';delete from TestInjection;select * from TestInjection where value = ''x'
    
    declare @ID int = 2
    
    Declare @sql nvarchar(1000) = 'select * from TestInjection where ID > @ID '
    SET @sql +=' AND [Value] LIKE @NameSubstring + char(37)'
    
    Declare @params nvarchar(100) = '@ID int, @NameSubstring nvarchar(1000)'
    
    exec sp_executesql @sql, @params, @ID, @NameSubstring           
    
    select * from TestInjection
    

    那么你仍然会得到第一次返回的 3 条记录,但是你在更改声明时不会丢失数据。

    【讨论】:

    • 不知道为什么投反对票。万一有人真的认为答案不好,我添加了可以运行的 SQL 作为证明。
    【解决方案3】:

    SET @Where = @Where + 'AND ac.AutoCompleteName like ''%' + @NameSubstring + '%'''

    【讨论】:

    • 你的意思是在 @NameString 前面加上 % 而不是在原始代码中指定的吗?
    【解决方案4】:

    那么,您是在问如何在使用动态查询和sp_executesql 时指定参数?

    可以这样做:

    DECLARE /* ... */
    SET @SQLString = N'SELECT @LastlnameOUT = max(lname) FROM pubs.dbo.employee WHERE job_lvl = @level'
    SET @ParmDefinition = N'@level tinyint, @LastlnameOUT varchar(30) OUTPUT'
    SET @IntVariable = 35
    EXECUTE sp_executesql @SQLString, @ParmDefinition,  @level = @IntVariable,  @LastlnameOUT=@Lastlname OUTPUT
    

    您可以在此处阅读更多信息:http://support.microsoft.com/kb/262499

    【讨论】:

      【解决方案5】:

      如果您不使用动态 SQL,也许这不会成为问题。在我看来,普通查询也可以正常工作,并且更易于阅读和调试。考虑以下几点:

      SELECT      ac.AutoCompleteID, 
                  ac.AutoCompleteName, 
                  ac.CompanyID, 
                  ac.StatusFlag, 
                  ac.OwnerOperID, 
                  ac.CreateDT, 
                  ac.CreateOperID, 
                  ac.UpdateDT, 
                  ac.UpdateOperID, 
                  ac.SubmitOperID, 
                  ac.SubmitDT, 
                  ac.ReviewComments
      FROM    tbAutoComplete ac
      WHERE   ((ac.AutoCompleteID = CAST(@AutoCompleteID AS nvarchar) OR (@AutoCompleteID IS NULL))
        AND   ((ac.StatusFlag = CAST(@StatusFlag AS nvarchar)) OR (@StatusFlag IS NULL))
        AND   ((ac.AutoCompleteName like @NameSubstring + '%') OR (@NameSubstring IS NULL))
        AND   ((ac.CompanyID =  CAST(@CompanyID AS nvarchar)) OR (@CompanyID IS NULL))
      

      这更简单,更清晰等。祝你好运!

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-07-08
        • 2010-10-07
        • 1970-01-01
        • 1970-01-01
        • 2015-04-30
        • 1970-01-01
        相关资源
        最近更新 更多