dynamic SQL : use variable declared in a string, outside of the string - sql

I have a stored procedure which should calculate one's age.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
alter PROCEDURE [dbo].[ageTest]
(
#curDate date
)
AS
BEGIN
SET NOCOUNT ON;
declare #sql nvarchar(max)
declare #params nvarchar (1000)
declare #age date
set #params = N' #curDate date , #age date output'
set #sql = '
declare #dif float
declare #ageRound int
declare #theAge varchar (100)
set #age = ''19890406''
set #theAge =(select (datediff (mm, #age , getdate())))
set #dif = #theAge % 12
set #ageRound = cast (#theAge as float)/12
select #ageRound as Years, #dif as months
'
set #sql = replace (#sql, '19890406', #curDate)
execute sp_executesql #sql, #params, #curDate, #age output
end
execute [dbo].[ageTest] '19511214'
What I want to obtain is two columns:
Years Months
63 10
Right now it looks like this:
The problem is it loops. I should probably remove the select from #sql and put it outside, then I have the declaration problem. Thoughts?
Edit: not a duplicate

IF you're really only a beginner.
First of all, you should always be using TRY...CATCH block in your procedures.
Here's a quick rewrite of what you've done in your code:
-- =============================================
-- Procedure Name : dbo.ageTest
-- Usage Example : EXECUTE [dbo].[ageTest] '19511214';
-- =============================================
ALTER PROCEDURE [dbo].[ageTest]
(
#curDate DATE
)
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
SELECT DATEDIFF(MM, #curDate, CURRENT_TIMESTAMP) / 12 AS Years
, DATEDIFF(MM, #curDate, CURRENT_TIMESTAMP) % 12 AS Months;
END TRY
BEGIN CATCH
SELECT ERROR_NUMBER(), ERROR_MESSAGE();
END CATCH
END
It shows how you should be using parameters in SP.

Related

EXEC sp_executesql with a datetime parameter?

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

Create a procedure sorting by time imported from another table?

I'm trying to use two datetime values to sort in my query but I cant get it work.
It just complain on datetime formatting.
I got this error message when I try to execute following command
exec GetSingleAvailability #MachineID = 1002
Conversion failed when converting date and/or time from character
string.
Heres my procedure:
ALTER PROCEDURE [dbo].[GetSingleAvailability]
-- Add the parameters for the stored procedure here
#MachineID int
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
DECLARE #Availabilitytime datetime
DECLARE #TimeNow datetime
set #Availabilitytime = (SELECT Availabilitytime FROM dbo.machines Where MachineID = #MachineID)
set #TimeNow = (SELECT GETDATE() AS time)
DECLARE #query nvarchar(150)
set #query = 'SELECT AVG(effectively) FROM ['+ Convert(nvarchar, #MachineID) +'] WHERE time between '+ #Availabilitytime +' and '+ #TimeNow +''
EXECUTE sp_executesql #query
END
Your procedure may need to be fixed a bit:
ALTER Proc GetSingleAvailability (#MachineID int) as
begin
set nocount on
declare #Availabilitytime datetime, #TimeNow datetime, #query nvarchar(150)
select
#Availabilitytime =
(select AvailabilityTime from machines where MachineID = #MachineID)
, #TimeNow = getDate()
, #query = N'select avg(effectively) from '
+ quoteName(convert(nvarchar, #MachineID))
+ N' where [time] between '
+ quoteName(convert(nvarchar, #Availabilitytime, 120), nchar(39))
+ N' and '
+ quoteName(convert(nvarchar, #TimeNow, 120), nchar(39))
execute sp_executesql #query
end
Change 120 to 8 if you only need the time (hh:mm:ss) part.
Found a solution like this:
USE [MITestdb]
GO
/****** Object: StoredProcedure [dbo].[GetSingleAvailability] Script Date: 2020-09-11 20:10:47 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: <Author,,Name>
-- Create date: <Create Date,,>
-- Description: <Description,,>
-- =============================================
ALTER PROCEDURE [dbo].[GetSingleAvailability]
-- Add the parameters for the stored procedure here
#MachineID int
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
DECLARE #Availabilitytime datetime
DECLARE #TimeNow datetime
set #Availabilitytime = (SELECT Availabilitytime FROM dbo.machines Where MachineID = #MachineID)
set #TimeNow = (SELECT GETDATE())
DECLARE #query nvarchar(150)
set #query = 'SELECT AVG(effectively) FROM ['+ Convert(nvarchar, #MachineID) +'] WHERE time between #qAvailabilitytime and #qTimeNow'
EXECUTE sp_executesql #query,
N'#qAvailabilitytime datetime, #qTimeNow datetime',
#qAvailabilitytime = #Availabilitytime, #qTimeNow = #TimeNow
END

Renaming table and view from a different database, works in editor but not from stored procedure

I have to run a monthly job in SQL Server to rename a table and a view in a variety of databases. The database names are stored in a table and this procedure loops through them. The table names change monthly, so I am concatenating the table names based on the current date.
This works well to creating the commands.
If I change my EXEC to PRINT and paste the results into a new query window it works great.
BW_Test.dbo.sp_rename 'BW_Test_DataLog_2018_05','BW_Test_DataLog_2018_06';
BW_Test.dbo.sp_rename 'BW_Test_DataLog','BW_Test_DataLog_2018_05';
However when I run the stored procedure it fails with the following error:
ErrorNumber: 2812 ErrorMessage: Could not find stored procedure 'BW_Test.dbo.sp_rename 'BW_Test_DataLog_2018_05','BW_Test_DataLog_2018_06';'
Here is the stored procedure, thanks in advance!
BEGIN
SET NOCOUNT ON;
-- Find month and year to concatenate with table names
DECLARE #RighNow DATE = GETDATE();
DECLARE #LastMonth DATE = DATEADD(MONTH, -1, GETDATE());
DECLARE #RenameView NVARCHAR(500);
DECLARE #RenameTable NVARCHAR(500);
DECLARE #LastMonthsName NVARCHAR(50);
DECLARE #ThisMonthsName NVARCHAR(50);
DECLARE #COUNTER INT = 0;
DECLARE #MAX INT = (SELECT COUNT(*) FROM DatabaseNames)
DECLARE #Machine VARCHAR(50);
--Start Loop here
WHILE #COUNTER < #MAX
BEGIN
SET #Machine = (SELECT DatabaseName
FROM
(SELECT
(ROW_NUMBER() OVER (ORDER BY (SELECT NULL))) [index],
DatabaseName
FROM
DatabaseNames) R
ORDER BY R.[index]
OFFSET #COUNTER ROWS FETCH NEXT 1 ROWS ONLY);
SET #LastMonthsName = CONCAT(#Machine, '_DataLog', '_', YEAR(#LastMonth), '_', FORMAT(MONTH(#LastMonth), '00'));
SET #ThisMonthsName = CONCAT(#Machine, '_DataLog', '_', YEAR(#RighNow), '_', FORMAT(MONTH(#RighNow), '00'));
SET #RenameView = CONCAT(#Machine, '.dbo.sp_rename ', char(39), #LastMonthsName, char(39), ',', char(39), #ThisMonthsName, char(39), ';');
SET #RenameTable = CONCAT(#Machine, '.dbo.sp_rename ', char(39), #Machine, '_DataLog', char(39), ',', char(39), #LastMonthsName, char(39), ';');
BEGIN TRY
--IMPORTANT - Change the View first or you will have duplicate table names
EXEC #RenameView
EXEC #RenameTable
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() AS ErrorNumber,
ERROR_MESSAGE() AS ErrorMessage;
END CATCH;
SET #COUNTER = #COUNTER + 1
END
END
Change
EXEC #RenameView
EXEC #RenameTable
to:
EXEC (#RenameView)
EXEC (#RenameTable)
The problem is that EXEC has actually 2 different implementations, one for dynamic SQL (with parenthesis) and another for procedures (without).

frequently DELETE in Stored Procedure

How I can create a stored procedure and use frequently query like this:
SET NOCOUNT ON;
DECLARE #r INT;
SET #r = 1;
WHILE #r > 0
BEGIN
BEGIN TRANSACTION;
DELETE TOP (100000)
dbo.table1
WHERE Create_Date < DATEADD(YEAR, -5, GETDATE());
SET #r = ##ROWCOUNT;
COMMIT TRANSACTION;
CHECKPOINT;
END
in my new stored procedure?
Thanks for Your answers.
You can make your DELETE statements dynamic using something like below:
CREATE PROCEDURE dbo.DeleteRows (
#tableName VARCHAR(50),
#timestampColName VARCHAR(100),
#since DATETIME2,
#rows INT = 100000
AS
BEGIN
SET NOCOUNT ON;
DECLARE #r INT;
SET #r = 1;
WHILE #r > 0
BEGIN
-- SQL injection might be a problem if table and column name are not coming from a trustworthy source (i.e. user input)
DECLARE #SQL = N'
DELETE TOP (' + CAST(#Count AS INT) + ')' + #tableName + '
WHERE ' + #timestampColName + ' < #since;'
EXEC sp_executesql #SQL, N'#since DATETIME', #since = #since
SET #r = ##ROWCOUNT;
END
END
SQL injection can be tackled using one of the techniques indicated in this question and its answers.

Value passed as parameter doesn't work in sql server for built in functions of datetime

DECLARE #GET DATETIME
SET #GET= GETDATE()
DECLARE #Val VARCHAR(10)
SET #Val='wk'
--SELECT DATEADD(#Type,2,#GET)
SELECT DATEPART(wk,GETDATE()) -- WORKING
The above line works but when i pass it as a paramter it doesn't work.
SELECT DATEPART(#val,GETDATE()) -- NOT WORKING
The interval passed seems to be of other data type.
Make it as Dynamic Query
DECLARE #sql nvarchar(500)
DECLARE #Val VARCHAR(10)
SET #Val='week' --Quarter, Month
set #sql = 'SELECT DATEPART('+#val+',GETDATE())'
exec sp_executesql #sql
Or Alternatively you can use this
If #val = 'Week'
SELECT DATEPART(Week,GETDATE())
Else If #val = 'Month'
SELECT DATEPART(Month,GETDATE())
....