InvalidSignatureException Sending POST request to AWS Kinesis from SQL - sql-server

I'm trying to send data to my kinesis stream from SQL but I'm only able to get back the error.
{
"__type": "InvalidSignatureException",
"message": "The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details."
}
My key & secret are correct as I've done a successful test in Postman.
Here is my function for creating the Authentication header...
ALTER FUNCTION [dbo].[CreateAuth]
(
#awsAccessKey NVARCHAR(MAX),
#awsSecretKey NVARCHAR(MAX),
#content NVARCHAR(MAX),
#dateTime Datetime
)
RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE #awsRegion NVARCHAR(MAX) = 'eu-west-2';
DECLARE #awsService NVARCHAR(MAX) = 'kinesis';
DECLARE #timeStamp NVARCHAR(16) = FORMAT(#dateTime, 'yyyyMMddTHHmmssZ');
DECLARE #scope NVARCHAR(MAX) = FORMAT(#dateTime, 'yyyyMMdd') + '/'+#awsRegion+'/'+#awsService+'/aws4_request'
DECLARE #x_amz_content_sha256 NVARCHAR(MAX);
DECLARE #hexbin VARBINARY(max) = HASHBYTES('SHA2_256 ',#content);
SET #x_amz_content_sha256 = LOWER(CONVERT([varchar](512), #hexbin,2))
-- CANONICAL REQUEST
DECLARE #CanonicalRequest NVARCHAR(MAX) = '';
-- HTTP verb
SET #CanonicalRequest += 'POST' + CHAR(13)
-- URL
SET #CanonicalRequest += 'kinesis.eu-west-2.amazonaws.com' + CHAR(13)
-- QUERYSTRING (must be sorted alphbetically)
SET #CanonicalRequest += '' + CHAR(13)
-- HEADERS (must be sorted alphbetically)
SET #CanonicalRequest += 'content-type:application/x-amz-json-1.1' + CHAR(13)
SET #CanonicalRequest += 'host:kinesis.eu-west-2.amazonaws.com' + CHAR(13)
SET #CanonicalRequest += 'x-amz-content-sha256:' + #x_amz_content_sha256 + CHAR(13)
SET #CanonicalRequest += 'x-amz-date:' + #timeStamp + CHAR(13)
SET #CanonicalRequest += 'x-amz-target:' + 'Kinesis_20131202.PutRecord' + CHAR(13)
-- SIGNED HEADERS
DECLARE #signedheaders NVARCHAR(MAX) = 'content-type;host;x-amz-content-sha256;x-amz-date;x-amz-target'
SET #CanonicalRequest += #signedheaders + CHAR(13)
-- HASHED PAYLOAD
SET #CanonicalRequest += #x_amz_content_sha256
DECLARE #CanonicalRequestHexbin VARBINARY(max) = HASHBYTES('SHA2_256 ',#CanonicalRequest);
-- STRING TO SIGN
DECLARE #stringToSign NVARCHAR(MAX) = '';
SET #stringToSign += 'AWS4-HMAC-SHA256' + CHAR(13)
SET #stringToSign += #timeStamp + CHAR(13)
SET #stringToSign += #scope + CHAR(13)
SET #stringToSign += LOWER(CONVERT([varchar](512), #CanonicalRequestHexbin,2))
-- CALCULATE SIGNATURE
DECLARE #DateKey VARBINARY(64);
DECLARE #DateRegionKey VARBINARY(64);
DECLARE #DateRegionServiceKey VARBINARY(64);
DECLARE #SigningKey VARBINARY(64);
DECLARE #Signature VARBINARY(64);
SET #DateKey = dbo.HMAC('SHA2_256',CONVERT(VARBINARY(MAX), 'AWS4'+#awsSecretKey),CONVERT(VARBINARY(MAX),FORMAT(#dateTime, 'yyyyMMdd')))
SET #DateRegionKey = dbo.HMAC('SHA2_256', #DateKey, CONVERT(VARBINARY(MAX),#awsRegion))
SET #DateRegionServiceKey = dbo.HMAC('SHA2_256', #DateRegionKey, CONVERT(VARBINARY(MAX),#awsService))
SET #SigningKey = dbo.HMAC('SHA2_256', #DateRegionServiceKey, CONVERT(VARBINARY(MAX),'aws4_request'))
SET #Signature = dbo.HMAC('SHA2_256',#SigningKey,CONVERT(VARBINARY(MAX),#stringToSign));
--BUILD Authorization
DECLARE #AuthValue NVARCHAR(MAX) = '';
SET #AuthValue += 'AWS4-HMAC-SHA256 Credential=' + #awsAccessKey + '/'+ #scope
SET #AuthValue += ',SignedHeaders=' + #signedheaders
SET #AuthValue += ',Signature=' + LOWER(CONVERT([varchar](512), #Signature,2))
RETURN #AuthValue
And here is the call to kinesis
-- Open the connection.
EXEC #ret = sp_OACreate 'MSXML2.ServerXMLHTTP', #token OUT;
IF #ret <> 0 RAISERROR('Unable to open HTTP connection.', 10, 1);
-- Send the request.
EXEC #ret = sp_OAMethod #token, 'open', NULL, 'POST', #url, 'false';
SET #auth = dbo.CreateAuth('MYACCESSKEY','MYSECRETKEY',#postData, #datetime)
EXEC #ret = sp_OAMethod #token, 'setRequestHeader', NULL, 'Authorization', #auth
PRINT #auth
PRINT ''
DECLARE #hexbin VARBINARY(max) = HASHBYTES('SHA2_256 ',#postData); --hash data
DECLARE #x_amz_content_sha256 NVARCHAR(MAX) = LOWER(CONVERT([varchar](512), #hexbin,2)) -- get hex of hashed data
EXEC #ret = sp_OAMethod #token, 'setRequestHeader', NULL, 'content-type', #contentType;
EXEC #ret = sp_OAMethod #token, 'setRequestHeader', NULL, 'host', 'kinesis.eu-west-2.amazonaws.com';
EXEC #ret = sp_OAMethod #token, 'setRequestHeader', NULL, 'x-amz-content-sha256', #x_amz_content_sha256;
EXEC #ret = sp_OAMethod #token, 'setRequestHeader', NULL, 'x-amz-date', #xAmzDate;
EXEC #ret = sp_OAMethod #token, 'setRequestHeader', NULL, 'x-amz-target', #xAmzTarget;
EXEC #ret = sp_OAMethod #token, 'send', NULL, #postData;
-- Handle the response.
EXEC #ret = sp_OAGetProperty #token, 'status', #status OUT;
EXEC #ret = sp_OAGetProperty #token, 'statusText', #statusText OUT;
EXEC #ret = sp_OAGetProperty #token, 'responseText', #responseText OUT;
-- Show the response.
PRINT 'Status: ' + #status + ' (' + #statusText + ')';
PRINT 'Response text: ' + #responseText;
-- Close the connection.
EXEC #ret = sp_OADestroy #token;
IF #ret <> 0 RAISERROR('Unable to close HTTP connection.', 10, 1);
The HMAC function I'm using is this one... https://gist.github.com/rmalayter/3130462
I've verified that the results produced with the SHA2_256 & HMAC functions are correct using https://codebeautify.org/
I'm pretty sure I'm doing everything according to... https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html and https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
I'm lost as to what to look try next.

