EXEC sp_executesql with a datetime parameter? - sql

I'm using EXEC sp_executesql for a dynamic query in SQL Server 2016, and am stumbling on when a user wants to pass in a year. I have a datetime field called tkemdate and it is stored as a datetime field in SQL.
Although SQL stores it as datetime, the user only passes in a year parameter (2020, 2019, 2018, etc). How do I get the query accept just the year?
Here's my stored procedure, with the datetime param.
(
#tkemdate datetime
)
AS
BEGIN
Declare #SQL NVARCHAR(MAX)
Set #SQL = 'SELECT timekeep.tkinit, timekeep.tkfirst, timekeep.tklast,
year(timekeep.tkemdate) as tkemdate
FROM abc123.timekeep'
WHERE 1 = 1
IF #tkemdate IS NOT NULL
Select #SQL = #SQL + 'AND ([tkemdate] = #tkemdate)'
EXEC sp_executesql #SQL, N'#tkemdate datetime', #tkemdate
END

You can use something like this:
IF #year IS NOT NULL
Select #SQL = #SQL + ' AND (YEAR([tkemdate]) = #year)'
EXEC sp_executesql #SQL, N'#year int', #year=#year;
It is not clear where #year comes from, but you say that the user is passing in the year.
If you only want to use the year from #tkemdate then:
IF #tkemdate IS NOT NULL
Select #SQL = #SQL + ' AND (YEAR([tkemdate]) = YEAR(#tkemdate))';
EXEC sp_executesql #SQL, N'##tkemdate datetime', ##tkemdate=##tkemdate;

