Dynamic Insert Loop - sql-server

As I understand it, the following code:
declare #csvnmbr nvarchar(3)
declare #sql nvarchar(400)
set #csvnmbr = 1
set #sql = 'BULK INSERT #tablename FROM ''C:\TEMP\'+#csvnmbr+'.csv''
WITH (FIELDTERMINATOR = '','')'
while #csvnmbr < 4
begin
print #sql
set #csvnmbr = #csvnmbr + 1
end
GO
Should print:
BULK INSERT #tablename FROM 'C:\TEMP\1.csv' WITH (FIELDTERMINATOR = ',')
BULK INSERT #tablename FROM 'C:\TEMP\2.csv' WITH (FIELDTERMINATOR = ',')
BULK INSERT #tablename FROM 'C:\TEMP\3.csv' WITH (FIELDTERMINATOR = ',')
but it doesn't, it prints:
BULK INSERT #tablename FROM 'C:\TEMP\1.csv' WITH (FIELDTERMINATOR = ',')
BULK INSERT #tablename FROM 'C:\TEMP\1.csv' WITH (FIELDTERMINATOR = ',')
BULK INSERT #tablename FROM 'C:\TEMP\1.csv' WITH (FIELDTERMINATOR = ',')
Does anyone know why the loop won't print the number sequence but just sticks to the first csv number instead?
If I do this though:
declare #csvnmbr nvarchar(3)
declare #sql nvarchar(400)
set #csvnmbr = 1
set #sql = 'HI'
while #csvnmbr < 4
begin
print #sql
print #csvnmbr
set #csvnmbr = #csvnmbr + 1
end
GO
There is no apparent need to write the #sql inside the loop. Any idea why the fact that the #sql statement is dynamic means it needs to be reset each time?

