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
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
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
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).
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.
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())
....