SQL Stored Procedure Insert All Dates From Current Month Not In - sql-server

I have 2 tables
Table Person with a column PersonID (integer)
Table CarePlanReport with columns PersonID (integer), Shift (nvarchar(5) - will hold the text values ‘day’ or ‘night’), CarePlanDate (datetime), Details (nvarchar(50))
I need a stored procedure that will for all dates in the current month, insert 2 records into the CarePlanReport table for each Person.PersonID (a day shift and a night shift) if they don't already exist. This is because each person requires both a day and night shift record for each day in the current month. The problem is a new person could be added during any day in the current month and the missing records will need to be added - so this procedure will need to be run multiple times in any given month. Existing records will remain unchanged because they could already store important details.
EXAMPLE
Person table holds:
PersonId
--------
1
2
CarePlanReport holds for current month
PersonID Shirt CarePlanDate
----------------------------------
1 day 2015/03/01
2 day 2015/03/01
1 night 2015/03/01
2 night 2015/03/01
.. and so on for each day in the month ....
1 day 2015/03/31
2 day 2015/03/31
1 night 2015/03/31
2 night 2015/03/31
I used this to return all dates for the current month (not sure if it’s the most efficient method)
declare #date datetime
set #date = cast(YEAR(GETDATE()) as nvarchar(4)) + RIGHT('00' + cast(Month(GETDATE()) as nvarchar(2)), 2) + '01';
with d as (
select #date as Date
union all
select dateadd(dd,1,Date)
from d
where month(date) = month(#Date)
)
select d.date
from d
where month(date) = month(#Date)

#tfa
It would be good if you write a trigger and that should get invoked as a new record inserted in the Person Table. Let me know if any need any help for this. You have to have few set of validations in your trigger
Use the below procedure which will insert "day" and "night" record for each personid. Just you have to invoke once in a month.
create procedure [dbo].[InsertsRecords]
as
begin
DECLARE #DATE DATETIME
declare #NoOfDays int
declare #lv_count int =1
declare #LastDateOfMonth date
declare #getdates date
declare #lv_personid int
declare #FirstDateOfMonth date
SET #DATE=convert(date,getdate())
set #LastDateOfMonth =(select DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,#date)+1,0)))
set #FirstDateOfMonth=(SELECT convert(date,DATEADD(mm, DATEDIFF(mm, 0, GETDATE()), 0)))
declare rcds_cur cursor for
select personid from dbo.person where personid not in (select personid from dbo.CarePlanReport between
#FirstDateOfMonth and #LastDateOfMonth )
open rcds_cur
FETCH NEXT FROM rcds_cur into #lv_personid
while ##FETCH_STATUS=0
begin
set #NoOfDays=( Select Day(EOMONTH(#DATE)) AS [Current Month])
set #NoOfDays=#NoOfDays-1
while (#lv_count<=#NoOfDays)
begin
set #getdates=(select DATEADD(dd,-#lv_count , #LastDateOfMonth))
insert into dbo.CarePlanReport values (#lv_personid,'day',#getdates,'day shift')
insert into dbo.CarePlanReport values (#lv_personid,'night',#getdates,'night shift')
set #lv_count=#lv_count+1
end
FETCH NEXT FROM rcds_cur into #lv_personid
end
close rcds_cur
deallocate rcds_cur
end

Related

Find out the quarter based on the financial year in SQL Server stored procedure

I have two tables, one is Period and the other is UserTarget.
Period table is:
QuarterNo StartDate EndDate
-----------------------------------------
1 2018-04-01 2018-06-30
2 2018-07-01 2018-09-30
3 2018-10-01 2018-12-31
4 2019-01-01 2019-03-31
UserTarget table is :
USERID YEAR QTR AMOUNT
---------------------------------
akshay 2019 1 200
Right now I am taking the qtr no from period table. Now I don't need take qtr no from period table. I want it from stored procedure based on year is entered in usertarget table
Existing stored procedure :
ALTER PROCEDURE [dbo].[GetQuarterlyTargetData]
#Userid VARCHAR(50)
AS
BEGIN
DECLARE #QuarterNumber VARCHAR(50)
DECLARE #SetTarget DECIMAL(10);
DECLARE #StartDate DATE
DECLARE #EndDate DATE
SELECT
#QuarterNumber = p.QuarterNo,
#SetTarget = AMOUNT
FROM
PERIOD p
LEFT OUTER JOIN
USERTARGETS s ON p.QuarterNo = s.QTR
WHERE
StartDate <= GETDATE() AND EndDate >= GETDATE()
SELECT
#StartDate = StartDate,
#EndDate = EndDate
FROM
PERIOD
WHERE
QuarterNo = #QuarterNumber
From this procedure I am getting the start date and end date for quarter but I don't want to modify in period table every time when I want to check previous years data.
I believe that the term you are looking for is fiscal year. It's where the company year is different than the calendar year.
Note that many people recommend using a lookup table instead of calculating it. Date matches can be difficult for SQL to optimize.
Here's one way to do it. Finding the fiscal year and quarter would probably be good to put in a table function.
DECLARE #userTarget TABLE (UserId VARCHAR(20), Year INT, Quarter INT, Amount INT)
INSERT INTO #userTarget
VALUES
('akshay', 2018, 4, 150)
,('akshay', 2019, 1, 200)
SELECT
s.UserId
,s.Amount
,FY.FiscalYear
,FQ.FiscalQuarter
FROM
(
SELECT
--DATEFROMPARTS(2019, 2, 23)
GETDATE()
AS reportDate
) AS ReportDate
CROSS APPLY (
SELECT
CASE WHEN MONTH(ReportDate.reportDate) < 4 THEN YEAR(ReportDate.reportDate) - 1 -- Fiscal Year begins in April
ELSE YEAR(ReportDate.reportDate)
END AS FiscalYear
) AS FY
CROSS APPLY (
SELECT
DATEDIFF(QUARTER, DATEFROMPARTS(FY.FiscalYear, 4, 1), ReportDate.reportDate) + 1 AS FiscalQuarter
) AS FQ
INNER JOIN #userTarget s
ON s.Year = FY.FiscalYear
AND s.Quarter = FQ.FiscalQuarter
Also, be careful with end dates. last_day >= GETDATE() does not include the last day. Take as an example the end of last quarter, it would calculate it as '2019-03-31 00:00' >= '2019-03-31 08:20' which is false when you want it to be true.
After thinking I come up to this solutions
ALTER PROCEDURE [dbo].[Dashboard_GetQuarterlyTargetData]
#Userid varchar(50)
AS
Begin
DECLARE #QuarterNumber varchar(50)
DECLARE #SetTarget decimal(10);
DECLARE #AchievedTarget decimal(10);
DECLARE #TargetProgress decimal(10);
DECLARE #RemainingTarget decimal(10);
DECLARE #StartDate Date
DECLARE #EndDate Date
DECLARE #Year as int
Select #QuarterNumber = QTR,#Year=YEAR,#SetTarget=AMOUNT from USERTARGETS where USERID=#Userid
if(#QuarterNumber = 1)
begin
SELECT #StartDate = DATEFROMPARTS(#year,4,1), #EndDate=DATEFROMPARTS(#year,6,30)
End
else if(#QuarterNumber = 2)
begin
SELECT #StartDate = DATEFROMPARTS(#year,7,1), #EndDate=DATEFROMPARTS(#year,9,30)
End
else if(#QuarterNumber = 3)
begin
SELECT #StartDate = DATEFROMPARTS(#year,10,1), #EndDate=DATEFROMPARTS(#year,12,31)
End
else if(#QuarterNumber = 4)
begin
SELECT #StartDate = DATEFROMPARTS(#year,1,1), #EndDate=DATEFROMPARTS(#year,3,31)
End

Creating a time dimension with specific dates on SQL

I'm making an analysis project on football transfers. I have a model with one Fact Table called FactTransfers and I need to link it to a time dimension but i need a specific range of dates, namely the dates where it's possible to transfer players (from June 1st to September 1st and from January 1st to January 31st).
I have seen some posts related to the matter but they all have code and attributes that I don't need.
Basically what i want is:
Date as primary key,
Day of the month,
Name of the month,
Transfer window (summer or winter),
Year.
I'm not too familiarized with sql code and I have spent hours trying to figure it out without the results I need.
Thank you in advance for all your help!
Here is the code to create and populate your Dim table for Dates. Hope this helps.
CREATE TABLE [dbo].[DimDate]
(
[DateKey] INT primary key,
[Date] DATETIME,
[DayofMonth] TINYINT, -- Field will hold day number of Month
[NameofMonth] VARCHAR(9),--January, February etc
[TransferWindow] VARCHAR(20), -- Summer & Winter
)
--Specify Start Date and End date here
--Value of Start Date Must be Less than Your End Date
DECLARE #StartDate DATETIME = '01/01/2015' --Starting value of Date Range
DECLARE #EndDate DATETIME = '12/31/2025' --End Value of Date Range
DECLARE #CurrentDate AS DATETIME = #StartDate
WHILE #CurrentDate < #EndDate
BEGIN
INSERT INTO [dbo].[DimDate]
SELECT
CONVERT (char(8),#CurrentDate,112) as DateKey,
#CurrentDate AS Date,
DATEPART(DD, #CurrentDate) AS [DayOfMonth],
DATENAME(MM, #CurrentDate) AS [MonthName],
CASE WHEN (MONTH(#CurrentDate) BETWEEN 6 AND 8) OR ( MONTH(#CurrentDate) =9 AND DATEPART(DD, #CurrentDate)=1) THEN 'Summer'
WHEN MONTH(#CurrentDate) =1 THEN 'Winter'
ELSE ''
END AS [TransferWindow]
SET #CurrentDate = DATEADD(DD, 1, #CurrentDate)
END
SELECT * FROM [DimDate]
--DROP TABLE [DimDate]

Function to calculate age

I am trying to write a function where I can find age based on the date the record was inserted rather than getdate(). I want to filter the user who are less than 18 years when they registered.
If I query it after a year, it should still show the user as 17 based on record insert date than current date. This is what I wrote but it is still giving the age based on current date than the record insert date. Any suggestions would be really helpful.
Thank You
--InputDate as DateOfBirth
--InsertDate as date the record was inserted
CREATE FUNCTION [dbo].[FindAge] (#InputDate int, #Insertdate datetime )
RETURNS int
AS
BEGIN
DECLARE #Age as Int
DECLARE #d DATETIME
SET #d = CONVERT(DATETIME, CONVERT(VARCHAR(8), #InputDate), 112)
SELECT #Age=DATEDIFF(year, #d, #Insertdate)
- CASE WHEN DATEADD(year, DATEDIFF(year, #d, #Insertdate), #d) <= GetDate()
THEN 0 ELSE 1 END
RETURN #Age
END
---- Drop Obselete procs
GO
Update
Followed Bacon Bits suggestion and it worked out perfectly.
All DATEDIFF() does is subtract the years from the date components. It's very stupid:
select datediff(yy,'2000-12-19','2014-01-01') --14
select datediff(yy,'2000-12-19','2014-12-18') --14
select datediff(yy,'2000-12-19','2014-12-19') --14
select datediff(yy,'2000-12-19','2014-12-20') --14
select datediff(yy,'2000-12-19','2014-12-31') --14
select datediff(yy,'2000-12-19','2015-01-01') --15
select datediff(yy,'2000-12-19','2015-12-31') --15
select datediff(yy,'2000-12-19','2016-01-01') --16
select datediff(yy,'2000-12-19','2016-12-31') --16
Don't calculate the number of hours in a year with the year being 365.25 days long or something like that. It's an exercise in futility, and just guarantees that you will be wrong near every person's birthday.
Your best bet is to calculate it how humans do it. In the US (and most Western nations, I believe) it's the difference between the years, but you only count the current year when you pass your birthday:
declare #birthdate date = '2000-12-19';
declare #target date;
SELECT DATEDIFF(yy, #birthdate, #target)
- CASE
WHEN (MONTH(#birthdate) > MONTH(#target))
OR (
MONTH(#birthdate) = MONTH(#target)
AND DAY(#birthdate) > DAY(#target)
)
THEN 1
ELSE 0
END
Here's the values you'd get:
set #target = '2014-01-01' --13
set #target = '2014-12-18' --13
set #target = '2014-12-19' --14
set #target = '2014-12-20' --14
set #target = '2014-12-31' --14
set #target = '2015-01-01' --14
set #target = '2015-12-31' --15
set #target = '2016-01-01' --15
set #target = '2016-12-31' --16
Change #target to getdate() to calculate the age as of now.
If your region uses East Asian age reckoning, however, you'll need to use a completely different method to determine what age a person is since they're considered age 1 on the day they're born, and their age increases each February.

sql query calculating no of employees joined each financial year i.e from 1-04-2002 to 31-03-2003

I have a table in which joining dates are give in datetime format.
I have to calculate how many employees joined each financial year resp. ie for eg from
1-04-2002 to 31-03-2003.this should work for each year..from 2003 to 2004,2004 to 2005...n so on.
can anybdy help?
thanxx.
You can map start date to financial year using YEAR(DATEADD(M,-3,JoinDate) I think and you can count records with a CTE, e.g.
with EmployeeStartFinYear(FinYear, EmployeeId)
as
(
select year(dateadd(M,-3,JoinDate)), EmployeeId
from Employees
where JoinDate is not null
)
select FinYear, count(EmployeeId)
from EmployeeStartFinYear
group by FinYear
order by FinYear;
Here's my answer. I think it looks horrid, but i think it works. I'll explain the logic behind it.
I declared the Start and End dates just because it's easier to write than a date.
The #Years variable is the difference in years between the start and end date.
#Counter is used to loop through the number of years stored in #Years. #Diff is always one more than #Counter because each time we go through the loop, we want to increment the date range so it's always 1 year, rather than be counting employees that joined in 1 year, then 2 years etc.
#TempTable stores the info we get each time we go through the query.
All the query does is get the count of employees between the Start Date and a year from that start date and puts it into a temp table. Then it looks through again, and gets the employees that started between Start Date + 1 and Start Date + 2.
Sorry if it's horrible and ugly and doesn't work.
DECLARE #StartDate DATETIME
DECLARE #EndDate DATETIME
DECLARE #Years TINYINT
DECLARE #Counter TINYINT
DECLARE #Diff TINYINT
DECLARE #TempTable TABLE
(
FinancialYear VARCHAR(9)
,Employees TINYINT
)
SET #Count = 0
SET #Diff = 1
SET #Years = DATEDIFF(yyyy, #StartDate, #EndDate)
WHILE #Count < #Years - 1
BEGIN
SELECT
CAST(DATEPART(yyyy, DATEADD(yyyy, #Count, #StartDate) AS VARCHAR(4)) + '-' + CAST(DATEPART(yyyy, DATEADD(yyyy, #Diff, #StartDate)) AS VARCHAR(4) AS FinancialYear
,COUNT(employee_id) AS Employees
INTO #TempTable
FROM
Employees
WHERE
join_date >= #StartDate AND join_date < DATEADD(yyyy, 1, #StartDate)
GROUP BY
CAST(DATEPART(yyyy, DATEADD(yyyy, #Count, #StartDate) AS VARCHAR(4)) + '-' + CAST(DATEPART(yyyy, DATEADD(yyyy, #Diff, #StartDate)) AS VARCHAR(4)
SET #Count = #Count + 1
SET #Diff = #Diff + 1
END
SELECT * FROM #TempTable

How to properly populate the age attribute?

I have an MS-SQL Server which keeps track on a number of clients. If the birthday is known it is stored as a datetime attribute called dayOfBirth. Now I would like to have another attribute age, which keeps track of the current age of the client. Since age can change any day I figured a script might be the best idea.
First thing I did, was to create a stored procedure which computes the age given the birthday as datetime. Here is what I came up with:
USE [MyDB]
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE CalculateAge
#dayOfBirth datetime,
#age INT OUTPUT
AS
DECLARE #today datetime, #thisYearBirthDay datetime
DECLARE #years int
SET #today = GETDATE()
SET #thisYearOfBirth = DATEADD(year, #dayOfBirth, #today), #dayOfBirth)
SET #years = DATEDIFF(year, #dayOfBirth, #today) - (CASE WHEN #thisYearBirthDay > #today THEN 1 ELSE 0 END)
SET #age = #years
Afterwards I created another script which runs through all records that have a non null dayOfBirth attribute and updates the age filled accordingly.
USE [MyDB]
GO
DECLARE #age int;
DECLARE #birth datetime;
DECLARE #id intl
DECLARE cursorQuery CURSOR FOR SELECT clientId FROM Clients WHERE dayOfBirth IS NOT NULL;
OPEN cursorQuery
FETCH NEXT FROM cursorQuery INTO #id
WHILE ##FETCH_STATUS = 0
BEGIN
SET #birth = (SELECT dayOfBirth from Kunden where clientId=#id);
EXEC dbo.CalculateAge #birth, #age OUTPUT;
UPDATE Clients SET Age = #age WHERE clientId = #id;
FETCH NEXT FROM cursorQuery INTO #id
END
CLOSE cursorQuery
DEALLOCATE cursorQuery
I would trigger the script above once per day to populate the age attribute. Thats what I have so far, but I have the feeling there is plenty of room for improvement.
** Thanks Sung Meister **
I ended up with something like this:
CREATE TABLE Client (
ID int identity(1,1) primary key,
DOB datetime null,
AGE as (case
when DOB is null then null
else DATEDIFF(YY,DOB,GETDATE()) - CASE WHEN (MONTH(GETDATE()) = MONTH(DOB) AND DAY(DOB) > DAY(GETDATE()) OR MONTH(GETDATE()) > MONTH(DOB)) THEN 1 ELSE 0 END
end
)
)
Instead of creating a trigger and do any manual updates,
easiest way to get around with this is to create a calculated field called Age.
This way, you do not have to worry about Age data being out of sync (data stays consistent) and no more trigger or manual update is required.
Calculating an age of person can be a bit hairy as you can see in the picture below.
Here is the text used
create table #Person (
ID int identity(1, 1) primary key,
DOB datetime null,
Age as
(case
when DOB is null then null
--; Born today!
when datediff(d, DOB, getdate()) = 0 then 0
--; Person is not even born yet!
when datediff(d, DOB, getdate()) < 0 then null
--; Before the person's B-day month so calculate year difference only
when cast(datepart(m, getdate()) as int) > cast(datepart(m, DOB) as int)
then datediff(year, DOB, getdate())
--; Before Person's b-day
else datediff(year, DOB, getdate()) - 1
end)
)
insert #Person select GetDate()
insert #Person select null
insert #Person select '12/31/1980'
select * from #Person
update #Person
set DOB = '01/01/1980'
where ID = 2
select * from #Person
You really shouldn't be storing the age within the database as it is easily calculated and changes on a daily basis.
I would suggest that you keep the date of birth field and just calculate the age as you need it. If you wish to have the age selected along with the other attributes then consider a view, perhaps with a user defined function to calculate the age.
The following is an example (untested) UDF that you could use
CREATE FUNCTION age
(
#userId int
)
RETURNS int
AS
BEGIN
DECLARE #Result int
SELECT #Result = DATEDIFF(year, dateofBirth, getDate())
FROM person
WHERE userId = #userId
RETURN #Result
END
GO
Then within your queries you can do something similar to the following,
SELECT *,
dbo.age(userId) as age
FROM person
In answer to your question on sorting etc, then you could create a view on the data and use that to show the data so something like this (untested)
CREATE VIEW personview(firstname, surname, dateOfBirth,age) AS
SELECT firstname,
surname,
dateOfbirth,
dbo.age(userid)
FROM person
You can then use this view to perform your queries, there could be a performance hit for filtering and sorting based on the age and if you regularly sort/filter based upon the age field then you may want to create an indexed view.
Use a view and/or function. Never store two fields that arew based on the exact same data if you can help it as they will eventually get out of sync.

Resources