I've solved it now. There were a number of problems aside from my lazy hard coding.
CanonicalURI should just be '/'. - thanks #AlwayLearning.
Changed all string variables to VARCHAR rather than NVARCHAR, this was causing incorrect hashing.
The date used for the #datekey also needs to be a VARCHAR CONVERT(VARCHAR(MAX),FORMAT(#dateTime, 'yyyyMMdd')) as the FORMAT() function returns a NVARCHAR.
And I found this site which was very helpful http://aws-signature.com.s3-website-us-west-2.amazonaws.com/
Successful function now looks like this...
ALTER FUNCTION [dbo].[CreateAuth]
(
#awsAccessKey VARCHAR(MAX),
#awsSecretKey VARCHAR(MAX),
#content VARCHAR(MAX),
#dateTime Datetime,
#awsRegion VARCHAR(MAX),
#awsService VARCHAR(MAX),
#CanonicalRequestURI VARCHAR(MAX),
#host VARCHAR(MAX),
#xAmzTarget VARCHAR(MAX),
#contentType VARCHAR(MAX)
)
RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE #date VARCHAR(8) = FORMAT(#dateTime, 'yyyyMMdd')
DECLARE #timeStamp VARCHAR(16) = FORMAT(#dateTime, 'yyyyMMddTHHmmssZ');
DECLARE #scope VARCHAR(MAX) = #date + '/'+#awsRegion+'/'+#awsService+'/aws4_request'
DECLARE #x_amz_content_sha256 VARCHAR(MAX);
DECLARE #hexbin VARBINARY(max) = HASHBYTES('SHA2_256 ',#content);
SET #x_amz_content_sha256 = LOWER(CONVERT([varchar](MAX), #hexbin,2))
-- CANONICAL REQUEST
DECLARE #CanonicalRequest VARCHAR(MAX) = '';
-- HTTP verb
SET #CanonicalRequest += 'POST' + CHAR(10)
-- URL
SET #CanonicalRequest += #CanonicalRequestURI + CHAR(10)
-- QUERYSTRING (must be sorted alphbetically)
SET #CanonicalRequest += '' + CHAR(10)
-- HEADERS (must be sorted alphbetically)
SET #CanonicalRequest += 'content-type:' + #contentType + CHAR(10)
SET #CanonicalRequest += 'host:' + #host + CHAR(10)
SET #CanonicalRequest += 'x-amz-content-sha256:' + #x_amz_content_sha256 + CHAR(10)
SET #CanonicalRequest += 'x-amz-date:' + #timeStamp + CHAR(10)
SET #CanonicalRequest += 'x-amz-target:' + #xAmzTarget + CHAR(10)
SET #CanonicalRequest += CHAR(10)
-- SIGNED HEADERS
DECLARE #signedheaders VARCHAR(MAX) = 'content-type;host;x-amz-content-sha256;x-amz-date;x-amz-target'
SET #CanonicalRequest += #signedheaders + CHAR(10)
-- HASHED PAYLOAD
SET #CanonicalRequest += #x_amz_content_sha256
DECLARE #CanonicalRequestHexbin VARBINARY(max) = HASHBYTES('SHA2_256 ',#CanonicalRequest);
-- STRING TO SIGN
DECLARE #stringToSign VARCHAR(MAX) = '';
SET #stringToSign += 'AWS4-HMAC-SHA256' + CHAR(10)
SET #stringToSign += #timeStamp + CHAR(10)
SET #stringToSign += #scope + CHAR(10)
SET #stringToSign += LOWER(CONVERT([varchar](MAX), #CanonicalRequestHexbin,2))
-- CALCULATE SIGNATURE
DECLARE #DateKey VARBINARY(MAX);
DECLARE #DateRegionKey VARBINARY(MAX);
DECLARE #DateRegionServiceKey VARBINARY(MAX);
DECLARE #SigningKey VARBINARY(MAX);
DECLARE #Signature VARBINARY(MAX);
SET #DateKey = dbo.HMAC('SHA2_256',CONVERT(VARBINARY(MAX), 'AWS4'+#awsSecretKey),CONVERT(VARBINARY(MAX),#date))
SET #DateRegionKey = dbo.HMAC('SHA2_256', #DateKey, CONVERT(VARBINARY(MAX),#awsRegion))
SET #DateRegionServiceKey = dbo.HMAC('SHA2_256', #DateRegionKey, CONVERT(VARBINARY(MAX),#awsService))
SET #SigningKey = dbo.HMAC('SHA2_256', #DateRegionServiceKey, CONVERT(VARBINARY(MAX),'aws4_request'))
SET #Signature = dbo.HMAC('SHA2_256',#SigningKey,CONVERT(VARBINARY(MAX),#stringToSign));
--BUILD Authorization
DECLARE #AuthValue VARCHAR(MAX) = '';
SET #AuthValue += 'AWS4-HMAC-SHA256 Credential=' + #awsAccessKey + '/'+ #scope
SET #AuthValue += ',SignedHeaders=' + #signedheaders
SET #AuthValue += ',Signature=' + LOWER(CONVERT([varchar](MAX), #Signature,2))
RETURN #AuthValue
END