It could be also something like that - where Your procedure has an integer parameter representing year.
We could also add some validation to it, for example if #year is less than 1000 or greater than 2500.
CREATE PROCEDURE abc123.get_timekeep_records (
#year int
)
AS
BEGIN
SET NOCOUNT ON; -- I usually add this because of a PHP driver
DECLARE #SQL NVARCHAR(MAX)
SET #SQL = 'SELECT timekeep.tkinit, timekeep.tkfirst, timekeep.tklast,
YEAR(timekeep.tkemdate) as [tkemdate]
FROM abc123.timekeep
WHERE 1 = 1 '
-- Validation
IF #year < 1000 OR #year > 2500
RAISERROR('Invalid year: %i', 16, 1, #year)
IF #year IS NOT NULL
SELECT #SQL = #SQL + 'AND (YEAR(timekeep.tkemdate) = #year)'
EXEC sp_executesql #SQL, N'#year int', #year;
END

Related

Dynamic table name and variable name in query in SQL Server

I am trying to make a query that I can run from Python with dynamic table name and date. In the process of this, I have tried the following query in SSMS, but it is producing an error message. How can I use variables for table name and a date, and get the query to work?
DECLARE #table_name VARCHAR(50)='table_name';
DECLARE #valid_to datetime = getdate();
EXEC('UPDATE '+ #table_name + '
SET valid_flag = 0,
valid_to = '+ #valid_to +'
where valid_flag=1')
I get the following error message:
Msg 102, Level 15, State 1, Line 3
Incorrect syntax near '10'
Use sp_executesql and parameters:
DECLARE #table_name VARCHAR(50) = 'table_name';
DECLARE #valid_to datetime = getdate();
DECLARE #sql NVARCHAR(max) = N'
UPDATE '+ #table_name + N'
SET valid_flag = 0,
valid_to = #valid_to
WHERE valid_flag = 1
';
EXEC sp_executesql #sql, N'#valid_to datetime', #valid_to=#valid_to;
EDIT:
As recommended by Larnu a comment:
DECLARE #table_name sysname = 'table_name';
DECLARE #valid_to datetime = getdate();
DECLARE #sql NVARCHAR(max) = N'
UPDATE '+ QUOTENAME(#table_name) + N'
SET valid_flag = 0,
valid_to = #valid_to
WHERE valid_flag = 1
';
EXEC sp_executesql #sql, N'#valid_to datetime', #valid_to=#valid_to;

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.

How to Set a variable using OPENQUERY in SQL Server

I am trying to read data from a table. This table have a list of table name.
for each row of the data set I want to run a couple of queries to pull data and insert it into a temporary table.
Here is What I have done
DECLARE #campName varchar(255);
DECLARE #sqlCommand varchar(1000);
DECLARE #sqlCommandMySQL varchar(1000);
DECLARE #LastRun varchar(60);
DECLARE #OPENQUERY varchar(1000);
DECLARE MY_CURSOR CURSOR
LOCAL STATIC READ_ONLY FORWARD_ONLY
FOR
SELECT LTRIM(RTRIM(CallListName)) AS CallListName
FROM [SMSQL1].[RDI_System].[dbo].[Campaigns]
WHERE dialer_campaign = 1 AND i3Server ='I3New' AND ClientID = 111 AND (EndDate IS NULL OR EndDate >= getdate() - 7)
OPEN MY_CURSOR
FETCH NEXT FROM MY_CURSOR INTO #campName
WHILE ##FETCH_STATUS = 0
BEGIN
--SET #LinkedServer = 'GUARDIAN';
SET #OPENQUERY = 'SELECT #LastRun = lr FROM OPENQUERY(GUARDIAN,''';
SET #sqlCommandMySQL = 'SELECT IFNULL(MAX(lastRun), DATE_SUB(NOW(), INTERVAL 7 DAY) ) AS lr
FROM guardian_dynamo.runtimes_i3
WHERE CampaignListName = "'+#campName+'" '')';
print #OPENQUERY + #sqlCommandMySQL;
EXEC(#OPENQUERY + #sqlCommandMySQL);
SET #sqlCommand = ' INSERT INTO #finalList(Attemtps, CAMPAIGNNAME, FINISHCODE, CALLDATE, AGENTID, RDINotes, PHONE, MERCHANTAccount)
SELECT ATTEMPTS, CAMPAIGNNAME, FINISHCODE, CALLDATE, AGENTID, RDINotes, PHONE, MERCHANTAccount
FROM [I3_IC4].[dbo].['+ #campName +']
WHERE CALLDATE > '''+#LastRun+''' AND ISNULL(status, ''C'') IN (''U'', ''E'', ''A'', ''F'') ';
EXEC (#sqlCommand);
FETCH NEXT FROM MY_CURSOR INTO #campName
END
CLOSE MY_CURSOR
DEALLOCATE MY_CURSOR;
every time I run this query I get this error
Msg 137, Level 15, State 1, Line 1
Must declare the scalar variable "#LastRun".
I am not sure why since I am declaring this variable on the top as you can see in my code above.
the took the output of print #OPENQUERY + #sqlCommandMySQL; and executed that manually. It worked with no issue and the variable #LastRun will have a datetime value as it should.
You need to use sp_executesql to execute the dynamic query which helps you output the variable(#LastRun)
Declare #OPENQUERY Nvarchar(max), #sqlCommandMySQL Nvarchar(max), #OPENQUERYFINAL Nvarchar(max)
....
SET #OPENQUERY = 'SELECT #LastRun = lr FROM OPENQUERY(GUARDIAN,''';
SET #sqlCommandMySQL = 'SELECT IFNULL(MAX(lastRun), DATE_SUB(NOW(), INTERVAL 7 DAY) ) AS lr
FROM guardian_dynamo.runtimes_i3
WHERE CampaignListName = "'+#campName+'" '')';
--print #OPENQUERY + #sqlCommandMySQL;
SET #OPENQUERYFINAL = #OPENQUERY + #sqlCommandMySQL;
EXEC sp_executesql #OPENQUERYFINAL,
N'#LastRun varchar(10) OUTPUT',
#LastRun output
Demo
DECLARE #str VARCHAR(10),
#sql NVARCHAR(max)
SET #sql= 'select #str=1 '
EXEC Sp_executesql
#sql,
N'#str varchar(10) OUTPUT',
#str output
PRINT #str

How to return the rowcount of a dynamic sql query in SP?

I would like to return the rowcount of a dynamic sql query using linq similar to mentioned here:
http://weblogs.asp.net/scottgu/archive/2007/08/16/linq-to-sql-part-6-retrieving-data-using-stored-procedures.aspx
I'm using dynamic sql to create the where clause and to implement paging on the result set, The rowcount I want to return is the total number of records that meet the where condition.
My SQL that is causing me problems:
-- get row count
SET #SQL = '#TotalRowCount = SELECT COUNT(*) as TotalRowCount'
SET #SQL = #SQL + #WHERE
IF (LEN(#SUBWHERE) > 0)
BEGIN
SET #SQL = #SQL + #SUBWHERE
END
SET #SQL = #SQL + ')) '
exec sp_executesql #SQL
END
(I need this to be the output param #TotalRowCount in the param list here):
ALTER PROCEDURE [dbo].[_tournament_GetTournamentsByConveners]
(
#LastName varchar(100) = null ,
#Username varchar(256) = null ,
#Email varchar(100) = null ,
#IsWildcard bit = null,
#PageIndex int ,
#PageSize int,
#TotalRowCount int output
)
AS
This is by design.
The scope of the #TotalRowCount in the dynamic SQL is different to the scope of #TotalRowCount declared in the stored procedure. That is, the dynamic SQL has it's own scope.
If you insist on using dynamic SQL, do this to add the total rows to the record set that is returned
SELECT col1, col2,
COUNT(*) OVER () AS TotalRows
FROM ...
Otherwise we only have partial code to offer any suggestions for improvement. You appear to have LINQ to call stored procs with execute dynamic SQL. This is too convoluted.
You can declare output parameters with sp_executesql. So to get your result alter the code as shown below.
Always be careful when concatenating SQL code like this as it is extremely vulnerable to SQL injection.
DECLARE #SQL nvarchar(4000)
SET #SQL = N'SELECT #TotalRowCount = COUNT(*) as TotalRowCount'
SET #SQL = #SQL + #WHERE
IF (LEN(#SUBWHERE) > 0)
BEGIN
SET #SQL = #SQL + #SUBWHERE
END
SET #SQL = #SQL + N')) '
exec sp_executesql #SQL, N'#TotalRowCount int output', #TotalRowCount output
you can also express this more compact as:
DECLARE #SQL nvarchar(4000)
SET #SQL = N'SELECT #TotalRowCount = COUNT(*) as TotalRowCount' + #WHERE + ISNULL(#SUBWHERE, N'') + N'))'
exec sp_executesql #SQL, N'#TotalRowCount int output', #TotalRowCount output

Selecting from a table where the name is passed as a variable

I am trying to write a simple stored proc which takes three arguments 'database name one', 'database name two' and 'table name'. The sql will then perform a row count for the defined table in each database and store it.
Working on it piecemeal I have hit the first problem in that you can't do
select * from #tablename
I know you can use dynamic sql with the exec command but this is not ideal as I can't return values.
The following example looks like it should work but doesn't.
declare #tablename as nvarchar(500)
declare #sqlstring as nvarchar(500)
declare #parmdefinition as nvarchar(500)
declare #numrows as bigint
set #tablename = N'dummy_customer'
set #parmdefinition = N'#tablenameIN nvarchar(500), #numrowsOUT as bigint OUTPUT'
select #sqlstring = 'select #numrowsOUT = count(*) from #tablenameIN'
select #sqlstring
exec sp_executesql #sqlstring, #parmdefinition, #tablenameIN = #tablename, #numrowsOUT = #numrows OUTPUT
select #numrows
The error message given is
Msg 1087, Level 16, State 1, Line 1
Must declare the table variable "#tablenameIN".
Currently using SQL Server 2008 SP2.
Edit:
We're doing this because we are doing a migration and the customer wants a report which shows the row count for each table in the source and destination database. As there are many tables being able to use sp_MSForEachTable to call the stored proc seems ideal.
Edit:
The final solution for future reference is
declare #tablename as nvarchar(500)
declare #sqlstring as nvarchar(500)
declare #parmdefinition as nvarchar(500)
declare #numrows as bigint
set #tablename = N'dummy_customers'
set #parmdefinition = N'#tablename nvarchar(500), #numrowsOUT as bigint OUTPUT'
select #sqlstring = 'select #numrowsOUT = count(*) from ' + quotename(#tablename)
exec sp_executesql #sqlstring, #parmdefinition, #tablename = #tablename, #numrowsOUT = #numrows OUTPUT
select #numrows
You'd have to use dynamic sql, and concatenate the table name into the SQL string to then execute via sp_executsql:
select #sqlstring = 'select #numrowsOUT = count(*) from ' + QUOTENAME(#tablename)
EXECUTE sp_executesql ....