Dynamic SQL stored procedure and datetime - sql

I have a simple query that I want to convert to dynamic SQL. I have 2 input parameters: a table and a datetime. And the output is the rowcount for the table and this specific datetime.
CREATE PROCEDURE [etl].[ROWCOUNT_TST2]
(#P_LOAD_TARGET nvarchar(250),
#P_LOAD_DATE DATETIME)
AS
BEGIN
DECLARE #SQL nvarchar(1000)
SET #SQL = 'SELECT COUNT(*) as Inserted FROM'+#P_LOAD_TARGET +' WHERE VALID_FROM ='''+ #P_LOAD_DATE+''' AND VALID_TO IS NULL'
EXEC (#SQL)
END;
GO
I tried different solutions. I tried the query with execute sp_executesql, I tied to add the the ''' before and after the #P_LOAD_DATE. I am probably missing something here.
When I execute the stored procedure with a table name and datetime like 2021-05-06 06:41:52.557, I get the following error:
Conversion failed when converting date and/or time from character string.
But why?
I even tried to add a conversion to datetime like this, but I still get the same error.
SET #SQL = 'SELECT COUNT(*) as Inserted FROM'+#P_LOAD_TARGET +' WHERE VALID_FROM = convert(datetime,'''+ #P_LOAD_DATE+''') AND VALID_TO IS NULL'
But when I execute SELECT convert(datetime, '2021-05-06 06:41:52.557') it works out fine. I am just confused right now and can't find the root of the problem.
Edit: valid_from is a datetime in the target table. So that is also not the reason for the problem

You need to properly and safely inject your dynamic object name and parametrise your parameter:
CREATE PROCEDURE [etl].[ROWCOUNT_TST2](#P_LOAD_SCHEMA sysname = N'dbo', --Always define your schema
#P_LOAD_TARGET sysname, --sysname is the data type for objects, a synonym of nvarchar(128) NOT NULL
#P_LOAD_DATE datetime) AS
BEGIN
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SET #SQL = N'SELECT COUNT(*) AS Inserted' + #CRLF +
N'FROM ' + QUOTENAME(#P_LOAD_SCHEMA) + N'.' + QUOTENAME(#P_LOAD_TARGET) + #CRLF +
N'WHERE VALID_FROM = #P_LOAD_DATE' + #CRLF +
N' AND VALID_TO IS NULL;';
--PRINT #SQL; --Your best friend.
EXEC sys.sp_executesql #SQL, N'#P_LOAD_DATE datetime', #P_LOAD_DATE;
END;

Related

Dynamic SQL not working for changing variable column name in SQL Server

I want to write a stored procedure which can be used to update IDs of owner name, backup contact, other contacts etc in a table. These IDs are to fetched from some other table. Instead of writing different stored procedure for all these contact information, I want to write a single dynamic SQL in which I can pass the column name as a variable name.
My stored procedure looks like:
CREATE PROCEDURE spUpdateUser
(#recordid [NVARCHAR](50),
#id [NVARCHAR](10),
#user [NVARCHAR](50))
AS
BEGIN
DECLARE #sql NVARCHAR(MAX);
SET NOCOUNT ON;
SET #sql = N'UPDATE [dbo].[table1]
SET'+ QUOTENAME(#user) + ' = (SELECT [dbo].[table2].User
FROM [dbo].[table2]
WHERE [dbo].[table2].id = ' + QUOTENAME(#id) + ')
WHERE record = ' + QUOTENAME(#recordid)
EXEC sp_executesql #sql;
END;
GO
After executing the query it runs without error but the user is not changed in table1.
What is missing in the procedure?
Don't inject your parameters, parametrise them:
CREATE PROCEDURE spUpdateUser
-- Add the parameters for the stored procedure here
( #recordid [nvarchar](50), --Are your IDs really an nvarchar?
#id [nvarchar](10), --Are your IDs really an nvarchar?
#user sysname --As this is an object, lets use the correct datatype
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
DECLARE #sql NVARCHAR(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SET NOCOUNT ON;
-- Insert statements for procedure here
SET #sql= N'UPDATE [dbo].[table1]' + #CRLF +
N'SET ' + QUOTENAME(#user) + N' = (SELECT [table2].User' + #CRLF + --3 part naming for columns is deprecated, don't use it
N' FROM [dbo].[table2]' + #CRLF +
N' WHERE [table2].id= #id)' + #CRLF + --3 part naming for columns is deprecated, don't use it
N'WHERE record = #recordid;';
--PRINT #SQL; --Your Best Friend
EXEC sp_executesql #sql, N'#id nvarchar(10), #recordid nvarchar(50)', #id, #recordid; --Assumes that IDs are an nvarchar again
END;
GO
Note I've left some comments in there for you to consume and review as well.

Invalid column name error when using QUOTENAME

I have several tables having the same structure. The tables are named by year that is 2001,2002 and so on. I am in need to search a column for a value in each table and get the count for each table.
I have created a stored procedure below but I keep getting an error
Invalid column 'lol'
This is the stored procedure used:
CREATE PROCEDURE [dbo].[CountSP]
#TableName NVARCHAR(128),
#SearchParam NVARCHAR(50),
#SearchInput NVARCHAR(200)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Sql NVARCHAR(MAX);
SET #Sql = N'SELECT COUNT('+QUOTENAME(#SearchParam)+') FROM ' + QUOTENAME(#TableName) +'WHERE'+QUOTENAME(#SearchParam)+'LIKE '+QUOTENAME(#SearchInput)+
+ N' SELECT * FROM '+QUOTENAME(#TableName)
EXECUTE sp_executesql #Sql
END
Executing it:
DECLARE #return_value INT
EXEC #return_value = [dbo].[CountSP]
#TableName = N'1999',
#SearchParam = N'USERDESC',
#SearchInput = N'lol'
SELECT 'Return Value' = #return_value
I don't know why you are using LIKE operator there while you don't use wildcards, also use SysName datatype directly for object names.
Create PROCEDURE [dbo].[CountSP]
(
#TableName SysName,
#SearchInput NVARCHAR(50),
#SearchParam SysName
)
AS
SET NOCOUNT ON;
DECLARE #SQL NVARCHAR(MAX) = N'SELECT COUNT(' +
QUOTENAME(#SearchParam) +
N') FROM ' +
QUOTENAME(#TableName) +
N' WHERE ' +
QUOTENAME(#SearchParam) +
N' = ' + --You can change it to LIKE if needed
QUOTENAME(#SearchInput, '''') +
N';';
-- There is no benifits of using LIKE operator there
EXEC sp_executesql #SQL;
Then you can call it as
EXEC [dbo].[CountSP] N'YourTableNameHere', N'SearchInput', N'ColumnName';
This is because it is currently translated to :
SELECT COUNT([USERDESC]) FROM [1999] WHERE [USERDESC] LIKE [lol]
this means that it is comparing the "USERDESC" column with the "lol" column but from what I am understanding lol isn't a column but a value? which means you should lose the QUOTENAME for that variable.
See the documentation here : https://learn.microsoft.com/en-us/sql/t-sql/functions/quotename-transact-sql?view=sql-server-2017
You need to pass your parameter #SearchInput as a parameter to sp_execute:
CREATE PROCEDURE [dbo].[CountSP] #TableName sysname, --This is effectively the same datatype (as sysname is a synonym for nvarchar(128))
#SearchParam sysname, --Have changed this one though
#SearchInput nvarchar(200)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Sql nvarchar(MAX);
SET #Sql = N'SELECT COUNT(' + QUOTENAME(#SearchParam) + N') FROM ' + QUOTENAME(#TableName) + N'WHERE' + QUOTENAME(#SearchParam) + N' LIKE #SearchInput;' + NCHAR(13) + NCHAR(10) +
N'SELECT * FROM ' + QUOTENAME(#TableName);
EXECUTE sp_executesql #SQL, N'#SearchInput nvarchar(200)', #SearchInput;
END;
QUOTENAME, by default, will quote a value in brackets ([]). It does accept a second parameter which can be used to define a different character (for example QUOTENAME(#Value,'()') will wrap the value in parentheses). For what you want though, you want to parametrise the value, not inject (a quoted) value.

Dynamic Sp_Executesql failing on datetime conversion error

I have a very simple dynamic SQL query that specifically needs to be called using sp_executesql with parameters. This query works fine in regular dynamic SQL, but fails when using sp_executesql on a conversion error.
I have tried many combinations of dynamic SQL, but none of them seem to work specifically for datetime conversions related to sp_executesql.
declare
#sql_nvarchar nvarchar(max),
#datetime datetime = GETDATE(),
#sqlparams nvarchar(max),
#tablename nvarchar(max) = 'SomeTableName'
Set #sql_nvarchar =
N'
Select *
from ' + #tablename + '
where Date > ''' + convert(nvarchar(23), #datetime, 101) + ''' '
Set #sqlparams =
N'
#datetime datetime,
#tablename nvarchar(max)
'
EXEC(#sql_nvarchar)
EXEC [sp_executesql] #sql_nvarchar,#sqlparams, #datetime, #tablename
The first exec correctly returns the desired query, the second EXEC throws an error: 'Error converting data type nvarchar(max) to datetime.'
You cannot parameterize an identifier, such as a table name. So, phrase this as:
Set #sql_nvarchar = N'
Select *
from ' + #tablename + '
where Date > #datetime
';
Set #sqlparams = N'#datetime datetime'
exec sp_executesql #sql_nvarchar, #sqlparams,
#datetime=#datetime

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.

Returning passed parameter as column in result set

I have the following stored procedure code working and want to add the passed parameter #tabname as a column in the result set.
CREATE PROCEDURE CountStar
#Tabname char(10)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQL varchar(250)
SELECT #SQL = 'SELECT ETL_LAST_UPD_DTTM, COUNT(*) FROM ls.' + QuoteName(#Tabname) +
'GROUP BY ETL_LAST_UPD_DTTM'
EXEC (#SQL)
SELECT #SQL = 'SELECT ETL_LAST_UPD_DTTM, COUNT(*) FROM ci.' + QuoteName(#Tabname) +
'GROUP BY ETL_LAST_UPD_DTTM'
EXEC (#SQL)
--COMMIT
END
GO
Currently this will return the last updated timestamp and the record count for the table being passed into the stored procedure for the 2 schemas identified. I want to add the #tabname to the result set as the first column followed by last updated timestamp and the record counts. This returns 2 result sets and each should look something like this for each returned result set.
Table_name Timestamp rec_cnt
--------------------------------------------------
CUSTOMERS 2015-09-24 13:10:01.1770000 378
I have tried a few things but can't get the syntax correct.
Thanks for any pointers.
Pat
CREATE PROCEDURE CountStar
#Tabname SYSNAME --<-- use appropriate data type for sql server objects
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQL nvarchar(max);
SELECT #SQL = N'SELECT #Tabname AS Table_name,ETL_LAST_UPD_DTTM, COUNT(*)
FROM ls.' + QuoteName(#Tabname) +
N' GROUP BY ETL_LAST_UPD_DTTM'
EXEC sp_executesql #SQL
,N'#Tabname SYSNAME'
,#Tabname
SELECT #SQL = N'SELECT #Tabname AS Table_name,ETL_LAST_UPD_DTTM, COUNT(*)
FROM ci.' + QuoteName(#Tabname) +
N' GROUP BY ETL_LAST_UPD_DTTM'
EXEC sp_executesql #SQL
,N'#Tabname SYSNAME'
,#Tabname
END
GO