Related

Maps API was working, but has suddenly stopped. Plan to create SP for ERP

Maps API was working, but has suddenly stopped. Getting ??? ??? for Address and NULL for everything else. This was working in the past.
Using this:
DECLARE #Address as varchar(100),
#City as varchar(25),
#State as varchar(2),
#PostalCode as varchar(10),
#Country varchar(40),
#County varchar(40),
#GPSLatitude numeric(18,9),
#GPSLongitude numeric(18,9),
#MapURL varchar(1024)
SET #Address = '333 W 35th st'
SET #City = 'Chicago'
SET #State = 'IL'
--Build the web API URL
DECLARE #URL varchar(MAX)
SET #URL = 'https://www.google.com/maps/api/geocode/xml?sensor=false&address='+
CASE WHEN #Address IS NOT NULL THEN #Address ELSE '' END +
CASE WHEN #City IS NOT NULL THEN ', ' + #City ELSE '' END +
CASE WHEN #State IS NOT NULL THEN ', '+ #State ELSE '' END +
CASE WHEN #PostalCode IS NOT NULL THEN ', ' + #PostalCode ELSE '' END +
CASE WHEN #Country IS NOT NULL THEN ', ' + #Country ELSE '' END+
'&key=I_HAVE_A_VALID_API'
SET #URL = REPLACE(#Url, ' ', '+')
---CREATE THE OATH REQUEST
DECLARE #Response varchar(8000)
DECLARE #XML xml
DECLARE #Obj int
DECLARE #Result int
DECLARE #HTTPStatus int
DECLARE #ErrorMsg varchar(MAX)
Exec #Result = sp_OACreate 'MSXML2.ServerXMLHttp',#Obj OUT
BEGIN TRY
EXEC #Result = sp_OAMethod #Obj, 'open', NULL, 'GET', #URL, false
EXEC #Result = sp_OAMethod #Obj, 'setRequestHeader', NULL, 'Content-Type', 'application/x-www-form-urlencoded'
EXEC #Result = sp_OAMethod #Obj, send, NULL, ''
EXEC #Result = sp_OAGetProperty #Obj, 'status', #HTTPStatus OUT
EXEC #Result = sp_OAGetProperty #Obj, 'responseXML.xml', #Response OUT
END TRY
BEGIN CATCH
SET #ErrorMsg = ERROR_MESSAGE()
END CATCH
EXEC #Result = sp_OADestroy #Obj
IF (#ErrorMsg IS NOT NULL) OR (#HTTPStatus <> 200)
BEGIN
SET #ErrorMsg = 'Error in spGeocode: ' + ISNULL(#ErrorMsg, 'Http result is: ' + CAST(#HTTPStatus AS varchar (10)))
RAISERROR (#ErrorMsg, 16,1, #HTTPStatus)
RETURN
END
--CAPTURE THE RESPONSE
BEGIN
SET #XML = CAST(#Response AS XML)
SET #GPSLatitude =#XML.value('(/GeocodeResponse/result/geometry/location/lat) [1]', 'numeric(18,9)')
SET #GPSLongitude = #XML.value('(/GeocodeResponse/result/geometry/location/lng) [1]', 'numeric(18,9)')
SET #City = #XML.value('(/GeocodeResponse/result/address_component[type="locality"]/long_name) [1]','varchar(40)')
SET #State = #XML.value('(/GeocodeResponse/result/address_component[type="administrative_area_level_1"]/short_name) [1]','varchar(40)')
SET #PostalCode = #XML.value('(/GeocodeResponse/result/address_component[type="postal_code"]/long_name) [1]','varchar(20)')
SET #Country = #XML.value('(/GeocodeResponse/result/address_component[type="country"]/short_name) [1]','varchar(40)')
SET #County = #XML.value('(/GeocodeResponse/result/address_component[type="administrative_area_level_2"]/short_name) [1]','varchar(40)')
SET #Address =
ISNULL(#XML.value('(/GeocodeResponse/result/address_component[type="street_number"]/long_name) [1]','varchar(40)'), '???') + ' '+
ISNULL (#XML.value('(/GeocodeResponse/result/address_component[type="route"]/long_name) [1]','varchar(40)'), '???')
SET #MapURL = 'https://maps.google.com/maps?f=q&hl=en&q=' + CAST (#GPSLatitude AS varchar(20)) + '+' + CAST(#GPSLongitude AS varchar(20))
END
BEGIN
SELECT #Address as Address,
#City as City,
#State as State,
#PostalCode as Zip,
#Country as Country,
#County as County,
#GPSLatitude as Lat,
#GPSLongitude as lon,
#MapURL as map
END
Output was Address, Latitude and Longitude and MapURL. Will eventually develop into a Stored Procedure in an ERP.
This worked a few weeks ago then had to shut it off for security reasons.

SP_Send_Dbmail containing comma

I'm trying to send out csv files through sp_send_dbmail and one of the fields (outcome) has commas in it. When running the code below, it will automatically separate some of the outcomes as two columns.
I've tried changing #query_result_separator, but then it wouldn't separate anything at all, except for the outcome field, which eventually gives me two columns.
declare #filename NVARCHAR(MAX)
DECLARE #prefix varchar(50)
DECLARE #q varchar(max)
DECLARE #body NVARCHAR(MAX)
SET #body = 'abc'
SET #filename = concat('abc_',replace(cast(cast(getdate() as date) as varchar),'-','_'),'.csv')
SET #prefix = '[sep=,' + CHAR(13) + CHAR(10) + 'ID]'
SET #q ='set nocount on; select right(''"=""'' + cast(ID as nvarchar) + ''"""'', 24)' + #prefix + ',Outcome, isnull(cast(Outcomedate as varchar),'''') as OutcomeDate,isnull(cast(Alerts as varchar),'''') as Alerts
from database.abc.defg'
EXEC msdb.dbo.sp_send_dbmail
#body = #body,
#body_format ='HTML',
#recipients = 'abc#defg.com',
#subject = 'abc',
#query = #q ,
#attach_query_result_as_file = 1,
#query_attachment_filename = #filename,
#query_result_separator = ',',
#query_result_no_padding = 1,
#query_result_width = 500;

SQL Server stored procedure to JSON error

I am trying to write the following stored procedure:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[spInsertAudit]
(#val1 VARCHAR(100),
#val2 VARCHAR(200))
AS
BEGIN TRY
DECLARE #return_value varchar(max)
/************************************/
/* The Query */
/************************************/
EXEC [dbo].[SerializeJSON]
'INSERT INTO tLogin (EMP, LoginInTime)
VALUES (#val1, #val2)'
END TRY
BEGIN CATCH
/************************************/
/* Get Error results back */
/************************************/
EXEC [dbo].[SerializeJSON]
'SELECT ERROR_NUMBER() AS ErrorNumber,
ERROR_SEVERITY() AS ErrorSeverity,
ERROR_STATE() AS ErrorState,
ERROR_PROCEDURE() AS ErrorProcedure,
ERROR_LINE() AS ErrorLine,
ERROR_MESSAGE() AS ErrorMessage'
END CATCH
The SerializeJSON stored procedure (found here) is this:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[SerializeJSON]
(#ParameterSQL AS VARCHAR(MAX))
AS
BEGIN
DECLARE #SQL NVARCHAR(MAX)
DECLARE #XMLString VARCHAR(MAX)
DECLARE #XML XML
DECLARE #Paramlist NVARCHAR(1000)
SET #Paramlist = N'#XML XML OUTPUT'
SET #SQL = 'WITH PrepareTable (XMLString)'
SET #SQL = #SQL + 'AS('
SET #SQL = #SQL + #ParameterSQL + 'FOR XML RAW,TYPE,ELEMENTS'
SET #SQL = #SQL + ')'
SET #SQL = #SQL + 'SELECT #XML=[XMLString]FROM[PrepareTable]'
EXEC sp_executesql #SQL
, #Paramlist
, #XML = #XML OUTPUT
SET #XMLString = CAST(#XML AS VARCHAR(MAX))
DECLARE #JSON VARCHAR(MAX)
DECLARE #Row VARCHAR(MAX)
DECLARE #RowStart INT
DECLARE #RowEnd INT
DECLARE #FieldStart INT
DECLARE #FieldEnd INT
DECLARE #KEY VARCHAR(MAX)
DECLARE #Value VARCHAR(MAX)
DECLARE #StartRoot VARCHAR(100);
SET #StartRoot = '<row>'
DECLARE #EndRoot VARCHAR(100);
SET #EndRoot = '</row>'
DECLARE #StartField VARCHAR(100);
SET #StartField = '<'
DECLARE #EndField VARCHAR(100);
SET #EndField = '>'
SET #RowStart = CharIndex(#StartRoot, #XMLString, 0)
SET #JSON = ''
WHILE #RowStart > 0
BEGIN
SET #RowStart = #RowStart + Len(#StartRoot)
SET #RowEnd = CharIndex(#EndRoot, #XMLString, #RowStart)
SET #Row = SubString(#XMLString, #RowStart, #RowEnd - #RowStart)
SET #JSON = #JSON + '{'
-- for each row
SET #FieldStart = CharIndex(#StartField, #Row, 0)
WHILE #FieldStart > 0
BEGIN
-- parse node key
SET #FieldStart = #FieldStart + Len(#StartField)
SET #FieldEnd = CharIndex(#EndField, #Row, #FieldStart)
SET #KEY = SubString(#Row, #FieldStart, #FieldEnd - #FieldStart)
SET #JSON = #JSON + '"' + #KEY + '":'
-- parse node value
SET #FieldStart = #FieldEnd + 1
SET #FieldEnd = CharIndex('</', #Row, #FieldStart)
SET #Value = SubString(#Row, #FieldStart, #FieldEnd - #FieldStart)
SET #JSON = #JSON + '"' + #Value + '",'
SET #FieldStart = #FieldStart + Len(#StartField)
SET #FieldEnd = CharIndex(#EndField, #Row, #FieldStart)
SET #FieldStart = CharIndex(#StartField, #Row, #FieldEnd)
END
IF LEN(#JSON) > 0
SET #JSON = SubString(#JSON, 0, LEN(#JSON))
SET #JSON = #JSON + '},'
--/ for each row
SET #RowStart = CharIndex(#StartRoot, #XMLString, #RowEnd)
END
IF LEN(#JSON) > 0
SET #JSON = SubString(#JSON, 0, LEN(#JSON))
SET #JSON = '[' + #JSON + ']'
SELECT #JSON
END
When I execute the spInsertAudit stored procedure, I get this error:
Msg 102, Level 15, State 1, Line 8
Incorrect syntax near 'XML'.
So it did write to that table but for some reason it's not returning the JSON and having that error above....

incorrect syntax near '*'

I was trying to execute one of my Stored procedure but i am getting an syntax error and i am unable to understand why.
here is the sproc:
ALTER PROCEDURE [dbo].[HotlinePlusAdministration_ArticleMigrator]
#ArticleKey AS INT,
--#CategoryID AS INT,
--#Title AS Varchar(200),
--#ArticleDate AS datetime,
#DestLinkServer AS VARCHAR(50),
#UserID AS VARCHAR(8),
#ReturnMsg AS VARCHAR(1000) OUTPUT
AS
BEGIN
DECLARE #Query AS NVARCHAR(4000)
DECLARE #Log AS VARCHAR(8000)
DECLARE #ArticleID as int
DECLARE #NewArticleID as int
DECLARE #ArticleKeyExists as int
DECLARE #Title as varchar(200)
DECLARE #CategoryID as INT
DECLARE #ArticleDate as varchar(30)
DECLARE #ParmDefinition nvarchar(500);
SET XACT_ABORT ON -- Required for nested transaction
BEGIN TRAN
-- Check if ArticleID exists in Destination Server
SET #Query = N' SELECT #ArticleKeyExists = COUNT(*)
FROM ' + #DestLinkServer + '.HL2_61.dbo.Article' + ' where ArticleKey = ' + str(#ArticleKey)
SET #ParmDefinition = N'#ArticleKey int, #ArticleKeyExists int OUTPUT';
EXECUTE sp_executesql #Query , #ParmDefinition, #ArticleKey , #ArticleKeyExists OUTPUT;
IF ##ERROR <> 0
BEGIN
ROLLBACK TRANSACTION
SET #ReturnMsg = #Log + '<span style="color:red;">ERROR: <br></span>'
RETURN -1
END
--Delete existing Articles for select page
set #Query = 'DELETE FROM ' + #DestLinkServer +
'.HL2_61.dbo.Article ' +
'WHERE ArticleKey = ' + CONVERT(VARCHAR, #ArticleKey)
--'WHERE CategoryID = ' + CONVERT(VARCHAR, #CategoryID) + ' and Title = ''' + #Title + ''' and ArticleDate = ''' + #ArticleDate + ''''
Print #Query
EXEC(#Query)
when i am trying to execute it i am getting an error here:
SELECT #ArticleKeyExists = COUNT(*)
FROM BRWSQLDC.HL2_61.dbo.Article where ArticleKey = 1591276581
Can some body please help me on this,
Thanks.
SET #ArticleKeyExists = (SELECT COUNT(*) FROM BRWSQLDC.HL2_61.dbo.Article where ArticleKey = 1591276581)

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