Calling Freshdesk API from a SQL Server trigger - sql

My client wants an insert trigger on his Order table (from Sage) to create a Freshdesk ticket using the API.
As part of my development, I built a stored procedure that does the job fine when provided with an order number. However, transplanting the same code into a trigger returns without error, but nothing appears in the Freshdesk system, when the same code in a stored procedure works.
I expect comments about why an API call in a trigger might be a bad idea, but the Freshdesk call is very quick (<1 second from the stored procedure).
What I'd like to know is -- Is this is architecturally forbidden by SQL Server for some reason? If it's allowed, where might I look for the error that's being thrown.
Edit2: OK, here's the whole trigger .. pervious version just had OA calls.
ALTER TRIGGER [dbo].[CreateFreshdeskTicketFromOrder]
ON [dbo].[OEORDH]
AFTER INSERT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Get the original order number, and use that in the main lookup query
DECLARE #ORDNUM VARCHAR(22)
SELECT #ORDNUM = ORDNUMBER FROM inserted
-- Variables for fields going to the API
DECLARE #EMAIL VARCHAR(60), #SHPCONTACT VARCHAR(60), #ORDNUMBER VARCHAR(22)
DECLARE #LOCATION VARCHAR(6), #EXPDATE INT
DECLARE #SHPPHONEC VARCHAR(30), #SHPNAME VARCHAR(60), #DESCR VARCHAR(60)
DECLARE #CODEEMPL VARCHAR(15)
-- Collect field values that were just inserted
SELECT
#EMAIL = rtrim(OEORDH1.SHPEMAILC), #SHPCONTACT = rtrim(SHPCONTACT),
#ORDNUMBER = rtrim(ORDNUMBER), #LOCATION = LOCATION, #EXPDATE = EXPDATE,
#SHPPHONEC = rtrim(OEORDH1.SHPPHONEC), #SHPNAME = SHPNAME,
#DESCR = rtrim([DESC]), #CODEEMPL = rtrim(ARSAP.CODEEMPL)
-- FROM inserted
FROM dbo.OEORDH
JOIN dbo.OEORDH1 on dbo.OEORDH.ORDUNIQ = dbo.OEORDH1.ORDUNIQ
JOIN dbo.ARSAP on dbo.OEORDH.SALESPER1 = dbo.ARSAP.CODESLSP
WHERE ORDNUMBER = #ORDNUM
-- Variables from database to the API call
DECLARE #EXPDATE_OUT VARCHAR(10)
SET #EXPDATE_OUT =
substring ( cast ( #EXPDATE as varchar(8) ), 1, 4 ) + '-' +
substring ( cast ( #EXPDATE as varchar(8) ), 5, 2 ) + '-' +
substring ( cast ( #EXPDATE as varchar(8) ), 7, 2 );
DECLARE #STATUS_OUT VARCHAR(2)
IF #LOCATION = '1A'
SET #STATUS_OUT = '23';
ELSE
IF #LOCATION = '1'
SET #STATUS_OUT = '40';
ELSE
SET #STATUS_OUT = '2';
-- Variables for building the API call
DECLARE #Object INT
DECLARE #Url VARCHAR(80)
DECLARE #Body1 VARCHAR(1000) =
'{ ' +
'"email": "'+ #EMAIL +'", ' +
'"custom_fields": { "order_number": "'+ #ORDNUMBER +'", "scheduled_date": "'+ #EXPDATE_OUT + '", ' +
'"delivered_to": "'+ #SHPCONTACT + '", ' + '"consignee_phone_number": "'+ #SHPPHONEC +'" }, ' +
'"status": '+ #STATUS_OUT + ', ' +
'"priority": 1, "subject": "'+ rtrim(#ORDNUMBER) + ' - ' + rtrim(#SHPNAME) + ' (' + #DESCR + ')", ' +
'"responder_id": ' + #CODEEMPL +
' }'
DECLARE #ResponseText VARCHAR(1000), #return_status INT
SET #Url = 'https://client.freshdesk.com/api/v2/tickets';
-- Do REST call to API / All return statuses commented out except for last
Exec #return_status = sp_OACreate 'MSXML2.ServerXMLHTTP', #Object OUT;
-- Select 'Create return', #return_status
Exec #return_status = sp_OAMethod #Object, 'Open', NULL, 'POST', #Url, false
-- Select 'Open return', #return_status
Exec #return_status = sp_OAMethod #Object, 'setRequestHeader', NULL,
'Content-Type', 'application/json'
-- Select 'Set Request Header1 return', #return_status
Exec #return_status = sp_OAMethod #Object, 'setRequestHeader', NULL,
'Authorization', 'Basic ABC123=='
-- Select 'Set Request Header2 return', #return_status
Exec #return_status = sp_OAMethod #Object, 'Send', NULL, #Body1
-- Select 'Send1 return', #return_status
Exec sp_OAMethod #Object, 'ResponseText', #ResponseText OUT
-- Select 'Response', #ResponseText
Exec sp_OADestroy #Object
-- Add the conversation to the TriggerLog
IF #ResponseText IS NULL
SET #ResponseText = '(Null)';
INSERT INTO dbo.TriggerLog (tl_source, tl_input, tl_output) VALUES
( 'FreshdeskInsertTrigger', #Body1, #ResponseText )
END
That's the trigger code.
A stored procedure that has the same code (but takes an order number as a parameter) works correctly and does the API call and does the logging. Commenting out the logging at the end of the trigger made the error from Sage go away, but the API call still didn't arrive.

What happens if you simply call your working stored procedure from the trigger (via EXEC) instead of including the procedure's code in the trigger?
One thing to look closely at is this place in the code:
-- Collect field values that were just inserted
SELECT
#EMAIL = rtrim(OEORDH1.SHPEMAILC), #SHPCONTACT = rtrim(SHPCONTACT),
#ORDNUMBER = rtrim(ORDNUMBER), #LOCATION = LOCATION, #EXPDATE = EXPDATE,
#SHPPHONEC = rtrim(OEORDH1.SHPPHONEC), #SHPNAME = SHPNAME,
#DESCR = rtrim([DESC]), #CODEEMPL = rtrim(ARSAP.CODEEMPL)
-- FROM inserted
FROM dbo.OEORDH
JOIN dbo.OEORDH1 on dbo.OEORDH.ORDUNIQ = dbo.OEORDH1.ORDUNIQ
JOIN dbo.ARSAP on dbo.OEORDH.SALESPER1 = dbo.ARSAP.CODESLSP
WHERE ORDNUMBER = #ORDNUM
You commented out FROM inserted and try to read values from the table directly.
When the trigger code runs the transaction is not committed yet, so most likely you should read the values from the inserted table. It is likely that this SELECT doesn't find a row with the given #ORDNUM and variables remain NULL.
Side note. Triggers in SQL Server are fired once per the statement, not once per row. Your trigger should work correctly even if inserted table has several rows. Right now your trigger would pick only one ORDNUMBER even if several rows were inserted into the table. Most likely this is not what you want.
How to debug the trigger?
One simple and very straight-forward way is to create a table for logging and add plenty of INSERT statements that would log values of all variables into that table. Then you can read through the logs and see what was going on.

Related

Teradata UDF to Call External RESTful API?

We have a RESTful API which performs some rather sophisticated encryption functions. Our customer is using Teradata and they want to implement this API natively in their database.
The goal is to not store unencrypted values in the database but to make the unencrypted values available when queried.
There are more controls which will be wrapped around this process but in its simplest form, one way to use it is to create Teradata UDFs and use them in:
An After Insert trigger which takes the unencrypted value from the new row, passes it to the API, then takes the result and replaces the unencrypted value with the encrypted value returned by the API.
A view which takes the encrypted value, passes it to our API, and returns the decrypted value in the view result set.
Both the API payload and the returned values are formatted Json and the SQL/UDF will need to get an authentication token to access the API.
We are able to use the API in any number of applications outside of Teradata, including SQL Server, but cannot find documentation to show us how to do this in Teradata. Does anyone have any leads or examples of calling RESTful APIs from within a Teradata UDF?
ALTER FUNCTION [dbo].[GetProfile]
(
#maskProfile tinyint ,
#PolyId nvarchar(50)
)
RETURNS
#profileTable TABLE
(
SourceSystemKey nvarchar(100),
PolyId nvarchar(50),
FirstName nvarchar(100),
LastName nvarchar(100),
DateOfBirth datetime,
Gender nvarchar(10),
EmailAddress nvarchar(100),
PhoneNumber nvarchar(50),
KeyType nvarchar(100),
KeyValue nvarchar(100),
StreetAddress1 nvarchar(100),
StreetAddress2 nvarchar(100),
StreetAddress3 nvarchar(100),
StreetAddress4 nvarchar(100),
City nvarchar(100),
StateCode nvarchar(10),
PostalCode nvarchar(10),
County nvarchar(100),
Country nvarchar(100)
)
AS
BEGIN
DECLARE #method varchar(5)
DECLARE #url varchar(1000)
DECLARE #token nvarchar(2000)
DECLARE #params nvarchar(2000)
DECLARE #tokenResponse nvarchar(max)
DECLARE #profileResponse nvarchar(max)
DECLARE #profileJson nvarchar(max)
DECLARE #vaultUri nvarchar(100) = 'http://xxxxx'
SET #method='POST';
SET #url = #vaultUri + '/passthrough/api/auth/login';
SET #token=''
SET #params = '{"AccountId": "<account id>",' +
' "ApiKey": "<api key>"}';
select #tokenResponse = dbo.GetPostAPI(#method,#url,#token,#params)
set #token =
(SELECT * FROM OPENJSON(#tokenResponse, N'$.Data')
WITH (
[token] nvarchar(max) N'$.Token'
))
set #method = 'PUT';
set #url = #vaultUri + '/piivault/api/profiles/GetProfile';
set #params = '{
"Seed": 0,
"Ids": [
{ "Index": 0,
"PolyId": "' + #PolyId + '" } ]
}';
set #profileResponse = dbo.GetPostAPI(#method,#url,#token,#params) ;
select #profileJson = JSON_QUERY (#profileResponse, '$.Data.Profiles')
select #profileJson = SUBSTRING(#profileJson, 2, len(#profileJson) - 2)
insert into #profileTable
select top 1 *
from GetProfileAttributes(#profileJson)
RETURN
END
GO
```
```
ALTER function [dbo].[GetPostAPI]
(
#method varchar(5),
#url varchar(8000),
#token NVARCHAR(2000),
#params NVARCHAR(2000)
)
returns varchar(8000)
as
BEGIN
DECLARE #authHeader NVARCHAR(2000);
DECLARE #contentType NVARCHAR(64);
DECLARE #postData NVARCHAR(2000);
DECLARE #responseText NVARCHAR(2000);
DECLARE #responseXML NVARCHAR(2000);
DECLARE #ret INT;
DECLARE #status NVARCHAR(32);
DECLARE #statusText NVARCHAR(32);
DECLARE #internalToken INT;
SET #authHeader= 'Bearer ' + #token;
SET #contentType = 'application/json';
EXEC #ret = sp_OACreate 'WinHttp.WinHttpRequest.5.1', #internalToken OUT;
-- Send the request.
EXEC #ret = sp_OAMethod #internalToken, 'open', NULL, #method, #url, 'false';
EXEC #ret = sp_OAMethod #internalToken, 'setRequestHeader', NULL, 'Authorization', #authHeader;
if #ret <> 0
begin
set #ret = 'sp_OAMethod setRequestHeader failed'
end
EXEC #ret = sp_OAMethod #internalToken, 'setRequestHeader', NULL, 'Content-type', #contentType;
EXEC #ret = sp_OAMethod #internalToken, 'send', NULL, #params;
-- Handle the response.
EXEC #ret = sp_OAGetProperty #internalToken, 'status', #status OUT;
EXEC #ret = sp_OAGetProperty #internalToken, 'statusText', #statusText OUT;
EXEC #ret = sp_OAGetProperty #internalToken, 'responseText', #responseText OUT;
-- Close the connection.
EXEC #ret = sp_OADestroy #internalToken;
return #responseText
END
GO
```
```
ALTER view [dbo].[v_xxx]
as
select ff.*, profile.FirstName, profile.LastName, profile.EmailAddress
from ff_flights_Anonymized ff
cross apply GetProfile(0, ff.mileageplus_number_PolyId) profile
GO
`
```

SQL Server : to column merge from a custom table-defined list

Using SQL Server 2012 Express.
I'm building a communications system which emails end users some notifications about upcoming work. I have a table that stores merge field placeholders and the database field which should populate the data into these placeholders.
I've tried to write a function where I can supply an input string with these placeholders, a unique identifier for that user/machine, and the tenantID, and have it return the string with all the placeholder values replaced with the user's/machine's database values.
I'm running into the error:
Only functions and some extended stored procedures can be executed from within a function.
which I've googled and understand why, however I don't know how else I can achieve what I'm trying to do without doing it the way I'm doing it.
The code I've written is as follows:
CREATE TABLE [dbo].[CommsFields]
(
[ID] [BIGINT] IDENTITY(1,1) NOT NULL,
[TenantID] [BIGINT] NOT NULL,
[Placeholder] [VARCHAR](255) NOT NULL,
[DBField] [VARCHAR](255) NOT NULL
)
INSERT INTO CommsFields (TenantID, Placeholder, DBField)
VALUES (1, '##Email', 'u_PrimarySMTP')
CREATE TABLE [dbo].[AllMUs]
(
[MUID] BIGINT,
u_PrimarySMTP VARCHAR(255)
)
INSERT INTO AllMUs (MUID, u_PrimarySMTP)
VALUES (28228, 'user#domain.com')
CREATE FUNCTION DoFieldMerge
(#SourceString NVARCHAR(MAX),
#MUID BIGINT,
#TenantID BIGINT)
RETURNS NVARCHAR(MAX)
AS
BEGIN
DECLARE #ResultString NVARCHAR(MAX)
DECLARE #sql NVARCHAR(MAX)
DECLARE #PHID AS INT
DECLARE #PHCursor AS CURSOR
SET #sql = CONCAT('DECLARE #Tenant bigint = ', #TenantID, '; DECLARE #MU bigint = ', #MUID, '; DECLARE #ResultString2 nvarchar(MAX) = ''', #SourceString, ''';')
SET #PHCursor = CURSOR FOR
SELECT ID FROM CommsFields
OPEN #PHCursor;
FETCH NEXT FROM #PHCursor INTO #PHID
WHILE ##FETCH_STATUS = 0
BEGIN
SET #sql = CONCAT(#sql, 'SET #ResultString2 = replace(#ResultString2, ''', (SELECT Placeholder FROM CommsFields WHERE ID = #PHID AND TenantID = #TenantID), ''', (SELECT ISNULL(', (SELECT DBField FROM CommsFields WHERE ID = #PHID AND TenantID = #TenantID), ', '''') FROM AllMUs WHERE MUID = ', #MUID, ' AND TenantID = ', #TenantID, '));')
FETCH NEXT FROM #PHCursor INTO #PHID
END
CLOSE #PHCursor;
DEALLOCATE #PHCursor;
--SET #ResultString = #sql
EXEC sp_executesql #sql, N'#ResultString2 nvarchar(MAX) output', #ResultString output;
RETURN #ResultString
END
GO
SELECT dbo.DoFieldMerge('test ##Email', 28228, 1)
I expect the output to be 'test user#domain.com' but instead I get the error
Only functions and some extended stored procedures can be executed from within a function
If I comment out the 'exec' command near the end, and un-comment the 'SET' command above it, I get the SQL output which I was trying to achieve, and if I run this I get a correct result, but I need to run the contents of #sql within the function and return just the resulting #ResultString.
I've also tried searching for a code snippet that acts as a custom column merger in SQL, but I'm getting so many results which are nothing to do with what I'm really wanting.
The answer here was to instead use a stored procedure with an output parameter. My final SQL ended up as follow:
CREATE PROCEDURE [dbo].[DoMerge]
#SourceString nvarchar(MAX)
, #MUID bigint
, #TenantID bigint
, #ResultString nvarchar(MAX) output
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql nvarchar(MAX)
DECLARE #PHID as INT
DECLARE #PHCursor as CURSOR
SET #sql = CONCAT('DECLARE #Tenant bigint = ', #TenantID, '; DECLARE #MU bigint = ', #MUID, '; DECLARE #ResultString2 nvarchar(MAX) = ''', #SourceString, ''';')
SET #PHCursor = CURSOR FOR
SELECT ID FROM CommsFields
OPEN #PHCursor;
FETCH NEXT FROM #PHCursor INTO #PHID
WHILE ##FETCH_STATUS = 0
BEGIN
SET #sql = CONCAT(#sql, 'SET #ResultString2 = replace(#ResultString2, ''', (SELECT CONCAT((SELECT TemplateFieldMarker FROM Config), Placeholder) FROM CommsFields WHERE ID = #PHID AND TenantID = #TenantID), ''', (SELECT ISNULL(', (SELECT DBField FROM CommsFields WHERE ID = #PHID AND TenantID = #TenantID), ', '''') FROM AllMUs WHERE MUID = ', #MUID, ' AND TenantID = ', #TenantID, '));')
FETCH NEXT FROM #PHCursor INTO #PHID
END
CLOSE #PHCursor;
DEALLOCATE #PHCursor;
SET #sql = CONCAT(#sql, ' SELECT #ResultString2')
exec (#sql)
END

Stored Procedure to import data into SQL Server database... Error

I have a text file file1.txt in the below format. Column 1 is AGUSR1 & Column 2 is AGUSR2. There are 3 records in the file:
"AGUSR1"|"AGUSR2"
"JASON "|"DEBBIE "
"JOY "|"JASON "
"ANNA "|"JOHN "
I have written a stored procedure to upload this text file into a SQL Server database like this:
CREATE PROCEDURE sp_Import
#TableName varchar(200),
#FilePath varchar(200)
AS
DECLARE #SQL varchar(5000)
SET #SQL = 'BULK INSERT ' + #TableName + ' FROM ''' + #FilePath +
''' WITH (FIELDTERMINATOR = ''|'', ROWTERMINATOR = ''{CR}{LF}'')'
EXEC (#SQL)
To execute it, I have used this statement:
EXEC sp_Import '[DB_DEMO].[dbo].[file1]' , '\\kacl1tsp048\DEMO\file1.txt'
Note : kacl1tsp048 is a remote server the input text file is at.
On execution, I am getting the below error -
Msg 4863, Level 16, State 1, Line 1
Bulk load data conversion error (truncation) for row 1, column 2 (AGUSR2).
The import into table schema is
CREATE TABLE [dbo].[file1]
(
[AGUSR1] [varchar](10) NULL,
[AGUSR2] [varchar](10) NULL
)
For ad-hoc style data imports I sometimes dispense with using BULK INSERTS in favor of selecting from the file itself to then process it. You could do something similar by creating a procedure to read your file as a table:
CREATE FUNCTION [dbo].[uftReadfileAsTable]
(
#Path VARCHAR(255),
#Filename VARCHAR(100)
)
RETURNS
#File TABLE
(
[LineNo] int identity(1,1),
line varchar(8000))
AS
BEGIN
DECLARE #objFileSystem int
,#objTextStream int,
#objErrorObject int,
#strErrorMessage Varchar(1000),
#Command varchar(1000),
#hr int,
#String VARCHAR(8000),
#YesOrNo INT
select #strErrorMessage='opening the File System Object'
EXECUTE #hr = sp_OACreate 'Scripting.FileSystemObject' , #objFileSystem OUT
if #HR=0 Select #objErrorObject=#objFileSystem, #strErrorMessage='Opening file "'+#path+'\'+#filename+'"',#command=#path+'\'+#filename
if #HR=0 execute #hr = sp_OAMethod #objFileSystem , 'OpenTextFile'
, #objTextStream OUT, #command,1,false,0--for reading, FormatASCII
WHILE #hr=0
BEGIN
if #HR=0 Select #objErrorObject=#objTextStream,
#strErrorMessage='finding out if there is more to read in "'+#filename+'"'
if #HR=0 execute #hr = sp_OAGetProperty #objTextStream, 'AtEndOfStream', #YesOrNo OUTPUT
IF #YesOrNo<>0 break
if #HR=0 Select #objErrorObject=#objTextStream,
#strErrorMessage='reading from the output file "'+#filename+'"'
if #HR=0 execute #hr = sp_OAMethod #objTextStream, 'Readline', #String OUTPUT
INSERT INTO #file(line) SELECT #String
END
if #HR=0 Select #objErrorObject=#objTextStream,
#strErrorMessage='closing the output file "'+#filename+'"'
if #HR=0 execute #hr = sp_OAMethod #objTextStream, 'Close'
if #hr<>0
begin
Declare
#Source varchar(255),
#Description Varchar(255),
#Helpfile Varchar(255),
#HelpID int
EXECUTE sp_OAGetErrorInfo #objErrorObject,
#source output,#Description output,#Helpfile output,#HelpID output
Select #strErrorMessage='Error whilst '
+coalesce(#strErrorMessage,'doing something')
+', '+coalesce(#Description,'')
insert into #File(line) select #strErrorMessage
end
EXECUTE sp_OADestroy #objTextStream
-- Fill the table variable with the rows for your result set
RETURN
END
Now you have a proc to convert your file to a table. You would still have to deal with the formatting of your delimiters so you could run something like this to populate [dbo].[file1]:
;WITH Split_Names (Value,Name, xmlname)
AS
(
SELECT
[LineNo],
line,
CONVERT(XML,'<Lines><line>'
+ REPLACE(line,'"|"', '</line><line>') + '</line></Lines>') AS xmlname
from [dbo].[uftReadfileAsTable]('\\kacl1tsp048\DEMO\file1.txt')
where line not like '"AGUSR1"%'
)
SELECT
Value,
RTRIM(REPLACE(xmlname.value('/Lines[1]/line[1]','varchar(100)'),'"', '')) AS AGUSR1,
RTRIM(REPLACE(xmlname.value('/Lines[1]/line[2]','varchar(100)'),'"', '')) AS AGUSR2
INTO [dbo].[file1]
FROM Split_Names
Hope that helps a little?!

Passing two values to a stored procedure?

I've written a stored procedure which is called on a link which provides a date value every time and #cg is NULL that time to filter the result on a particular date.
DECLARE #return_value int
EXEC #return_value = [dbo].[Get_Mydata]
#cg = NULL,
#tosearch = '15-05-2014'
SELECT 'Return Value' = #return_value
GO
And after first execution of the stored procedure, it gives some results and using same stored procedure.
I need to filter result by passing below parameter so this time #cg is NOT NULL.
DECLARE #return_value int
EXEC #return_value = [dbo].[Get_Mydata]
#cg = 'CUSTOMER NAME',
#tosearch = 'manish'
SELECT 'Return Value' = #return_value
GO
I'm not able to figure how should I create a dynamic where clause and add it to existing query as well as how to pass value to same parameter which already been passed as date.
More like first getting results for a particular date and then applying like filter on that result. I cannot pass different parameter that's Front end developers requirement.
This is my stored procedure and table data here. http://sqlfiddle.com/#!3/bb917
create proc Get_Mydata
(
#cg varchar(50),
#tosearch varchar(50)
)
as
begin
set nocount on
declare #sqlquery nvarchar(max)
set #sqlquery = N'select q_no, trandate, cust_name from testsp where CONVERT(Date, trandate, 103) = CONVERT(Date, ''' + #tosearch + ''' ,103)';
create table #temp1
(
q_no int,
trandate datetime,
cust_name varchar(50)
)
insert into #temp1(q_no, trandate, cust_name)
exec (#sqlquery)
select * from #temp1 as T;
set nocount off
end
What I have understood is that you want stored procedure to filter results on Date column when you pass null to #cg param and you want to filter results on Cust_name when you pass string 'Cust_Name' to your #Cg Param.
It should be fairly simple, But in any case you do not need a temp table to get the results back its just an over kill of a fairly simple query.
I would do something like this....
Pass the column name to #ColumnName Parameter, and your value to #tosearch parameter. It will build the query depending on what values you pass.
Make sure when you pass a value(Column Name) to #ColumnName.
create proc Get_Mydata
(
#ColumnName varchar(50),
#tosearch varchar(50)
)
as
begin
set nocount on;
declare #sqlquery nvarchar(max);
set #sqlquery = N' select q_no, trandate, cust_name '
+ N' from testsp '
+ N' where ' + QUOTENAME(#ColumnName) + N' = '
+ CASE
WHEN #ColumnName = 'trandate'
THEN N' CAST(#tosearch AS DATE)'
WHEN #ColumnName = 'cust_name'
THEN N' #tosearch'
ELSE N'' END
EXECUTE sp_executesql #sqlquery
,N'#tosearch varchar(50)'
,#tosearch
set nocount off;
end

Execute sp_executesql

Table called Emp contain id,name,lname,birthdate,address and salery .I want to select from emp.
Basic query : select * from emp
If pass the value for lname ,query : select * from emp where lname = 'fgfg' like this.
So I created following sp.
create Procedure Proc_selectEmp
(
#name varchar(10) = null,
#lname varchar(10) = null,
#id varchar(10) = null
)
as
begin
select * from Emp
where
(#name is null or name = #name)
and (#lname is null or lname = #lname)
and (#id is null or id = #id)
end
Like emp,there are 13 table having same column name.
So my tablenmae is also dynamic.That's why, I choose execute sp_executesql.Can I create like this
create Procedure Proc_selectEmp
(
#name varchar(10) = null,
#lname varchar(10) = null,
#id varchar(10) = null
#tableName varchar(30)
)
as
begin
declare #query nvarchar(1000)
set #query = #query +'select * from '+#tableName+'
where ('+#name+' is null or name = '+#name+')
and ('+#lname+' is null or lname = '+#lname+')
and ('+#id+' is null or id = '+#id+')
end'
execute sp_executesql #query
It will work, although is pretty smelly, given that it requires that it requires that table name is a variable and thus tables must have the same column definitions.
Also, rather than including the #param is null or column = #param, rather, leave out unnecessary filters entirely, which is easy to do since you are using dynamic sql. This will avoid the parameter sniffing problem.
Lastly, instead of appending the column filters into the string, rather use the parameterized overload of sp_executesql, which will protect your from SQL injection attacks, and handle the escaping of quotes etc for you. Unfortunately, #tablename can't be parameterized, but hopefully? this isn't a user or foreign-supplied variable (in which case you will need to do some more thinking about design and or validation techniques).
i.e.
declare #query nvarchar(max)
set #query = N'select * from ' + #tableName + N` where (1=1)`
if (#name is not null)
set #query = #query + N'and name = #name'
-- ... same for #id and #lname
exec sp_executesql #SQL,
N'#name varchar(10),
#lname varchar(10),
#id varchar(10)',
#name = #name,
#lname = #lname,
#id = #id
Edit
Re : securing un-parameterizable inputs like table or column names in dynamic sql - see this post here for ideas - use of QUOTENAME and white-listing column / table names are prominent.
Yes you can but you have to write
EXECUTE(#query)
Instead of
execute sp_executesql #query