Creating a time dimension with specific dates on SQL - sql-server

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]

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

SQL Sever - Datediff, hours, but exclude Saturday/ Sundays

I'm wondering how to calculate the "number of hours" between two timestamps (2016-02-24 17:30:00 and another, for instance) in SQL server- but excluding Saturday and Sunday's full 48 hour period, if crossed.
This isn't quite the same as pure business hours, but sort of. The reason for this is long-winded and unnecessary.
EDIT: I can also say that the end-date will always be during the week. So really ... the "start date" can simply be transmuted to Monday midnight, if on Sat/ Sun ... then maybe a function include the total week count...
DATEDIFF(Week, date, date2) will return the number of week boundaries that are crossed between the two dates. For SQL Server, this means how many Sundays are between the dates (as opposed to the number of 7 day periods are between them). This means, that if you can indeed assume that start and end date will not be a saturday or sunday, you can subtract 48 X DATEDIFF(Week, date, date2) from your normal DATEDIFF call and that should give you what are after.
I would use the below code
declare #NumberOfHours int
declare #StartTime datetime
declare #EndTime datetime
set #StartTime = '2017-02-02 17:30:00.000'
set #EndTime = '2017-02-07 00:00:00.000'
set #NumberOfHours = DATEDIFF(HOUR,#StartTime,#EndTime)
if(datepart(WEEKDAY, #StartTime)=1)
begin
set #NumberOfHours = #NumberOfHours DATEDIFF(HH,#StartTime,#EndTime)%24
end
else if(datepart(WEEKDAY, #StartTime)=7)
begin
set #NumberOfHours = #NumberOfHours - DATEDIFF(HH,#StartTime,#EndTime)%24
set #NumberOfHours = #NumberOfHours - 24
end
else
begin
set #NumberOfHours = #NumberOfHours - datediff(ww,#StartTime,#EndTime)*48
end
print #NumberOfHours
I would use a calendar table (ex. dbo.DateDimension, ref https://www.mssqltips.com/sqlservertip/4054/creating-a-date-dimension-or-calendar-table-in-sql-server/):
CREATE TABLE dbo.DateDimension
(
DateKey INT NOT NULL PRIMARY KEY,
[Date] DATE NOT NULL,
[Day] TINYINT NOT NULL,
DaySuffix CHAR(2) NOT NULL,
[Weekday] TINYINT NOT NULL,
WeekDayName VARCHAR(10) NOT NULL,
IsWeekend BIT NOT NULL,
IsHoliday BIT NOT NULL,
...
)
and, also, following query:
SELECT SUM(
CASE
WHEN dd.[Date] = CONVERT(DATE, #StartDate) THEN DATEDIFF(MINUTE, #StarDate, DATEADD(DAY, 1, dd.[Date]))
WHEN dd.[Date] = CONVERT(DATE, #EndDate) THEN DATEDIFF(MINUTE, dd.[Date], #EndDate)
ELSE 24 * 60 -- Full day
END) / 60 AS SumOfHours
FROM dbo.DateDimension dd
WHERE dd.[Date] >= CONVERT(DATE, #StartDate) AND dd.[Date] <= CONVERT(DATE, #EndDate)
AND dd.IsWeekend = 0
Above query will compute total amount of minutes for requested period of time and then it will divide by 60 to get number of hours.

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.

Calculating the working hours in a given period

I'm trying to create a function where I can calculate the total working hours in a given period.
Explanation:
inputs are in mm/dd/yyyy hh:mm format.
For example user Inputs:
startDateTime: 01/10/2013 9:30am endDateTime: 12/12/2013 5pm
Daily working hours from 7am to 5pm. For example, if someone starts working at 9am and ends at 8pm them is working hours will calculate as 8 hours (only calculate the working hours between 7am to 5pm).
I do have a holiday list:
Every week's Saturday and Sunday are holiday
and bellow are the public holidays
HOLIDAY LIST:
07/04/yyyy
12/25/yyyy
12/31/yyyy
Memorial Day:- Last Monday of every May
Labor Day:- First Monday of Every September
Thanksgiving Day:- Fourth Thursday of every November
Now my problem is how to find out Memorial Day, Labor Day and Thanksgiving Day with in any given time period.
Final output will be the total working hours in the given period substituting the hours of holiday.
Bellow function I'm using only to calculate working hours eliminating weekends
CREATE FUNCTION [dbo].[WorkTime]
(
#StartDate DATETIME,
#FinishDate DATETIME
)
RETURNS BIGINT
AS
BEGIN
DECLARE #Temp BIGINT
SET #Temp=0
DECLARE #FirstDay DATE
SET #FirstDay = CONVERT(DATE, #StartDate, 112)
DECLARE #LastDay DATE
SET #LastDay = CONVERT(DATE, #FinishDate, 112)
DECLARE #StartTime TIME
SET #StartTime = CONVERT(TIME, #StartDate)
DECLARE #FinishTime TIME
SET #FinishTime = CONVERT(TIME, #FinishDate)
DECLARE #WorkStart TIME
SET #WorkStart = '07:00'
DECLARE #WorkFinish TIME
SET #WorkFinish = '17:00'
DECLARE #DailyWorkTime BIGINT
SET #DailyWorkTime = DATEDIFF(HOUR, #WorkStart, #WorkFinish)
IF (#StartTime<#WorkStart)
BEGIN
SET #StartTime = #WorkStart
END
IF (#FinishTime>#WorkFinish)
BEGIN
SET #FinishTime=#WorkFinish
END
DECLARE #CurrentDate DATE
SET #CurrentDate = #FirstDay
DECLARE #LastDate DATE
SET #LastDate = #LastDay
WHILE(#CurrentDate<=#LastDate)
BEGIN
IF (DATEPART(dw, #CurrentDate)!=1 AND DATEPART(dw, #CurrentDate)!=7)
BEGIN
IF (#CurrentDate!=#FirstDay) AND (#CurrentDate!=#LastDay)
BEGIN
SET #Temp = #Temp + #DailyWorkTime
END
--IF it starts at startdate and it finishes not this date find diff between work finish and start as minutes
ELSE IF (#CurrentDate=#FirstDay) AND (#CurrentDate!=#LastDay)
BEGIN
SET #Temp = #Temp + DATEDIFF(HOUR, #StartTime, #WorkFinish)
END
ELSE IF (#CurrentDate!=#FirstDay) AND (#CurrentDate=#LastDay)
BEGIN
SET #Temp = #Temp + DATEDIFF(HOUR, #WorkStart, #FinishTime)
END
--IF it starts and finishes in the same date
ELSE IF (#CurrentDate=#FirstDay) AND (#CurrentDate=#LastDay)
BEGIN
SET #Temp = DATEDIFF(HOUR, #StartTime, #FinishTime)
END
END
SET #CurrentDate = DATEADD(day, 1, #CurrentDate)
END
-- Return the result of the function
IF #Temp<0
BEGIN
SET #Temp=0
END
RETURN #Temp
END
Please help me how to find out the Thanksgiving Day, Labor Day and Memorial Day in a given period.

Show 0 if null in query

I am running a sql query that is omitting the day if the return count is 0. I want my query to return the day and a 0 count if the the count is 0. Snare I have is that if 0 were sold for the day, the day is omitted from my return results.
SELECT ISNULL([day],0) As [day], COUNT(ISNULL(Sold,0)) As [Sold]
FROM productionInfo
You're drawing information from a single table, productionInfo. If productionInfo has no rows with that date information (because there are no widgets sold on that date), how does it know what dates to use?
You might want to look at using a Numbers Table to get a row for each day of the month/year, then join that to productionInfo so you have a day value available, even if there was no production that day.
This will give you a dates table:
CREATE FUNCTION dbo.DatesTable (#startDate DATETIME, #endDate DATETIME)
RETURNS #retTable TABLE (DateValue DATETIME)
AS
BEGIN
DECLARE #currentDate DATETIME
SET #currentDate = #startDate
WHILE (DATEDIFF(dd, #currentDate, #endDate) >= 0)
BEGIN
INSERT INTO #retTable VALUES (#currentDate)
SET #currentDate = DATEADD(dd, 1, #currentDate)
END
RETURN
END
Then your query will look like:
SELECT dt.DateValue AS [day], COUNT(Sold) AS [Sold]
FROM dbo.DatesTable('2-1-2014', '2-10-2014') dt
LEFT JOIN productionInfo pi ON pi.day = dt.DateValue
GROUP BY dt.DateValue

Resources