Dynamic SQL Server stored procedure with table name as input param - sql

I have the following stored procedure
CREATE PROCEDURE [dbo].[Insert]
#Service varchar(max),
#TableName varchar(100),
#Query varchar(250),
#Results varchar(max),
#CreatedDate datetime,
#ExpirationDate datetime
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRANSACTION
DECLARE #SQL NVARCHAR(MAX), #ParmDefinition NVARCHAR(MAX)
DECLARE #q1 VARCHAR(MAX), #rez1 VARCHAR(MAX),
#date1 DATETIME, #date2 DATETIME
DECLARE #tablename VARCHAR(MAX) = #Service + '.' + #TableName
SET #SQL = N'if not EXISTS (select #q from ' + #tablename + ' where Query = #q) insert into ' + #tablename + ' values(#q, #rez, #date1, #date2)'
SET #ParmDefinition = N'#q varchar(max), #rez varchar(max),
#date1 datetime, #date2 datetime'
EXECUTE sp_executeSQL -- Dynamic T-SQL
#SQL,
#ParmDefinition,
#q = #Query,
#rez = #Results,
#date1= #CreatedDate,
#date2 = #ExpirationDate
COMMIT TRANSACTION
END
When I try to execute it, it doesn't insert anything, 0 rows
If I execute the code without the stored procedure, like a single query it inserts
Am I missing something?

