Stored procedure SQL Injection check - sql-server

I wrote the following stored procedure for querying the database. Can someone tell me if this dynamic query stored procedure is vulnerable to a SQL injection attack?
If it is, how to modify the following code to prevent SQL injection attacks?
The second question is OPTION (RECOMPILE) at the end of the WHERE cause, is it necessary with every execution?
CREATE PROCEDURE DataMapMainQuery
(#DataMapID VARCHAR(MAX),
#DataMapIDName VARCHAR(MAX),
#StartIndex INT,
#MaximumRows INT,
#sortExpression VARCHAR(MAX))
AS
BEGIN
DECLARE #FilteredTotalRows AS INT
DECLARE #SqlString NVARCHAR(MAX)
DECLARE #WhereString1 NVARCHAR(MAX)
DECLARE #WhereString2 NVARCHAR(MAX)
IF (#DataMapID IS NULL)
SET #WhereString1 = ' AND (DataMapID LIKE ' + '''%%''' + ' OR NULL IS NULL)'
ELSE
SET #WhereString1 = ' AND (DataMapID LIKE ' + '''%' + #DataMapID + '%''' + ' OR ''' + #DataMapID + ''' IS NULL)'
IF (#DataMapIDName IS NULL)
SET #WhereString2 = ' AND (DataMapIDName LIKE ' + '''%%''' + ' OR NULL IS NULL)'
ELSE
SET #WhereString2 = ' AND (DataMapIDName LIKE ' + '''%' + #DataMapIDName + '%''' + ' OR ''' + #DataMapIDName + ''' IS NULL)'
IF (#sortExpression IS NULL)
SET #sortExpression = 'DataMapID'
SELECT
#FilteredTotalRows = COUNT(*)
FROM
DataMapMain
WHERE
1 = 1
AND (DataMapID LIKE '%' + #DataMapID + '%' OR #DataMapID IS NULL)
AND (DataMapIDName LIKE '%' + #DataMapIDName + '%' OR #DataMapIDName IS NULL)
IF (#FilteredTotalRows < #StartIndex + 1)
BEGIN
SET #SqlString = '
SELECT
DataMapID, DataMapIDName,
DataMapGroup, DataMapGroupRemark,
CONVERT(BIGINT, TimeStamp) AS TimeStamp
FROM
(SELECT
ROW_NUMBER() OVER (ORDER BY ' + #sortExpression + ') AS RowNumber,
DataMapID, DataMapIDName,
DataMapGroup, DataMapGroupRemark,
TimeStamp
FROM
DataMapMain
WHERE
1 = 1'
+ #WhereString1
+ #WhereString2
+ ') DataMapMain
WHERE
RowNumber >= 1
AND RowNumber < (1 + ' + CONVERT(NVARCHAR(10), #MaximumRows) + ')
OPTION (RECOMPILE)'
END
ELSE
BEGIN
SET #SqlString = '
SELECT
DataMapID
,DataMapIDName
,DataMapGroup
,DataMapGroupRemark
,CONVERT(bigint, TimeStamp) as TimeStamp
FROM
(
Select ROW_NUMBER() over (order by ' + #sortExpression + ') as RowNumber
,DataMapID
,DataMapIDName
,DataMapGroup
,DataMapGroupRemark
,TimeStamp
From DataMapMain
WHERE
1 = 1'
+ #WhereString1
+ #WhereString2
+ ') DataMapMain
WHERE
RowNumber >= (' + CONVERT(nvarchar(10),#StartIndex) + ' + 1) and RowNumber < (' + CONVERT(nvarchar(10),#StartIndex) + ' + 1 + ' + CONVERT(nvarchar(10),#MaximumRows) + ' )
OPTION (RECOMPILE)'
END
PRINT #SqlString
PRINT #FilteredTotalRows
EXEC sp_executesql #SqlString
END

Just use sp_executesql with parameters. Build your dynamic T-SQL statements, but instead the value add #parameter_name. Then call the routine like this:
EXEC sp_executesql #sql
,N'#parameter_name1 INT, #parameter_name2 VARCHAR(128), #parameter_name3 BIT'
,#parameter_name1, #parameter_name2, #parameter_name3;

So far your #DataMapID and #DataMapName are safe because your building it first before applying in your main sql query. I would suggest adding these lines to check proper values of your sort expression, maxrows and start index
IF (#sortExpression NOT IN ('ASC', 'DESC'))
BEGIN
RAISERROR('invalid order expression', 16,1);
RETURN;
END;
IF (TRY_CAST(#StartIndex as int) = null or TRY_CAST(#MaximumRows as int) = null)
BEGIN
RAISERROR('invalid startindex or maximum rows', 16,1);
RETURN;
END;

Adding OPTION(RECOMPILE) hint provides to rebuild a new execution plan for the query execution for every execution. Under some circumstances it can help to improve the performance. However the recompile operation uses memory and CPU resources in order to generate new execution plan. As a result, if you are not sure about the effects of the performance you don't use it

Thanks for all of your helps, I rewrote the code below. Please let me know if it is not OK. thank you all!
CREATE PROCEDURE DataMapMainQuery
(#DataMapID VARCHAR(MAX),
#DataMapIDName VARCHAR(MAX),
#StartIndex INT,
#MaximumRows INT,
#sortExpression VARCHAR(MAX))
AS
BEGIN
DECLARE #FilteredTotalRows AS INT
DECLARE #SqlString NVARCHAR(MAX)
DECLARE #params NVARCHAR(MAX);
DECLARE #WhereString1 NVARCHAR(MAX)
DECLARE #WhereString2 NVARCHAR(MAX)
IF (#DataMapID IS NULL)
SET #WhereString1 = ' AND (DataMapID LIKE ' + '''%%''' + ' OR NULL IS NULL)'
ELSE
SET #WhereString1 = ' AND (DataMapID LIKE ' + '''%' + #DataMapID + '%''' + ' OR ''' + #DataMapID + ''' IS NULL)'
IF (#DataMapIDName IS NULL)
SET #WhereString2 = ' AND (DataMapIDName LIKE ' + '''%%''' + ' OR NULL IS NULL)'
ELSE
SET #WhereString2 = ' AND (DataMapIDName LIKE ' + '''%' + #DataMapIDName + '%''' + ' OR ''' + #DataMapIDName + ''' IS NULL)'
IF (#sortExpression IS NULL)
SET #sortExpression = 'DataMapID'
SELECT
#FilteredTotalRows = COUNT(*)
FROM
DataMapMain
WHERE
1 = 1
AND (DataMapID LIKE '%' + #DataMapID + '%' OR #DataMapID IS NULL)
AND (DataMapIDName LIKE '%' + #DataMapIDName + '%' OR #DataMapIDName IS NULL)
IF (#FilteredTotalRows < #StartIndex + 1)
BEGIN
SET #SqlString = '
SELECT
DataMapID, DataMapIDName,
DataMapGroup, DataMapGroupRemark,
CONVERT(BIGINT, TimeStamp) AS TimeStamp
FROM
(SELECT
ROW_NUMBER() OVER (ORDER BY ' + #sortExpression + ') AS RowNumber,
DataMapID, DataMapIDName,
DataMapGroup, DataMapGroupRemark,
TimeStamp
FROM
DataMapMain
WHERE
1 = 1'
+ #WhereString1
+ #WhereString2
+ ') DataMapMain
WHERE
RowNumber >= 1
AND RowNumber < (1 + ' + CONVERT(NVARCHAR(10), #MaximumRows) + ')'
END
ELSE
BEGIN
SET #SqlString = '
SELECT
DataMapID
,DataMapIDName
,DataMapGroup
,DataMapGroupRemark
,CONVERT(bigint, TimeStamp) as TimeStamp
FROM
(
Select ROW_NUMBER() over (order by ' + #sortExpression + ') as RowNumber
,DataMapID
,DataMapIDName
,DataMapGroup
,DataMapGroupRemark
,TimeStamp
From DataMapMain
WHERE
1 = 1'
+ #WhereString1
+ #WhereString2
+ ') DataMapMain
WHERE
RowNumber >= (' + CONVERT(nvarchar(10),#StartIndex) + ' + 1) and RowNumber < (' + CONVERT(nvarchar(10),#StartIndex) + ' + 1 + ' + CONVERT(nvarchar(10),#MaximumRows) + ' )'
END
SET #params = '
#DataMapID VARCHAR(MAX)
,#DataMapIDName VARCHAR(MAX)
,#StartIndex INT
,#MaximumRows INT
,#sortExpression VARCHAR(MAX)';
EXEC sp_executesql
#SqlString
,#params
,#DataMapID
,#DataMapIDName
,#StartIndex
,#MaximumRows
,#sortExpression;
END

Related

Set values of one column to match results outcome of another column

I'm looking to change this output below. Instead of it being -999.99. I want the result to show <3.
We're wanting the Column Reported As that shows <3.0 to show on the Report and NOT -999.99.
This is the following script that I am working with:
USE [HarvestSQL]
GO
SET ANSI_NULLS OFF
GO
SET QUOTED_IDENTIFIER OFF
GO
ALTER PROC [dbo].[spRp20DataListData]
#date1 DATETIME,
#date2 DATETIME,
#location VARCHAR(255),
#patients NTEXT
AS
EXEC ('
SELECT
*
FROM
vwReportPortalMajorTest
WHERE
(DrawDate >= ''3/1/2006 00:00:00'') AND
(DrawDate < ''5/7/2006 00:00:00'') AND
(DrawLocation = ''Ameri-Tech Kidney Center Arlington'')
')
DECLARE #date1String VARCHAR(40)
DECLARE #date2String VARCHAR(40)
SET #date1String = CONVERT(VARCHAR(40),#date1,109)
SET #date2String = CONVERT(VARCHAR(40),#date2,109)
IF #location = 'Charleston Renal Care'
BEGIN
SET #location = 'Liberty Dialysis Petersburg'
END
EXEC ('
SELECT
*
FROM
vwRp20MajorTest
WHERE
(DrawDate >= ''' + #date1String + ''') AND
(DrawDate < ''' + #date2String + ''') AND
(DrawLocation = ''' + #location + ''')
')
Any suggestions will help me out.
Here is another Stored Procedure that may help give more details:
USE [ReportPortal]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROC [dbo].[spRp20DataListData]
#date1 DATETIME,
#date2 DATETIME,
#location VARCHAR(1000),
#patients NTEXT,
#dynamicSqlSelectClause NTEXT,
#dynamicSqlGroupByClause NTEXT,
#suppressEmptyRows VARCHAR(10) = NULL,
#resultsToQuery INT = 0
AS
DECLARE #date1String VARCHAR(40)
DECLARE #date2String VARCHAR(40)
set #date2 = DateAdd(day, 1, #date2)
SET #date1String = CONVERT(VARCHAR(40),#date1,109)
SET #date2String = CONVERT(VARCHAR(40),#date2,109)
print #location
-- print SUBSTRING(#location,0, 7)
-- print SUBSTRING(#location,7, (len(#location)-6))
IF SUBSTRING(#location,0, 12) = 'Locations: '
BEGIN
print 'Running for Multiple Locations'
set #location = SUBSTRING(#location,12, (len(#location)-11))
print '#location updated to'
print #location
IF #resultsToQuery = 0 --All results
BEGIN
EXEC ('
SELECT
' + #dynamicSqlSelectClause + --PatientName,DrawDate,MAX((CASE WHEN NAME=''Albumin'' THEN NumberResult ELSE 0 END)) AS AlbuminResult
', PatientKey FROM
vwRp20CompletedTest
WHERE
DrawDate >= ''' + #date1String + ''' AND
DrawDate < ''' + #date2String + ''' AND
DrawAbbrev IN (' + #location + ') AND
PatientKey IN ( ' + #patients + ') AND
((NumberResult <> 0) OR (Name like ''%Hepatitis%'')) '
+ 'GROUP BY ' + #dynamicSqlGroupByClause + ', PatientKey'
)
END
ELSE
BEGIN
EXEC ('
SELECT
' + #dynamicSqlSelectClause + --PatientName,DrawDate,MAX((CASE WHEN NAME=''Albumin'' THEN NumberResult ELSE 0 END)) AS AlbuminResult
', PatientKey FROM
vwRp20MajorTest
WHERE
DrawDate >= ''' + #date1String + ''' AND
DrawDate < ''' + #date2String + ''' AND
DrawAbbrev IN (' + #location + ') AND
PatientKey IN ( ' + #patients + ') AND
((NumberResult <> 0) OR (Name like ''%Hepatitis%'')) '
+ 'GROUP BY ' + #dynamicSqlGroupByClause + ', PatientKey'
)
END
END
ELSE
BEGIN
print 'Running for Single Location'
IF #resultsToQuery = 0 --All results
BEGIN
EXEC ('
SELECT
' + #dynamicSqlSelectClause + --PatientName,DrawDate,MAX((CASE WHEN NAME=''Albumin'' THEN NumberResult ELSE 0 END)) AS AlbuminResult
', PatientKey FROM
vwRp20CompletedTest
WHERE
DrawDate >= ''' + #date1String + ''' AND
DrawDate < ''' + #date2String + ''' AND
DrawLocation = ''' + #location + ''' AND
PatientKey IN ( ' + #patients + ') ' --Ended statement here 07032019 added single quote here.
-- AND --removed all this because the number result is breaking many reports.
-- ((NumberResult > 2.9)
-- OR (Name like ''%Hepatitis%'')) '-- unsure if removing this Hepatitis will break reports.
+ 'GROUP BY ' + #dynamicSqlGroupByClause + ', PatientKey'
+ ' ORDER BY ' + #dynamicSqlGroupByClause
)
END
ELSE
BEGIN
EXEC ('
SELECT
' + #dynamicSqlSelectClause + --PatientName,DrawDate,MAX((CASE WHEN NAME=''Albumin'' THEN NumberResult ELSE 0 END)) AS AlbuminResult
' ,PatientKey FROM
vwRp20MajorTest
WHERE
DrawDate >= ''' + #date1String + ''' AND
DrawDate < ''' + #date2String + ''' AND
DrawLocation = ''' + #location + ''' AND
PatientKey IN ( ' + #patients + ') AND
((NumberResult <> 0) OR (Name like ''%Hepatitis%'')) '
+ 'GROUP BY ' + #dynamicSqlGroupByClause + ', PatientKey'
)
END
END
Would really need to see what's happening under the hood with the stored procedure. Otherwise, you would really need to modify your SELECT * statement to also include a CASE statement if "if this, then that".
Something like
USE [HarvestSQL]
GO
SET ANSI_NULLS OFF
GO
SET QUOTED_IDENTIFIER OFF
GO
ALTER PROC [dbo].[spRp20DataListData]
#date1 DATETIME,
#date2 DATETIME,
#location VARCHAR(255),
#patients NTEXT
AS
EXEC ('
SELECT
t.*,
CASE WHEN t.[Aluminum] < -999 THEN ''<3'' ELSE NULL END AS [Reported As]
FROM
vwRp20MajorTest t
WHERE
(t.DrawDate >= ''3/1/2006 00:00:00'') AND
(t.DrawDate < ''5/7/2006 00:00:00'') AND
(t.DrawLocation = ''Ameri-Tech Kidney Center Arlington'')
')
DECLARE #date1String VARCHAR(40)
DECLARE #date2String VARCHAR(40)
SET #date1String = CONVERT(VARCHAR(40),#date1,109)
SET #date2String = CONVERT(VARCHAR(40),#date2,109)
IF #location = 'Charleston Renal Care'
BEGIN
SET #location = 'Liberty Dialysis Petersburg'
END
EXEC ('
SELECT
*
FROM
vwRp20MajorTest
WHERE
(DrawDate >= ''' + #date1String + ''') AND
(DrawDate < ''' + #date2String + ''') AND
(DrawLocation = ''' + #location + ''')
')
https://www.w3schools.com/sql/sql_case.asp

Stored procedure with dynamic SQL and ORDER BY

I have built a stored procedure that aims to identify duplicates in a table and to display the duplicated rows in a meaningful order. It looks like this:
CREATE PROCEDURE [dbo].[spFindDuplicates]
#tableName nvarchar(255),
#field1 nvarchar(255),
#field2 nvarchar(255) = '1',
#field3 nvarchar(255) = '2',
#field4 nvarchar(255) = '3',
#field5 nvarchar(255) = '4'
AS
BEGIN
DECLARE #query AS nvarchar(MAX);
SET #query = '
SELECT *
FROM ' + #tableName + '
WHERE CAST(' + #field1 + ' AS nvarchar(255)) + CAST(' + #field2 + ' AS nvarchar(255)) + CAST(' + #field3 + ' AS nvarchar(255)) + CAST(' + #field4 + ' AS nvarchar(255)) + CAST(' + #field5 + ' AS nvarchar(255))
IN
(
SELECT CAST(' + #field1 + ' AS nvarchar(255)) + CAST(' + #field2 + ' AS nvarchar(255)) + CAST(' + #field3 + ' AS nvarchar(255)) + CAST(' + #field4 + ' AS nvarchar(255)) + CAST(' + #field5 + ' AS nvarchar(255))
FROM ' + #tableName + '
GROUP BY CAST(' + #field1 + ' AS nvarchar(255)) + CAST(' + #field2 + ' AS nvarchar(255)) + CAST(' + #field3 + ' AS nvarchar(255)) + CAST(' + #field4 + ' AS nvarchar(255)) + CAST(' + #field5 + ' AS nvarchar(255))
HAVING COUNT(*) > 1
)
ORDER BY ' + #field1 + ', ' + #field2 + ', ' + #field3 + ', ' + #field4 + ', ' + #field5
EXECUTE(#query);
END
GO
--Example:
EXEC spFindDuplicates #tableName = 'someRandomTable', #field1 = 'firstField', #field2 = 'secondField', #field3 = 'thirdField'
As you can see, I can use at most 5 different fields that I concatenate in order for me to get a key used to determine whether we have a duplicate or not. Please note that I use the CAST function to be able to concatenate fields with various datatypes (varchar, int, dates, etc.).
When I execute the above stored procedure with 5 different fields, it works fine. But I would like to be able to run it with a variable number of fields (from 1 to 5), which is why I provided default values for #field2 to #field5.
But when I execute it with the above example (3 fields provided), I get the following error message:
A column has been specified more than once in the order by list. Columns in the order by list must be unique.
QUESTION: How can I keep ordering the resulting table without getting an error?
BONUS QUESTION: If you find a dynamic way to use that stored procedure with any number of fields (4, 17, or whatever), that'd be even more useful to me.
Like I said in the comments, injection is a huge problem here, and you need to consider it. Saying "Let's consider I don't mind about injection" is naïve and you need to change that attitude. Always make your SQL safe; then there are no excuses and chances for your application being compromised.
As what you are after, I suspect this achieves the goal. There's no need for the subquery to scan your table with an IN here, you can make use of COUNT and the OVER clause within a CTE.
CREATE PROCEDURE [dbo].[FindDuplicates] --I've removed te sp prefix, as sp_ is reserved by MS
#tableName sysname,
#field1 sysname,
#field2 sysname = NULL,
#field3 sysname = NULL,
#field4 sysname = NULL,
#field5 sysname = NULL
AS BEGIN
DECLARE #query AS nvarchar(MAX);
SET #query = N'WITH CTE AS(' + NCHAR(10) +
N' SELECT *' + NCHAR(10) +
N' COUNT(*) OVER (PARTITION BY ' + STUFF(CONCAT(N',' + QUOTENAME(#field1),N',' + QUOTENAME(#field2),N',' + QUOTENAME(#field3),N',' + QUOTENAME(#field4),N',' + QUOTENAME(#field5)),1,1,N'') + N' AS RowCount' + NCHAR(10) +
N' FROM ' + QUOTENAME(#tableName) + N')' + NCHAR(10) +
N'SELECT *' + NCHAR(10) +
N'FROM CTE' + NCHAR(10) +
N'WHERE RowCount > 1' + NCHAR(10) +
N'ORDER BY ' + STUFF(CONCAT(N',' + QUOTENAME(#field1),N',' + QUOTENAME(#field2),N',' + QUOTENAME(#field3),N',' + QUOTENAME(#field4),N',' + QUOTENAME(#field5)),1,1,N'') + N';';
PRINT #query;
--EXEC sys.sp_executesql #query; --Uncomment to rrun the actual query
END
GO
For the command you gave us EXEC dbo.FindDuplicates #tableName = 'someRandomTable', #field1 = 'firstField', #field2 = 'secondField', #field3 = 'thirdField';, this returns the SQL:
WITH CTE AS(
SELECT *
COUNT(*) OVER (PARTITION BY [firstField],[secondField],[thirdField] AS RowCount
FROM [someRandomTable])
SELECT *
FROM CTE
WHERE RowCount > 1
ORDER BY [firstField],[secondField],[thirdField];
Which, I believe gives you the behaviour you are after.
Edited the code to check if the column list exists on the sys.columns there by making sure we get only the columns which are appropriate.
CREATE FUNCTION dbo.fn_SplitString
(
#List NVARCHAR(MAX),
#Delimiter NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
SELECT Item = y.i.value('(./text())[1]', 'nvarchar(4000)')
FROM
(
SELECT x = CONVERT(XML, '<i>'
+ REPLACE(#List, #Delimiter, '</i><i>')
+ '</i>').query('.')
) AS a CROSS APPLY x.nodes('i') AS y(i)
);
GO
ALTER PROCEDURE [dbo].[spFindDuplicates]
#tableName nvarchar(255),
#columnlist nvarchar(max)
AS
BEGIN
DECLARE #query AS nvarchar(MAX);
SET #columnlist = (SELECT STUFF((SELECT ','+'['+[name]+']'
FROM SYS.columns
WHERE object_id = object_id(#tableName)
AND [Name] IN
(
SELECT Item
FROM dbo.fn_SplitString(#columnlist,',')
)
FOR XML PATH('')
)
,1,1,''))
PRINT #columnlist
SET #query = 'SELECT * FROM (SELECT '+CAST(#columnlist AS NVARCHAR(MAX))+'
FROM '+CAST(#tableName AS nvarchar(MAX))+'
GROUP BY '+CAST(#columnlist AS NVARCHAR(MAX))+'
HAVING COUNT(*) > 1)Res1
ORDER BY '+#columnlist
EXEC SP_EXECUTESQL #query;
END
GO

Incorrect syntax near keyword IF dynamic sql

I'm currently new to sql server.
I am using stored procedure with the help of visual studio 2010 express.
And i am experimenting on stored procedure in sql server when i found this error, which is very annoying :
Incorrect syntax near keyword 'IF
UPDATE [table_name]
SET
[delete_datetime] = CURRENT_TIMESTAMP,
[delete_user_record_id] = 2
WHERE [table_name].[record_id] = 2
Here's my stored procedure function. It is to update time stamps on my current table :
USE [MGroupIS]
GO
/****** Object: StoredProcedure [dbo].[iSP_SET_DATA_INFORMATION] Script Date: 02/01/2016 14:40:26 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[SP_SET_DATA_INFORMATION]
--Paramateres
AS
DECLARE #sql AS NVARCHAR(4000)
DECLARE #pFlag AS VARCHAR(6)
SET #pFlag = #flag
--IF Delete
IF #pFlag = 'DELETE'
BEGIN
SET #sql = 'UPDATE [' + #tableName + '] ' + CHAR(13) +
' SET ' + CHAR(13) +
' [delete_datetime] = CURRENT_TIMESTAMP, ' + CHAR(13) +
' [delete_user_record_id] = ' + CAST(#userRecordID AS VARCHAR(20)) + CHAR(13) +
' WHERE [' + #tableName + '].[' + #keyField + '] = ' + CAST(#recordID AS VARCHAR(20))
END
ELSE
BEGIN
SET #sql = 'UPDATE [' + #tableName + '] ' + CHAR(13) +
' SET ' + CHAR(13) +
' [edit_datetime] = CURRENT_TIMESTAMP, ' + CHAR(13) +
' [edit_user_record_id] = ' + CAST(#userRecordID AS CHAR(3))
END
EXEC sp_executeSQL #sql
PRINT #sql
And i call the stored procedure from another stored procedure (ISP_CFG_ACCESS_RIGHT_QUERY) :
ELSE IF #action = 'SAVE'
--SAVE Action
BEGIN
--If saved
IF #removeUnsaved = 0
BEGIN
UPDATE [CFG_ACCESS_RIGHT]
SET [CFG_ACCESS_RIGHT].[NAME] = '' + #name + '',
[CFG_ACCESS_RIGHT].[NOTE] = '' + #note + ''
WHERE [CFG_ACCESS_RIGHT].[RECORD_ID] = #recordID
DELETE FROM [CFG_ACCESS_RIGHT_DETAIL]
WHERE [CFG_ACCESS_RIGHT_DETAIL].[access_right_record_id] = #recordID
SET #sql = 'INSERT INTO [CFG_ACCESS_RIGHT_DETAIL] ' + CHAR(13) +
'SELECT * FROM [' + #tempTable + '] ' + CHAR(13) +
' WHERE [' + #tempTable + '].[delete_datetime] IS NULL;'
EXEC sp_executeSQL #sql
EXEC [ISP_SET_DATA_INFORMATION]
'CFG_ACCESS_RIGHT',
'record_id',
#userRecordID,
#recordID,
#voidStatus,
#voidReason,
#flag,
0
EXEC [ISP_SET_DATA_INFORMATION]
'CFG_ACCESS_RIGHT_DETAIL',
'access_right_record_id',
#userRecordID,
#recordID,
#voidStatus,
#voidReason,
#flag,
0
END
--If leave without saving
ELSE
BEGIN
EXEC [ISP_SET_DATA_INFORMATION]
'CFG_ACCESS_RIGHT',
'record_id',
#userRecordID,
#recordID,
#voidStatus,
#voidReason,
'DELETE',
0
END
Please help.
Thank you before hand.
Edit 1 : Isolating the potential problematic part. Please help
Your dynamic DROP table statement in procedure is syntactically wrong.
It should be like this,
IF OBJECT_ID('tablename', 'U') IS NOT NULL
DROP TABLE tablename;
In addition to that there are few syntax errors in your T-SQL too. I have fixed it.
Please refer this code,
IF #pFlag = 'DELETE'
BEGIN
SET #sql = 'UPDATE [' + #tableName + '] ' + Char(13)
+ ' SET ' + Char(13)
+ ' [delete_datetime] = CURRENT_TIMESTAMP, '
+ Char(13) + ' [delete_user_record_id] = '
+ Cast(#userRecordID AS VARCHAR(20))
+ Char(13) + ' WHERE [' + #tableName + '].['
+ #keyField + '] = '
+ Cast(#recordID AS VARCHAR(20))
END
ELSE
BEGIN
SET #sql = 'UPDATE [' + #tableName + '] ' + Char(13)
+ ' SET ' + Char(13)
+ ' [edit_datetime] = CURRENT_TIMESTAMP, '
+ Char(13) + ' [edit_user_record_id] = '
+ Cast(#userRecordID AS CHAR(3))
--IF New Record
IF #flag = 'NEW'
BEGIN
SET #sql = #sql + ', ' + Char(13)
+ '[create_datetime] = CURRENT_TIMESTAMP, '
+ Char(13) + '[create_user_record_id] = '
+ Cast(#userRecordID AS CHAR(3))
END
--IF Void Record
IF #voidStatus = 1
BEGIN
SET #sql = #sql + ', ' + Char(13) + '[void_status] = 1, '
+ Char(13)
+ '[void_datetime] = CURRENT_TIMESTAMP, '
+ Char(13) + '[void_user_record_id] = '
+ Cast(#userRecordID AS CHAR(3)) + ', '
+ Char(13) + '[void_reason] = ''' + #voidReason
+ ''''
END
ELSE
BEGIN
SET #sql = #sql + ', ' + Char(13) + '[void_status] = 0, '
+ Char(13) + '[void_datetime] = NULL, '
+ Char(13) + '[void_user_record_id] = '
+ Cast(#userRecordID AS CHAR(3)) + ', '
+ Char(13) + '[void_reason] = NULL'
END
--IF Print
IF #printed = 1
BEGIN
SET #sql = #sql + ', ' + Char(13)
+ '[print_count] = [print_count] + 1, '
+ Char(13) + '[last_print_datetime] = NULL, '
+ Char(13) + '[print_user_record_id] = 0'
+ Cast(#userRecordID AS CHAR(3))
END
SET #sql = #sql + ', ' + Char(13)
+ 'record_version = record_version + 1 '
+ Char(13) + ' WHERE [' + #tableName + '].['
+ #keyField + '] = '
+ Cast(#recordID AS VARCHAR(20))
END
EXEC Sp_executesql
#sql
PRINT #sql
Procedure Code:
ALTER PROCEDURE [dbo].[Sp_cfg_access_right_query] #recordID INT,
#userRecordID INT,
#action CHAR(4),
#flag CHAR(6),
#tempTable VARCHAR(50),
#name VARCHAR(50),
#note VARCHAR(1000),
#voidStatus BIT,
#voidReason VARCHAR(200),
#removeUnsaved BIT
AS
--Setting NULL values
SET #recordID = Isnull(#recordID, 0)
SET #userRecordID = Isnull(#recordID, 0)
SET #action = Isnull(#action, '')
SET #flag = Isnull(#flag, '')
SET #tempTable = Isnull(#tempTable, '')
SET #name = Isnull(#name, '')
SET #note = Isnull(#note, '')
SET #voidStatus = Isnull(#voidStatus, 0)
SET #voidReason = Isnull(#voidReason, '')
DECLARE #detailCount INT
DECLARE #currentID INT
DECLARE #sql NVARCHAR(1000)
DECLARE #temp VARCHAR(100)
DECLARE #tempConstraint VARCHAR(100)
IF #action = 'LOAD'
--LOAD action
BEGIN
SET #temp = 'CFG_TEMP_ARD_'
+ Replace(Cast(Newid() AS VARCHAR(50)), '-', '')
SET #tempConstraint = Cast(Year(CURRENT_TIMESTAMP) AS VARCHAR(4))
+ Cast(Month(CURRENT_TIMESTAMP) AS VARCHAR(2))
+ Cast(Day(CURRENT_TIMESTAMP) AS VARCHAR(2))
+ Cast(Datepart(HOUR, CURRENT_TIMESTAMP) AS VARCHAR(2))
+ Cast(Datepart(MINUTE, CURRENT_TIMESTAMP) AS VARCHAR(2))
+ Cast(Datepart(SECOND, CURRENT_TIMESTAMP) AS VARCHAR(2))
+ Cast(Datepart(MILLISECOND, CURRENT_TIMESTAMP) AS VARCHAR(3))
SET #sql = 'SELECT * INTO [' + #temp
+ '] FROM [CFG_ACCESS_RIGHT_DETAIL] WHERE 1=0;'
EXEC Sp_executesql
#sql
SET #sql = 'ALTER TABLE [' + #temp + '] ' + Char(13)
+ 'ADD CONSTRAINT PK_' + #tempConstraint
+ Char(13)
+ 'PRIMARY KEY ([record_id], [access_right_record_id]);'
EXEC Sp_executesql
#sql
IF #flag = 'NEW'
BEGIN
INSERT INTO [CFG_ACCESS_RIGHT]
(NAME,
note,
record_version)
VALUES ('',
'',
1)
SELECT #recordID = Scope_identity()
FROM [CFG_ACCESS_RIGHT];
END
SELECT #recordID currentID,
#temp tempTable
--Load Access Right
SELECT *
FROM [CFG_ACCESS_RIGHT]
WHERE [record_id] = #recordID
--Check & Return Detail Count
SELECT #detailCount = ##ROWCOUNT
FROM [CFG_ACCESS_RIGHT_DETAIL]
WHERE [access_right_record_id] = #recordID
SELECT Isnull(#detailCount, 0) detailCount
--Load Access Right Details if exist
IF #detailCount > 0
BEGIN
SELECT *
FROM [CFG_ACCESS_RIGHT_DETAIL]
WHERE [access_right_record_id] = #recordID
AND [delete_datetime] IS NULL
END
END
ELSE IF #action = 'SAVE'
--SAVE Action
BEGIN
--If saved
IF #removeUnsaved = 0
BEGIN
UPDATE [CFG_ACCESS_RIGHT]
SET [CFG_ACCESS_RIGHT].[NAME] = '' + #name + '',
[CFG_ACCESS_RIGHT].[NOTE] = '' + #note + ''
WHERE [CFG_ACCESS_RIGHT].[RECORD_ID] = #recordID
DELETE FROM [CFG_ACCESS_RIGHT_DETAIL]
WHERE [CFG_ACCESS_RIGHT_DETAIL].[access_right_record_id] = #recordID
SET #sql = 'INSERT INTO [CFG_ACCESS_RIGHT_DETAIL] '
+ Char(13) + 'SELECT * FROM [' + #tempTable + '] '
+ Char(13) + ' WHERE [' + #tempTable
+ '].[delete_datetime] IS NULL;'
EXEC Sp_executesql
#sql
EXEC [Sp_set_data_information]
'CFG_ACCESS_RIGHT',
'record_id',
#userRecordID,
#recordID,
#voidStatus,
#voidReason,
#flag,
0
EXEC [Sp_set_data_information]
'CFG_ACCESS_RIGHT_DETAIL',
'access_right_record_id',
#userRecordID,
#recordID,
#voidStatus,
#voidReason,
#flag,
0
END
--If leave without saving
ELSE
BEGIN
EXEC [Sp_set_data_information]
'CFG_ACCESS_RIGHT',
'record_id',
#userRecordID,
#recordID,
#voidStatus,
#voidReason,
'DELETE',
0
END
END
SET #sql ='IF OBJECT_ID(''' + #tempTable
+ ''', ''U'') IS NOT NULL
DROP TABLE ' + #tempTable
EXEC Sp_executesql
#sql

Incremental batch delete and insert actions very slow in SQL Server

I have the following stored procedure which deletes and inserts rows in a table. It is running slow.
I have read various proposals and i have implemented:
Delete rows in batches and using derived table
Disable FK
Indexes on fields that are in the where clause.
So the way it should go is:
Tables have around 10 million records.
Every day i need to refresh around 15-30% of them. I use this SP to do it.
Source:
CREATE PROCEDURE [dbo].[spIncrementalUpdate]
-- Add the parameters for the stored procedure here
#table_inc nvarchar(30),
#table_target nvarchar(30),
#table_date nvarchar(30),
#field1 nvarchar(10),
#field2 nvarchar(10)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #logmessage as nvarchar(2048)
DECLARE #logdatacode as int
DECLARE #cmd as nvarchar(max)
DECLARE #fromjulian nvarchar(10)
DECLARE #datadate datetime
DECLARE #datadate_str nvarchar(10)
DECLARE #rows int
DECLARE #ParmDefinition nvarchar(500)
select #fromjulian = '114000'
print 'Using ' + #fromjulian
--
-- GET THE ROW COUNT OF THE INC TABLE.
--
IF #field2 = ''
BEGIN
SET #cmd = N'SELECT #retval = count(*) from ' +
#table_inc + ' where ' + #field1 +' > ' + #fromjulian
END
ELSE
BEGIN
SET #cmd = N'SELECT #retval = count(*) from ' +
#table_inc + ' where ' + #field1 +' > ' + #fromjulian +
' OR ' + #field2 +' > ' + #fromjulian
END
SET #ParmDefinition = N'#retval int OUTPUT'
EXEC sp_executesql #cmd, #ParmDefinition, #retval = #rows OUTPUT
--
if #rows <> 0
BEGIN
SET #logmessage = #table_inc + ' has ' +
cast(#rows as nvarchar(10)) +
' rows after ' + #fromjulian + ', deleting'
SET #logdatacode = 1000
--
-- Delete the records from original table based on #fromjulian
--
IF #field2 = ''
BEGIN
SET #cmd = N'delete ' + #table_target +
' from (select top(50000) * from ' + #table_target +
' where ' + #field1 + ' > ' + #fromjulian +
' order by ' + #field1 + ') ' +
#table_target
print #cmd
END
ELSE
BEGIN
SET #cmd = N'delete ' + #table_target +
' from (select top(50000) * from ' +
#table_target +
' where ' + #field1 + ' > ' + #fromjulian +
' OR ' + #field2 +' > ' + #fromjulian +
' order by ' + #field1 + ',' + #field2 + ') ' +
#table_target
print #cmd
END
SELECT 1
WHILE ##ROWCOUNT <> 0
BEGIN
EXEC sp_executesql #cmd
END
--
-- Inserting the records to target from INC table
--
IF #field2 = ''
BEGIN
SET #cmd = N'insert into ' + #table_target +
' select * from ' + #table_inc +
' where ' + #field1 +' > ' + cast(#fromjulian as nvarchar(10))
print #cmd
END
ELSE
BEGIN
SET #cmd = N'insert into ' + #table_target +
' select * from ' + #table_inc +
' where ' + #field1 +' > ' + cast(#fromjulian as nvarchar(10)) +
' OR ' + #field2 +' > ' + cast(#fromjulian as nvarchar(10))
print #cmd
END
print #cmd
EXEC sp_executesql #cmd
END
ELSE
BEGIN
SET #logmessage = 'NO ROWS IN ' + #table_inc + ' AFTER ' + cast(#fromjulian as nvarchar(10))
SET #logdatacode = 1001
END
--
-- LOG
--
INSERT INTO YLA_GROUP.[dbo].[sysssislog]
([event]
,[computer]
,[operator]
,[source]
,[sourceid]
,[executionid]
,[starttime]
,[endtime]
,[datacode]
,[databytes]
,[message])
VALUES
('spIncrementalUpdate','','','',NEWID(),NEWID(),getdate(),getdate(),#logdatacode,null,#logmessage)
print cast(#logdatacode as nvarchar(10)) + ' - ' + #logmessage
END
Since you are deleting rows in batches, you should remove the ORDER BY clause from your subquery because it is not necessary.
Here's the extract from your script:
IF #field2 = ''
BEGIN
SET #cmd = N'delete ' + #table_target +
' from (select top(50000) * from ' + #table_target +
' where ' + #field1 + ' > ' + #fromjulian + ') ' + #table_target
print #cmd
END
ELSE
BEGIN
SET #cmd = N'delete ' + #table_target +
' from (select top(50000) * from ' + #table_target +
' where ' + #field1 + ' > ' + #fromjulian +
' OR ' + #field2 +' > ' + #fromjulian + ') ' + #table_target
print #cmd
END
Remember that sorting can be an expensive operation, and even more expensive if you remove (correctly in this case) indexes.
Beyond that, there's not much you can do. Also consider that using dynamic SQL, execution plans are not cached. So, every time you execute the command inside #cmd the Query Engine needs to calculate the optimal execution plan. Unfortunately, as far as I can see, you need dynamic SQL.

How to create temp table from a dynamic query?

I want to create dynamic query for paging [Main Motive], here is my code:
Alter proc proc_GetData
(
#TableName varchar(500)='tblPropertyType',
#PrimaryKey varchar(500)='Id',
#Columns varchar(max)='PropertyType',
#WhereCondition varchar(max)='',
#RecsPerPage int =3,
#Page int=1,
#SortColumn varchar(500)='Id',
#SortDirection Varchar(56)='asc'
)
as
DECLARE #SQL VARCHAR(MAX)
DECLARE #DSQL VARCHAR(MAX)
DECLARE #FirstRec int, #LastRec int
SET #FirstRec = (#Page - 1) * #RecsPerPage
SET #LastRec = (#Page * #RecsPerPage + 1)
select #FirstRec
select #LastRec
SET #SQL='
SELECT
ROW_NUMBER() OVER (ORDER BY ' + #SortColumn+ ' '+ #SortDirection +') RowNum,'+ #PrimaryKey +' , ' +#Columns + ' from ' +#TableName+ ' Where 1=1 '+
#WhereCondition
What I want to do is:
First: inert all the records in a temp table from the above query.
Second: SELECT * FROM #TEMPResult WHERE RowNum > #FirstRec AND RowNum < #LastRec.
Please help me
SET #SQL='SELECT ROW_NUMBER() OVER
(ORDER BY ' + #SortColumn+ ' ' + #SortDirection + ') RowNum, ' + #PrimaryKey + ', ' + #Columns + '
INTO tempTableName
FROM ' + #TableName + ' Where 1 = 1 ' + #WhereCondition

Resources