3
votes

The following stored procedure works correctly execpt when I pass in the @NameSubstring parameter. I know I am not dynamically building the like clause properly. How can I build the like clause when this parameter also needs to be passed as a parameter in the EXEC sp_executesql call near the bottom of the procedure?

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
5

5 Answers

4
votes

@NameString needs to be outside of the quotes. To get @NameString% enclosed in quotes, you use two single quotes to escape the quote character as a literal.

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

To avoid SQL injection, do not use concatenation when adding the parameter to your SQL statement. I strongly recommend that you use this format:

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

By using char(37) instead of '%' you avoid having to escape the apostrophes around the string literal

If you wanted to put a wildcard at either side, then you would use

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

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

In case someone believes I am wrong, here's proof that concatenation is a risk.

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

Run it the first time and you will get a resultset with 3 records, and another with all 6 records.

Now swap the declaration of @NameSubstring to the alternative, and re-run. All data in the table has been deleted.

If on the other hand you write your code like:

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

Then you still get the 3 records returned the first time, but you don't lose your data when you change the declaration.

0
votes

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

0
votes

So, you are asking how to specify parameters when you use dynamic queries and sp_executesql ?

It can be done, like this:

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

You can read more about it here: http://support.microsoft.com/kb/262499

0
votes

Perhaps this wouldn't be an issue if you weren't using dynamic SQL. It looks to me like a vanilla query would work just as well and be much more straightforward to read and debug. Consider the following:

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))

This is much simpler, clearer etc. Good luck!