There are a lot of things you have done in your question which doesnt make any sense to me, Why do you need to declare all these variables inside your procedure.
Yes true you are using parametrised query to protect yourself against sql injection attack, yet you left a hole by concatenating the object names (Table and database name), yes you will need to concatenate them but you can use QUOTENAME() function around them passed parameters and enforce sql server to put square brackets around these parameters and force sql server to treat them as nothing else but object names.
And Selecting a variable in IF EXISTS not make much sense. Select 1 which returns true if a row is found with matching criteria , and if no row is found it will simply insert a row.
Only declare variables that needs to declared, otherwise this make it look like a mess and difficult to debug. As they say Keep it simple :)
Also use appropriate data types for your parameters, #Service I believe is your database name why does it need to be a VARCHAR(MAX) data type, use the data type specific to store Sql Server Object names SYSNAME.
CREATE PROCEDURE [dbo].[Insert]
#Service SYSNAME,
#TableName SYSNAME,
#Query varchar(250),
#Results varchar(max),
#CreatedDate datetime,
#ExpirationDate datetime
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRANSACTION
DECLARE #SQL NVARCHAR(MAX), #ParmDefinition NVARCHAR(MAX)
SET #SQL = N'IF NOT EXISTS (select 1 from ' + QUOTENAME(#Service) + '.' + QUOTENAME(#TableName)
+ N' where Query = #q) '
+ N'insert into ' + QUOTENAME(#Service) + '.' + QUOTENAME(#TableName)
+ N' values(#q, #rez, #date1, #date2)'
SET #ParmDefinition = N'#q varchar(250), #rez varchar(max),
#date1 datetime, #date2 datetime'
EXECUTE sp_executeSQL #SQL
,#ParmDefinition
,#q = #Query
,#rez = #Results
,#date1= #CreatedDate
,#date2 = #ExpirationDate
COMMIT TRANSACTION
END

Related

sql assigned to a variable couldn't be printed/queried when executing a procedure

DECLARE #DROP_SQL NVARCHAR(MAX)
DECLARE #create_Sql NVARCHAR(MAX)
DECLARE #tablename2 NVARCHAR(MAX)
DECLARE #insert_sql NVARCHAR(MAX)
DECLARE #db_schema NVARCHAR(MAX)
SET #db_schema='GGW'
SET #tablename2='VIEW_LOG'
DECLARE #count INT
SET #count=1
DECLARE #StartTime DATETIME
DECLARE #EndTime DATETIME
SET #StartTime =GETDATE()
SET #EndTime=GETDATE()
DECLARE #schema_nm NVARCHAR(MAX)
SET #schema_nm='GW'
DECLARE #tablename NVARCHAR(MAX)
SET #tablename='x'
SET #drop_sql = 'DROP TABLE IF EXISTS [GW].['+#tablename2+']'
PRINT #drop_sql
EXEC(#drop_sql)
SET #create_Sql = 'CREATE TABLE [GGW].[VIEW_LOG] (
schema_name NVARCHAR(MAX),
view_name NVARCHAR(max)
,row_count INT
,error_msg NVARCHAR(max)
,query_start_time DATETIME
,query_end_time DATETIME
,execution_time_in_ms NVARCHAR(max)
);'
PRINT #create_Sql
EXEC(#create_Sql)
SET #insert_sql='INSERT INTO ['+#db_schema+'].['+#tablename2+'] SELECT '+ #schema_nm+','+#tablename+','+CONVERT(NVARCHAR,#count)+','+NULL+','+CONVERT(VARCHAR, #StartTime)+','+CONVERT(VARCHAR, #EndTime)+','+CONVERT(VARCHAR, DATEDIFF(ms,#StartTime,#EndTime))
PRINT(#insert_sql)
EXEC(#insert_sql)
I have created a table and trying to insert some records in to the table. the insert statement is assigned to a variable which is #insert_sql. i am trying to execute the sql that i have created and assigned to variable but it couldn't be printed or executed. am i missing something here?
There's so much wrong with the above... The comments under the question touch why the above fails, you concatenate NULL to your string which results in NULL, and I highlight some more below and in the comments of the SQL. Of course, you shouldn't be using that concatenation at all and should be parametrising your parameters.
There's a lot of inconsistencies in the SQL too; varying references to schema names, some hard coded others not. I've replaced any schema references with dbo to avoid incorrect assumptions.
I also inject your object names, where there is dynamic SQL, safely with QUOTENAME. '[' + #SomeVariable + ']' is NOT injection safe; especially when you give someone 2GB/~1 billion characters to "play" with. A right bracket (]) can just as easily be escaped as a single quote ('), and with 2GB of data, someone malicious can do literally what ever they want; your code is a security vulnerability waited to be exploited.
I make some assumptions on data types, and I've not tested this, but it should, at least, get you much closer to the desired behaviour.
DECLARE #DROP_SQL nvarchar(MAX),
#create_Sql nvarchar(MAX),
#tablename2 sysname,
#insert_sql nvarchar(MAX),
#db_schema sysname;
SET #db_schema = N'dbo';
SET #tablename2 = N'VIEW_LOG';
DECLARE #count int;
SET #count = 1;
DECLARE #StartTime datetime,
#EndTime datetime;
SET #StartTime = GETDATE();
SET #EndTime = GETDATE();
DECLARE #schema_nm sysname;
SET #schema_nm = 'dbo';
DECLARE #tablename sysname;
SET #tablename = 'x';
SET #DROP_SQL = 'DROP TABLE IF EXISTS ' + QUOTENAME(#db_schema) + N'.' + QUOTENAME(#tablename2) + N';';
--PRINT #drop_sql
EXEC sys.sp_executesql #SQL;
--This query wasn't dynamic, so there's no point making it dynamic.
--Though the data types are all wrong here, see the comments below, but I create it with more correct data types
/*
CREATE TABLE [dbo].[VIEW_LOG] (
schema_name NVARCHAR(MAX), --Should this really be sysname?
view_name NVARCHAR(max), --Should this really be sysname?
row_count INT,
error_msg NVARCHAR(max), --Is your error really likely to be 4,000 characters or more?
query_start_time DATETIME,
query_end_time DATETIME,
execution_time_in_ms NVARCHAR(max)); --If it's time in ms, why is this an nvarchar(MAX)? Stop (ab)using nvarchar(MAX)
*/
CREATE TABLE [dbo].[VIEW_LOG] (schema_name sysname,
view_name sysname,
row_count int,
error_msg nvarchar(4000),
query_start_time datetime,
query_end_time datetime,
execution_time_in_ms int);
--PRINT #db_schema
--PRINT #tablename2
DECLARE #CRLF nchar(2) = NCHAR(13) + NCHAR(10); --Let's add some whitespace and line breaks to this query
SET #insert_sql = N'INSERT INTO ' + QUOTENAME(#db_schema) + N'.' + QUOTENAME(#tablename2) + N' (schema_name, view_name, row_count, error_msg, query_start_time, query_end_time, execution_time_in_ms)' + #CRLF + N'VALUES(#schema_nm,#tablename,#count,NULL,#StartTime,#EndTime,DATEDIFF(ms,#StartTime,#EndTime));';
--PRINT(#insert_sql)
EXEC sys.sp_executesql #SQL,
N'#schema_nm sysname,#tablename sysname,#count int,#StartTime datetime,#EndTime datetime',
#schema_nm,
#tablename,
#count,
#StartTime,
#EndTime;

Can I create a stored procedure to select a table from a database with the database name as a variable? [duplicate]

I'm looking to pass my database name as a parameter to the stored procedure, and I'm looking to use it in the where condition to set the database of the stored procedure. But I get an error:
Incorrect syntax near '.'
Sample Code
Create proc [dbo].[stored_procedure_one]
#variable1 int,
#dbname varchar(10)
as
begin
select *
from #dbname..table_name
End
Can someone suggest me how to solve this?
You will need to use dynamic sql for this something like this.....
Create proc [dbo].[stored_procedure_one]
#variable1 int,
#dbname SYSNAME --<-- use appropriate data type for object names
as
begin
DECLARE #Sql NVARCHAR(MAX);
SET #Sql = N' select * from ' + QUOTENAME(#dbname) + N'..table_name'
Exec sp_executesql #Sql
End
Also use QUOTENAME() function to protect yourself against possible sql-injection attack.
Just to offer an alternative, it's fun to note that EXEC can take a string as the thing to execute, so for example:
DECLARE #sp nvarchar(255) = N'sys.sp_who2';
EXEC #sp;
It can also take parameters, e.g.
DECLARE #sp nvarchar(255) = N'sys.sp_who2';
EXEC #sp 'active';
So we can dynamically build the context where we run a command by using:
DECLARE #dbname sysname = N'tempdb';
DECLARE #context nvarchar(1000) = QUOTENAME(#dbname)
+ N'.sys.sp_executesql';
DECLARE #sql nvarchar(max) = N'SELECT DB_NAME();';
EXEC #context #sql;
And you can pass parameters, too:
DECLARE #dbname sysname = N'tempdb';
DECLARE #context nvarchar(1000) = QUOTENAME(#dbname)
+ N'.sys.sp_executesql';
DECLARE #sql nvarchar(max) = N'SELECT DB_NAME(), #x;';
EXEC #context #sql, N'#x int', 5;
This approach really simplifies things like concatenating the database name all over the place, avoiding db-specific functions like object_name, and ensures that your entire command runs in that other database. You can also do it across linked servers, e.g.:
DECLARE #server sysname = N'linked_server';
DECLARE #dbname sysname = N'tempdb';
DECLARE #context nvarchar(1000) = QUOTENAME(#server)
+ N'.' + QUOTENAME(#dbname)
+ N'.sys.sp_executesql';
...

Dynamic Stored Procedure with View name and Date as parameters

I created a stored procedure which takes a view name and date as parameters and checks if there is record for that date in the view. However, I get the following error
'Operand type clash: date is incompatible with int'.
I am hoping that if the record exists that 1 will be returned else 0 will be returned and I can use that to make a decision in another stored procedure.
The code is listed below
CREATE PROCEDURE [dbo].[usr_RecordExist]
-- Add the parameters for the stored procedure here
#ViewName SYSNAME,
#TransDate Date
--<#Param2, sysname, #p2> <Datatype_For_Param2, , int> = <Default_Value_For_Param2, , 0>
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #DATEVARCHAR nvarchar(4000);
SET #DATEVARCHAR = CONVERT(NVARCHAR, #TransDate, 103);
-- Insert statements for procedure here
DECLARE #SQLCommand NVARCHAR(MAX) =
N'SELECT COUNT(SYMBOL) FROM ' + QUOTENAME(#ViewName) + 'WHERE TRANSDATE = ' + '' + #DATEVARCHAR + '';
EXECUTE [dbo].[sp_executesql]
#sqlCommand;
END
The expression + '' does nothing, use + '''' to add a single quote.
... + '''' + #DATEVARCHAR + '''';
You are using the right tools but in the wrong way, You should not concatenate parameters but pass them as parameters to the system stored procedure sp_executesql as shown below:
CREATE PROCEDURE [dbo].[usr_RecordExist]
#ViewName SYSNAME,
#TransDate Date
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQLCommand NVARCHAR(MAX);
SET #SQLCommand = N'SELECT COUNT(SYMBOL) FROM ' + QUOTENAME(#ViewName)
+ N' WHERE TRANSDATE = #TransDate';
EXECUTE [dbo].[sp_executesql] #sqlCommand
,N'#TransDate Date'
,#TransDate
END
Edit
To get the count in an output parameter you would do the following:
CREATE PROCEDURE [dbo].[usr_RecordExist]
#ViewName SYSNAME,
#TransDate Date,
#Count INT OUTPUT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQLCommand NVARCHAR(MAX);
SET #SQLCommand = N'SELECT #Count = COUNT(SYMBOL) FROM ' + QUOTENAME(#ViewName)
+ N' WHERE TRANSDATE = #TransDate';
EXECUTE [dbo].[sp_executesql] #sqlCommand
,N'#TransDate Date, #Count INT OUTPUT'
,#TransDate
,#Count OUTPUT
END
Since you used QUOTENAME() for ViewName why not QUOTENAME(#DATEVARCHAR, '''') or QUOTENAME(#DATEVARCHAR, CHAR(39))
Cosmin got it. Although I also noticed you set #DATEVARCHAR to NVARCHAR(4000) even though convert(NVARCHAR without a length defaults to 30.

Db Name as parameter in stored procedure SQL Server

I'm looking to pass my database name as a parameter to the stored procedure, and I'm looking to use it in the where condition to set the database of the stored procedure. But I get an error:
Incorrect syntax near '.'
Sample Code
Create proc [dbo].[stored_procedure_one]
#variable1 int,
#dbname varchar(10)
as
begin
select *
from #dbname..table_name
End
Can someone suggest me how to solve this?
You will need to use dynamic sql for this something like this.....
Create proc [dbo].[stored_procedure_one]
#variable1 int,
#dbname SYSNAME --<-- use appropriate data type for object names
as
begin
DECLARE #Sql NVARCHAR(MAX);
SET #Sql = N' select * from ' + QUOTENAME(#dbname) + N'..table_name'
Exec sp_executesql #Sql
End
Also use QUOTENAME() function to protect yourself against possible sql-injection attack.
Just to offer an alternative, it's fun to note that EXEC can take a string as the thing to execute, so for example:
DECLARE #sp nvarchar(255) = N'sys.sp_who2';
EXEC #sp;
It can also take parameters, e.g.
DECLARE #sp nvarchar(255) = N'sys.sp_who2';
EXEC #sp 'active';
So we can dynamically build the context where we run a command by using:
DECLARE #dbname sysname = N'tempdb';
DECLARE #context nvarchar(1000) = QUOTENAME(#dbname)
+ N'.sys.sp_executesql';
DECLARE #sql nvarchar(max) = N'SELECT DB_NAME();';
EXEC #context #sql;
And you can pass parameters, too:
DECLARE #dbname sysname = N'tempdb';
DECLARE #context nvarchar(1000) = QUOTENAME(#dbname)
+ N'.sys.sp_executesql';
DECLARE #sql nvarchar(max) = N'SELECT DB_NAME(), #x;';
EXEC #context #sql, N'#x int', 5;
This approach really simplifies things like concatenating the database name all over the place, avoiding db-specific functions like object_name, and ensures that your entire command runs in that other database. You can also do it across linked servers, e.g.:
DECLARE #server sysname = N'linked_server';
DECLARE #dbname sysname = N'tempdb';
DECLARE #context nvarchar(1000) = QUOTENAME(#server)
+ N'.' + QUOTENAME(#dbname)
+ N'.sys.sp_executesql';
...

Compare date in stored procedure in an exec statement

I have a stored procedure that looks like this:
create stored procedure aaa
#columnName nvarchar(10),
#comparisonParam nvarchar(10),
#val nvarchar(100)
as
declare #date date
set #date = convert(#val, date)
exec('select * from Sheep where ' + #columnName + #comparisonParam + #date )
When actually the query is supposed to be like this:
select * from Sheep where birth_date = 12-12-2000
When I run the procedure it doesn't work with date value, but with string and int it works.
The date value must be quoted.
On a side note, I'd warn against doing this. If you need to build up dynamic sql you need to consider the risks such as: sql injection attacks, bad syntax, invalid semantics etc.
Consider using an existing component to build the query. A few examples:
.NET LINQ (to SQL/Entities) http://msdn.microsoft.com/en-us/library/bb397926.aspx
.NET SqlCommandBuilder http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlcommandbuilder.aspx
See Best way of constructing dynamic sql queries in C#/.NET3.5?
Your date literal needs to be surrounded in single quotes (I use CHAR(39) usually since it is easier to read and doesn't require escaping). Otherwise you are saying:
WHERE birth_date = (12) - (12) - (2000)
Which resolves to:
WHERE birth_date = -2000
Which resolves to DATEADD(DAY, -2000, '1900-01-01') or:
WHERE birth_date = '1894-07-11'
This is probably not going to yield the results you want.
With typical SQL injection warnings in place of course, and assuming that #columnName is always a string or date/time column, here is how I would re-write your stored procedure (though I would probably try to avoid the dynamic SQL altogether if I could).
ALTER PROCEDURE dbo.aaa
#columnName NVARCHAR(10),
#comparisonParam NVARCHAR(10),
#val NVARCHAR(100)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'SELECT * FROM dbo.Sheep WHERE '
+ QUOTENAME(#columnName) + #comparisonParam + CHAR(39)
+ REPLACE(#val, CHAR(39), CHAR(39) + CHAR(39))
+ CHAR(39);
EXEC sp_executesql #sql;
END
GO
In order to thwart potential issues you may want to add validation for columns and data types, and ensure that the operation is one you expect. e.g.
CREATE PROCEDURE dbo.bbb
#columnName NVARCHAR(10),
#comparisonParam NVARCHAR(10),
#val NVARCHAR(100)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #delimiter CHAR(1);
SELECT #delimiter = CASE
WHEN [system_type_id] IN
(104,48,52,56,127,59,60,62,106,108,122) THEN '' -- numeric
WHEN [system_type_id] IN
(35,40,41,42,43,58,61,99,167,175,231,239) THEN CHAR(39) -- string
END FROM sys.columns WHERE [object_id] = OBJECT_ID(N'dbo.Sheep')
AND name = #columnName;
IF #delimiter IS NULL
BEGIN
RAISERROR('Column ''%s'' was not found or an unexpected data type.', 11, 1,
#columnName);
RETURN;
END
IF #comparisonParam NOT IN (N'=', N'>=', N'<=', N'<', N'>', N'LIKE')
BEGIN
RAISERROR('Comparison param ''%s'' was not valid.', 11, 1, #comparisonParam);
RETURN;
END
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'SELECT * FROM dbo.Sheep WHERE '
+ QUOTENAME(#columnName) + ' ' + #comparisonParam + ' '
+ #delimiter + REPLACE(#val, CHAR(39), CHAR(39) + CHAR(39))
+ #delimiter;
EXEC sp_executesql #sql;
END
GO
Now make sure you use an unambiguous date format for your string literals. 12-12-2000 is not a good choice. 20001212 is much better.
There are possibly some ways to do this without dynamic SQL - I gave a very simplified answer here. This may be feasible depending on the data types, the number of potential columns, and the number of operations you want to support.
create stored procedure aaa
#columnName nvarchar(10),
#comparisonParam nvarchar(10),
#val nvarchar(100)
as
declare #date date
set #date = convert(#val, date)
exec('select * from Sheep where ' + #columnName + #comparisonParam + #date )
Build your dynamic SQL using a typed date parameter. Use sp_executesql which allows to pass parameter definitions and parameter values to the embedded SQL:
create procedure aaa
#columnName nvarchar(10),
#comparisonParam nvarchar(10),
#val nvarchar(100)
as
declare #date date, #sql nvarchar(max);
set #date = convert(#val, date);
-- Note how #date is a *variable* in the generated SQL:
set #sql =N'select * from Sheep where ' +
quotename(#columnName) + #comparisonParam + N'#date';
-- Use sp_executesql and define the type and value of the variable
exec sp_executesql #sql, N'#date date', #date;
You need to create table valued function for this rather than creating a stored procedure.
You can use any table valued function like
SELECT * from dbo.CallMyFunction(parameter1, parameter2
eg.
CREATE FUNCTION Sales.ufn_SalesByStore (#storeid int)
RETURNS TABLE
AS
RETURN
(
SELECT P.ProductID, P.Name, SUM(SD.LineTotal) AS 'Total'
FROM Production.Product AS P
JOIN Sales.SalesOrderDetail AS SD ON SD.ProductID = P.ProductID
JOIN Sales.SalesOrderHeader AS SH ON SH.SalesOrderID = SD.SalesOrderID
JOIN Sales.Customer AS C ON SH.CustomerID = C.CustomerID
WHERE C.StoreID = #storeid
GROUP BY P.ProductID, P.Name
);
GO
See this for reference http://msdn.microsoft.com/en-us/library/ms191165(v=sql.105).aspx
EDIT
Instead of using dynamic sql try giving a thought on
SELECT * FROM
FROM [dbo].[Person]
WHERE ([PersonID] = #PersonID
OR #AreaID IS NULL
)
AND (([Code] BETWEEN #Code AND CHAR(255))
OR #Code IS NULL
)
AND (([Name] BETWEEN #Name AND CHAR(255))
OR #Name IS NULL
)
AND (([Notes] BETWEEN #Notes AND CHAR(255))
OR #Notes IS NULL
)