Because it is set once before loop starts. You need to set #sql inside loop:
DECLARE #csvnmbr INT = 1
,#sql NVARCHAR(MAX);
WHILE #csvnmbr < 4
BEGIN
SET #sql = N'BULK INSERT #tablename FROM ''C:\TEMP\'' +
CAST(#csvnmbr AS NVARCHAR(10)) +
'.csv'' WITH (FIELDTERMINATOR = '','')';
PRINT #sql;
SET #csvnmbr += 1;
END
About your question why it needs to be inside loop:
declare #csvnmbr nvarchar(3)
declare #sql nvarchar(400)
set #csvnmbr = 1
/* At this point #sql if fixed string, during concatenation #csvnmbr was
replaced by its actual value */
set #sql = 'BULK INSERT #tablename FROM ''C:\TEMP\'+#csvnmbr+'.csv''
WITH (FIELDTERMINATOR = '','')'
while #csvnmbr < 4
begin
print #sql /* Here sql will be always the same you need to set it again */
set #csvnmbr = #csvnmbr + 1
end
What you trying to achieve is passing string by reference. You can mimic it like this:
DECLARE #csvnmbr NVARCHAR(3) = '1'
,#sql NVARCHAR(MAX);
SET #sql =
N'BULK INSERT #tablename FROM ''C:\TEMP\#csvnmbr.csv'' WITH (FIELDTERMINATOR = '','')';
WHILE #csvnmbr < 4
BEGIN
/* Warning!!! This will execute code in #sql*/
EXEC [dbo].[sp_executesql]
#sql,
N'#csvnmbr nvarchar(3)',
#cvnmbr; /* Will substitute #csvnmbr with passed value */
SET #csvnmbr += 1;
END

Keep your statement inside the while loop
declare #csvnmbr nvarchar(3)
declare #sql nvarchar(400)
set #csvnmbr = 1
while #csvnmbr < 4
begin
set #sql = 'BULK INSERT #tablename FROM ''C:\TEMP\'+#csvnmbr+'.csv''
WITH (FIELDTERMINATOR = '','')'
print #sql
set #csvnmbr = #csvnmbr + 1
end
GO
the while loop iteration happens between the while loop condition begin and end blocks.
WHILE (Some Condition) --<-- first the condition is checked
BEGIN
--<-- if the while condition is evaluated to true
-- code between the BEGIN and END is executed
-- it keeps getting executing until while condition returns false
END

You need to fill #sql in each iteration:
DECLARE #csvnmbr NVARCHAR(3);
DECLARE #sql NVARCHAR(400);
SET #csvnmbr = 1;
SET #sql = 'BULK INSERT #tablename FROM ''C:\TEMP\' + #csvnmbr + '.csv''
WITH (FIELDTERMINATOR = '','')';
WHILE #csvnmbr < 4
BEGIN
PRINT #sql;
SET #csvnmbr = #csvnmbr + 1;
SET #sql = 'BULK INSERT #tablename FROM ''C:\TEMP\' + #csvnmbr + '.csv''
WITH (FIELDTERMINATOR = '','')';
END;
GO

Related

Set #Variable1 within a stored proc by executing #Variable2

I want to set the value of #Count by executing #Counter within a Begin Try of a stored procedure.
SET #Counter ='SET #Count = (SELECT COUNT(' + #COLUMN + ') FROM ' + #TABLE + ' WHERE CONVERT(VARCHAR(MAX),' + #COLUMN + ') = ''' + #DATATOFIND + ''')'
I have tested the above code and it does give me the expected result for populating the #Count variable inside of a normal sql statement outside of a stored procedure.
Once the #Count variable is populated I want to use it in a print statement.
PRINT '-- No. of Entries in the ' + #TABLE + ' Table = ' + #Count
I have tried to the following two options to get the #Count populated but neither has worked
EXEC #Counter
and
EXECUTE sp_executesql (#Counter)
UPDATE:
After some more research I tried this:
DECLARE #Counter NVARCHAR(1000)
SET #Counter = N'DECLARE #Count NVARCHAR(100); SET #COUNT = (SELECT COUNT(UserId) FROM UserGrp WHERE CONVERT(VARCHAR(MAX),UserId) = ''za02'')'
EXECUTE sp_executesql #Counter
Print #Count
But I receive this error:
Must declare the scalar variable "#Count"
UPDATE: Workaround / Solution to my situation
DECLARE #Counter NVARCHAR(2000)
SET #Counter = 'DECLARE #Count NVARCHAR(100); SET #COUNT = (SELECT COUNT(UserId) FROM UserGrp WHERE CONVERT(VARCHAR(MAX),UserId) = 'to01'); Print '/* No. of Entries in the UserGrp Table - ' + #Count + ' */''
EXEC (#Counter)
This gives me clear information in my result to decide what to do with the created code from the rest of the stored proc
Dynamic SQL requires careful handling:
DECLARE #Counter NVARCHAR(1000);
DECLARE #COUNT BIGINT;
DECLARE #DATATOFIND VARCHAR(100) = 'za02';
DECLARE #TABLE SYSNAME = N'UserGrp';
DECLARE #COLUMN SYSNAME = N'UserId';
SET #Counter = N'SELECT #COUNT = COUNT(<column_name>)
FROM <table_name>
WHERE CONVERT(VARCHAR(MAX),<column_name>) = #DATATOFIND;';
SET #Counter = REPLACE(#Counter, '<column_name>', QUOTENAME(#COLUMN));
SET #Counter = REPLACE(#Counter, '<table_name>', QUOTENAME(#TABLE));
PRINT #Counter; -- debug
EXECUTE sp_executesql #Counter,
N'#DATATOFIND VARCHAR(100), #COUNT BIGINT OUTPUT',
#DATATOFIND,
#COUNT OUTPUT;
SELECT #COUNT;
db<>fiddle demo
Minimum:
params are parameters, not concatenated string
identifiers(here column/table name) - should be quoted for instance using QUOTENAME function
it is good to print query to see if it doing what is expected
parameters set inside dynamic query could be passed to outer block by defining them as OUTPUT

Challenge with dynamic SQL

I have a procedure that generates dynamic SQL that creates an insert into statement while querying an excel spreadsheet.
The resulting print from the messages screen can be pasted into an ssms window and executes. When I try to execute the SQL from within the stored procedure I get a syntax error as follows:
'SELECT * into TestClient FROM OPENROWSET('Microsoft.ACE.OLEDB.12.0', 'Excel 12.0;HDR=YES;Database=G:\CustomerETL\Employee\PendingETL\ETLEmployeexls.xls;', [Sheet1$])'
Msg 102, Level 15, State 1, Line 15
Incorrect syntax near 'SELECT * into TestClient FROM OPENROWSET('.
Below is the entire stored procedure. I know the problem is in the ticks (within the SET blocks that create the dynamic SQL I just can't figure out where the missing ticks are.
Here is the proc:
USE [ETL]
GO
/****** Object: StoredProcedure [dbo].[ImportExcelSheetForCustomerEmployeeUpdate2] Script Date: 12/19/2017 4:03:05 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[ImportExcelSheetForCustomerEmployeeUpdate2](#BatchID int)
as
--EXEC ImportExcelSheetForCustomerEmployeeUpdate 2
/* -- TRUNCATE TABLE FilesToImport
UPDATE FilesToImport
SET StatusID = 1
*/
-- Jeffery Williams
-- 12/18/2017
DECLARE #FileID int
,#ETLFilename varchar(250)
,#ClientName varchar(100)
,#FileType varchar(5)
,#ColumnCount int
,#RowsToETL int
,#StatusID int
,#Processed bit = 0
,#Count int
,#SQL nvarchar(4000)
,#Sheetname varchar(50) = '[Sheet1$]'
,#CMDSQL as varchar(4000)
,#SQLCmd NVARCHAR(MAX)
SELECT *
FROM FilesToImport
BEGIN
SELECT #Count = count(*)
FROM FilesToImport
WHERE BatchID = #BatchID
AND StatusID = 1
END
PRINT 'Count of records to process: ' + cast(#Count as varchar)
WHILE #Count > 0
BEGIN
BEGIN
SELECT TOP 1 #FileID = FileID, #ETLFilename = ETLFilename, #ClientName = ClientName
,#FileType = FileType, #ColumnCount = ColumnCount, #RowsToETL = RowsToETL
FROM FilesToImport
WHERE StatusID = 1
AND BatchID = #BatchID
END
-- Rename the file
set #CMDSQL = 'rename G:\CustomerETL\Employee\PendingETL\' + #ETLFilename + ' ETLEmployeexls.xls'
exec master..xp_cmdshell #CMDSQL
--PRINT cast(#cmdsql as varchar(4000))
-- Ciode below generates our select. Need to add an INTO clause and create a staging table for each import. Prior to this step we need to rename the file.
SET #SQL = ''''
SET #SQL = #SQL + 'SELECT * into ' + coalesce(#ClientName, 'TestClient') + ' FROM OPENROWSET('
SET #SQL = #SQL + ''''
SET #SQL = #SQL + '''' + 'Microsoft.ACE.OLEDB.12.0' + '''' --+ ', '
-- Excel 12.0;HDR=NO;Database=g:\UnZip\ImportSampleXLSX.xlsx;' + ''
SET #SQL = #SQL + '''' + ', '
SET #SQL = #SQL + '''' + '''Excel 12.0;HDR=YES;Database=G:\CustomerETL\Employee\PendingETL\ETLEmployeexls.xls;''' + '''' + ', ' + #Sheetname + ')'
SET #SQL = #SQL + ''''
PRINT cast(#SQL as varchar(8000))
EXEC sp_executesql #SQL
set #CMDSQL = 'rename G:\CustomerETL\Employee\PendingETL\ETLEmployeexls.xls ' + #ETLFilename
exec master..xp_cmdshell #CMDSQL
UPDATE FilesToImport
SET StatusID = 2
WHERE FileID = #FileID
/* -- TRUNCATE TABLE FilesToImport
UPDATE FilesToImport
SET StatusID = 1
*/
SET #Count = (#Count - 1)
CONTINUE
END
I am posting this as an answer but it should be comment. When I tried adding this as a comment StackOveflow kept thinking that I was trying to add #count as an email target.
In your code:
WHILE #Count > 0
BEGIN
BEGIN
SELECT TOP 1 #FileID = FileID, #ETLFilename = ETLFilename, #ClientName = ClientName
,#FileType = FileType, #ColumnCount = ColumnCount, #RowsToETL = RowsToETL
FROM FilesToImport
WHERE StatusID = 1
AND BatchID = #BatchID
END
you are not updating the value of #count. This will either never loop or loop forever. You probably want to add a statement (right before the end) such as this:
Set #count= ##rowcount;
Ben

Msg 102, Level 15, State 1, Line 34 Incorrect syntax near '.'

I am getting above error while executing query as following, which is a stored procedure command line execution
exec sp_Bind_Division_Store_Camera_On_Filter_In_Liveview
null, null, null, null
Stored procedure is as following
ALTER PROCEDURE [dbo].[sp_Bind_Division_Store_Camera_On_Filter_In_Liveview]
#division varchar(45),
#store varchar(45),
#camera varchar(68),
#group varchar(100)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE
#BaseQuery nvarchar(max) = 'select
distinct tblcameradetails.name as CameraName,
tblcameradetails.IsDeviceEnabled,
tblcameradetails.cameraID,
tblcameradetails.deviceIP,
tblmediasourcedetails.StreamName,
tblmediasourcedetails.streamID,
tblcameradetails.Model_ID,
tblcameradetails.IsHidden,
tblcameradetails.hasPTZcapability,
tblcameradetails.CameraModelNo,
tblcameradetails.username as CameraUserName,
tblcameradetails.hasPTZCycle,
tblcameradetails.hasPreset,
tblcameradetails.password as CameraPassword,
tblmediasourcedetails.isRecordingStarted as IsRecordingOn,
tblcameradetails.IsCovert,
tblcameradetails.constCameraName,
tblmediasourcedetails.constStreamName,
tblstoredetails.Store_ID,
tblsystemlocationdetails.division,
tblstoredetails.Store_Name,
tblstoredetails.Store_IP,
tblstoreconfiguration.Liveview_Session_Timeout
from
tblmediasourcedetails,
tblcameradetails,
tblcameragroupdetails,
tblgroupdetails,
tblvssaddsourcedetails,
tblsystemlocationdetails,
tblstoredetails,
tblstoreconfiguration'
, #ParamList nvarchar(max) = N'#p1 varchar(45), #p2 varchar(45), #p3 varchar(68), #p4 varchar(100)'
, #WhereClause nvarchar(max) = N'where
tblmediasourcedetails.cameraID=tblcameradetails.cameraID
and tblmediasourcedetails.streamID=tblvssaddsourcedetails.streamID
and tblcameradetails.cameraID = tblcameragroupdetails.cameraID
and tblcameragroupdetails.groupID=tblgroupdetails.groupID
and tblstoredetails.Store_ID= tblcameradetails.Store_ID
and tblsystemlocationdetails.Store_ID= tblstoredetails.Store_ID
and tblstoredetails.Store_ID=tblstoreconfiguration.Store_ID and 1=1'
, #OrderByClause nvarchar(100) = 'order by CameraName';
IF #division IS NOT NULL
BEGIN
SET #WhereClause = #WhereClause + ' and division like (#p1)';
END
IF #store IS NOT NULL
BEGIN
SET #WhereClause = #WhereClause + ' and (store like (#p2))';
END
IF #camera IS NOT NULL
BEGIN
SET #WhereClause = #WhereClause + ' and tblcameradetails.name like (#p3)';
END
IF #group IS NOT NULL
BEGIN
SET #WhereClause = #WhereClause + ' and tblgroupdetails.name in (#p4)';
END
SET #BaseQuery = #BaseQuery + #WhereClause + #OrderByClause;
EXECUTE sp_executesql #BaseQuery, #ParamList, #p1 = #division, #p2 = #store, #p3 = #camera, #p4 = #group;
END
Now error shows me that on line 34 error at syntax near '.', but I found no problem at all, because I am run this query form command prompt very well same applied in stored procedure, it will raised error. Please help me to solved out this.
You can easily print your variable #BaseQuery to check the resulting query.
the query in the variable #BaseQuery looks like this
select
distinct tblcameradetails.name as CameraName,
tblcameradetails.IsDeviceEnabled,
tblcameradetails.cameraID,
tblcameradetails.deviceIP,
tblmediasourcedetails.StreamName,
tblmediasourcedetails.streamID,
tblcameradetails.Model_ID,
tblcameradetails.IsHidden,
tblcameradetails.hasPTZcapability,
tblcameradetails.CameraModelNo,
tblcameradetails.username as CameraUserName,
tblcameradetails.hasPTZCycle,
tblcameradetails.hasPreset,
tblcameradetails.password as CameraPassword,
tblmediasourcedetails.isRecordingStarted as IsRecordingOn,
tblcameradetails.IsCovert,
tblcameradetails.constCameraName,
tblmediasourcedetails.constStreamName,
tblstoredetails.Store_ID,
tblsystemlocationdetails.division,
tblstoredetails.Store_Name,
tblstoredetails.Store_IP,
tblstoreconfiguration.Liveview_Session_Timeout
from
tblmediasourcedetails,
tblcameradetails,
tblcameragroupdetails,
tblgroupdetails,
tblvssaddsourcedetails,
tblsystemlocationdetails,
tblstoredetails,
tblstoreconfigurationwhere
tblmediasourcedetails.cameraID=tblcameradetails.cameraID
and tblmediasourcedetails.streamID=tblvssaddsourcedetails.streamID
and tblcameradetails.cameraID = tblcameragroupdetails.cameraID
and tblcameragroupdetails.groupID=tblgroupdetails.groupID
and tblstoredetails.Store_ID= tblcameradetails.Store_ID
and tblsystemlocationdetails.Store_ID= tblstoredetails.Store_ID
and tblstoredetails.Store_ID=tblstoreconfiguration.Store_ID and 1=1order by CameraName
You can see that you are missing a space before the WHERE and ORDER BY clause
You need to add a space in the starting of your WHERE and ORDER BY variables like this
#WhereClause nvarchar(max) = N' where
#OrderByClause nvarchar(100) = ' order by CameraName';
Try to put some PRINT in the middle of the query.
For example PRINT #BaseQuery after SET #BaseQuery = #BaseQuery + #WhereClause + #OrderByClause;
Because maybe you need a space in some variable (for example N' where...1=1' instead of N'where...1=1' )

SQL print line number in comment of dynamically created stored procedure?

I have written a script that is several thousands of lines long that I am using to generate some stored procedures dynamically.
I want to reference the script that generated the stored procedures in the comments in the stored procedures, and would like to be able to refer to the line in the script file by inserting the line number of the script file into the comments in the stored procedure file.
So for example if ##line_number gave the line number I want in the code bellow then ##line_number should be 5
1| declare #job varchar(max)
2| SET #job = '/* this is generated dynamicly by _______ */'
3| SET #job = #job + 'SELECT *' + CHAR(10)
4| SET #job = #job + 'FROM ' + #Table_Name + CHAR(10)
5| SET #job = #job + '/* ' + ##line_number + ' */'
You can use TRY / CATCH with a forced error as the CATCH block can return the line number that the error occurred on via the ERROR_LINE() function. The full construct, formatted for readability, is:
BEGIN TRY
;THROW 50000, 'Line#', 1 -- all 3 values are arbitrary, but required
END TRY
BEGIN CATCH
SET #LineNumber = ERROR_LINE()
END CATCH
Now, to get the #LineNumber variable to populate with the line number that it is being set on, you can reduce that construct to a single line as follows:
BEGIN TRY;THROW 50000,'',1;END TRY BEGIN CATCH;SET #Line=ERROR_LINE();END CATCH
Here is a full example of it working:
SET ANSI_NULLS ON
SET NOCOUNT ON
GO
-- Line #1 (of current batch, not of the entire script if GOs are used)
DECLARE #CRLF NCHAR(2) = NCHAR(13) + NCHAR(10),
#SQL1 NVARCHAR(MAX) = '',
#SQL2 NVARCHAR(MAX) = '', -- Line #5
#Line INT = -1 -- default to an invalid line #
SET #SQL1 += N'/********************' + #CRLF
SET #SQL1 += N' *' + #CRLF
SET #SQL1 += N' * Test Auto-' + #CRLF -- Line #10
SET #SQL1 += N' * Generated Proc 1' + #CRLF
BEGIN TRY;THROW 50000,'',1;END TRY BEGIN CATCH;SET #Line=ERROR_LINE();END CATCH
SET #SQL1 += N' * Line #:' + CONVERT(NVARCHAR(10), #Line) + #CRLF
SET #SQL1 += N' *' + #CRLF
SET #SQL1 += N' ********************/' + #CRLF -- Line #15
-- more code here
SET #SQL2 += N'/********************' + #CRLF
SET #SQL2 += N' *' + #CRLF -- Line #20
SET #SQL2 += N' * Test Auto-' + #CRLF
SET #SQL2 += N' * Generated Proc 2' + #CRLF
BEGIN TRY;THROW 50000,'',1;END TRY BEGIN CATCH;SET #Line=ERROR_LINE();END CATCH
SET #SQL2 += N' * Line #:' + CONVERT(NVARCHAR(10), #Line) + #CRLF
SET #SQL2 += N' *' + #CRLF -- Line #25
SET #SQL2 += N' ********************/' + #CRLF
PRINT #SQL1
PRINT #SQL2
GO
The line numbers returned for Proc 1 and Proc 2 are 12 and 23 respectively, which is correct for both.
Please note that the THROW command started in SQL Server 2012. If you are using SQL Server 2005, 2008, or 2008 R2, then you need to use RAISERROR() function instead of THROW.
I changed SolomonRutzky's answer a bit to get is to work in SQL Server versions prior to 2012:
DECLARE #Line INT
SET #Line = 0 BEGIN TRY RAISERROR ('Line#', 11, 1)WITH NOWAIT END TRY BEGIN CATCH SET #Line=ERROR_LINE() END CATCH
PRINT('/* testing ... I messed up somewhere near line: ' + CONVERT(varchar(10), ISNULL(#Line, 0)) + ' */')
Haven't found a built in function to return the current line number so I have started making a function to find the line number for me.
If I get the text of the current running query at the top and declare some variable, and then copy and past the function call and the #LineCounter increment code, I can get the current line number.
DECLARE #var1 NVARCHAR(MAX)
SELECT #var1 = sqltext.TEXT
FROM sys.dm_exec_requests req
CROSS APPLY sys.dm_exec_sql_text(sql_handle) AS sqltext
WHERE req.session_id = ##SPID
DECLARE #LineCounter int
SET #LineCounter = 0
DECLARE #Current_Line_Number int
SET #Current_Line_Number = 0
SET #LineCounter = #LineCounter + 1
SELECT #Current_Line_Number = [MSMS].[dbo].[ReturnLineNumber] (#var1, #LineCounter)
PRINT #Current_Line_Number
This is the function
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: James J
-- Create date: 11/11/2013
-- Description: Function to return the line number for
-- where the query was called from when passed the query
-- an the count of the times it has already been used.
-- =============================================
ALTER FUNCTION ReturnLineNumber
(
#CurrentQuery nvarchar(max),
#Count int
)
RETURNS int
AS
BEGIN
DECLARE #var1 NVARCHAR(MAX)
DECLARE #functionName nvarchar(30)
SET #functionName = 'ReturnLineNumber'
SET #var1 = #CurrentQuery
DECLARE #LineCount int
SET #LineCount = 0
IF (CHARINDEX(CHAR(13), #var1) > 0)
BEGIN
DECLARE #queryString nvarchar(max)
SET #queryString = #var1
DECLARE #LineIndex int
SET #LineIndex = 1
DECLARE #LineLength int
DECLARE #linestring nvarchar(max)
DECLARE #functioncount int
SET #functioncount = 0
WHILE (#LineIndex > 0)
BEGIN
SET #LineIndex = CHARINDEX(CHAR(13), #queryString)
SET #LineLength = LEN(#queryString) - CHARINDEX(CHAR(13), #queryString)
SET #linestring = SUBSTRING(#queryString, 0, #LineIndex + 1)
SET #queryString = SUBSTRING(#queryString, #LineIndex + 1, #LineLength)
SET #LineCount = #LineCount + 1
IF (CHARINDEX(#functionName, #linestring) > 0)
BEGIN
SET #functioncount = #functioncount + 1
IF (#functioncount = #Count)
BEGIN
RETURN #LineCount
END
END
END
END
RETURN 0
END
GO
This is not a great way to get the line number and I probably should add some more checks to make sure I don't have commented out function calls but this is the closest I have got for the moment.

How do I dynamically build a like clause in an executable sql stored procedure that uses EXEC sp_executesql?

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
#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 + '%'''
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.
SET #Where = #Where + 'AND ac.AutoCompleteName like ''%' + #NameSubstring + '%'''
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
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!

Resources