In my old database, there is a table Album which stores information about ID, AlbumName, Release_Date (e.g. 01/01/2017) etc.
I want to further break down the Release_Date into a time dimension table, so I create a DimDateAlbum table.
This is the time dimension table I have created.
CREATE TABLE [DimDateAlbum]
(
[DateKey] INT PRIMARY KEY,
[Date] DATETIME NOT NULL,
[Year] INT NOT NULL,
[Quarter] TINYINT NOT NULL,
[QuarterName] VARCHAR(6) NOT NULL, -- January to March: First, April to
June: Second etc
[Month] TINYINT NOT NULL,
[MonthName] VARCHAR(9) NOT NULL, -- January, February etc
[Day] TINYINT NOT NULL, -- Field holds day number of Month
[DayofWeek] TINYINT NOT NULL,
[WeekName] VARCHAR(9) NOT NULL, -- Field displays 1: Monday, 2: Tuesday etc
)
As discussed below: I can insert Release_Date into time dimension table as [DateKey], however, how do I further break down the date into year, quarter, day etc.?
INSERT INTO DimDateAlbum
SELECT
a.Release_Date AS [DateKey],
CONVERT (char(8), a.Release_Date, 112) AS [DateKey],
a.Release_Date AS [Date],
DATEPART(YEAR, a.Release_Date) AS [Year], -- calendar year
DATEPART(QQ, a.Release_Date) AS [Quarter], -- calendar quarter
CASE (qq, a.Release_Date)
WHEN 1 THEN 'First'
WHEN 2 THEN 'Second'
WHEN 3 THEN 'Third'
WHEN 4 THEN 'Fourth'
END AS [QuarterName],
DATEPART(MONTH, a.Release_Date) AS [Month], -- month number of the year
DATENAME(MM, a.Release_Date) AS [MonthName], -- month name
DATEPART(DAY, a.Release_Date) AS [Day], -- day number of the month
DATEPART(DW, a.Release_Date) AS [DayofWeek], -- day number of week
CASE datepart(DW, a.Release_Date)
WHEN 1 THEN 'Monday'
WHEN 2 THEN 'Tuesday'
WHEN 3 THEN 'Wednesday'
WHEN 4 THEN 'Thursday'
WHEN 5 THEN 'Friday'
WHEN 6 THEN 'Saturday'
WHEN 7 THEN 'Sunday'
END AS [WeekName]
FROM
dbo.Album AS a
This code does not work, any help on how to fix it? Thank you so much!
If I understood your correctly you want to populate DimDateAlbum table. I've edited a little bit your table( added identity constraint to avoid writing this field manually) and now it looks like this:
CREATE TABLE [DimDateAlbum]
(
[DateKey] INT IDENTITY CONSTRAINT PK_DimDateAlbum_ID PRIMARY KEY,
[Date] DATETIME NOT NULL,
[Year] INT NOT NULL,
[Quarter] TINYINT NOT NULL,
[QuarterName] VARCHAR(50) NOT NULL, -- January to March: First, April to
[Month] TINYINT NOT NULL,
[MonthName] VARCHAR(9) NOT NULL, -- January, February etc
[Day] TINYINT NOT NULL, -- Field holds day number of Month
[DayofWeek] TINYINT NOT NULL,
[WeekName] VARCHAR(50) NOT NULL, -- Field displays 1: Monday, 2: Tuesday etc
)
And now you can insert your data. I've added a test variable to insert one row, however it can be used for inserting from table:
INSERT INTO dbo.DimDateAlbum
(
DateKey,
Date,
Year,
Quarter,
QuarterName,
Month,
MonthName,
Day,
DayofWeek,
WeekName
)
SELECT
CAST(a.Release_Date AS DATETIME)
, YEAR(CAST(a.Release_Date AS DATETIME)) --
, DATEPART(QUARTER, CAST(a.Release_Date AS DATETIME)) -- Quarter
, CASE -- Quarter Name
WHEN DATEPART(QUARTER, CAST(a.Release_Date AS DATETIME)) = 1 THEN 'January to March' -- Quarter Name
WHEN DATEPART(QUARTER, CAST(a.Release_Date AS DATETIME)) = 2 THEN 'April to June' -- Quarter Name
WHEN DATEPART(QUARTER, CAST(a.Release_Date AS DATETIME)) = 3 THEN 'July to September' -- Quarter Name
WHEN DATEPART(QUARTER, CAST(a.Release_Date AS DATETIME)) = 4 THEN 'October to December' -- Quarter Name
END
, MONTH(CAST(a.Release_Date AS DATETIME)) -- Month number
, DATENAME(MONTH, DATEADD( MONTH, MONTH(CAST(a.Release_Date AS DATETIME)), 0) - 1) -- Month name
, DAY(CAST(a.Release_Date AS DATETIME)) -- 6
, DATEPART(dw, CAST(a.Release_Date AS DATETIME)) -- 5
, DATENAME(dw, CAST(a.Release_Date AS DATETIME)) -- Thursday
FROM Album a
Work example:
DECLARE #FooDate VARCHAR(30) = '2018-12-06 12:10:51.727'
INSERT INTO dbo.DimDateAlbum
(
DateKey,
Date,
Year,
Quarter,
QuarterName,
Month,
MonthName,
Day,
DayofWeek,
WeekName
)
SELECT
CAST(#FooDate AS DATETIME)
, YEAR(CAST(#FooDate AS DATETIME)) --
, DATEPART(QUARTER, CAST(#FooDate AS DATETIME)) -- Quarter
, CASE -- Quarter Name
WHEN DATEPART(QUARTER, CAST(#FooDate AS DATETIME)) = 1 THEN 'January to March' -- Quarter Name
WHEN DATEPART(QUARTER, CAST(#FooDate AS DATETIME)) = 2 THEN 'April to June' -- Quarter Name
WHEN DATEPART(QUARTER, CAST(#FooDate AS DATETIME)) = 3 THEN 'July to September' -- Quarter Name
WHEN DATEPART(QUARTER, CAST(#FooDate AS DATETIME)) = 4 THEN 'October to December' -- Quarter Name
END
, MONTH(CAST(#FooDate AS DATETIME)) -- Month number
, DATENAME(MONTH, DATEADD( MONTH, MONTH(CAST(#FooDate AS DATETIME)), 0) - 1) -- Month name
, DAY(CAST(#FooDate AS DATETIME)) -- 6
, DATEPART(dw, CAST(#FooDate AS DATETIME)) -- 5
, DATENAME(dw, CAST(#FooDate AS DATETIME)) -- Thursday
Related
I'm trying to extend a CalendarDate table in one of our systems because the initial table only had dates up to the end of 2019. (I know...)
The issue is that one of the fields is a week field for our client that is in the format YYYYXX, where XX is a counter of week (ie 01 - 52).
The problem I am encountering is that there is a corresponding DayOfWeek field where Saturday is 1, and the week in the problem column increments on a Saturday, whereas in SQL Server it seems that DATEPART(WEEK, #Date) increments on a Sunday.
Using a temporary table of dates from today until 2030, is there any way I can compute this column?
One caveat is that the column must contain 52 full weeks per year. This means, for example, that the week 202001 in this column actually started on 2019-12-28, so I cannot simply get the year component from DATEPART(YEAR, #date)
Here is what I have so far (#TempDates is just a table with list of dates from today onward):
SELECT
10 AS TrackingGroupID,
CAST([Date] AS DATETIME) AS CalendarDate,
NULL AS RetailerWeek,
CAST(CAST(DATEPART(YEAR, Date) AS VARCHAR(4))
+ RIGHT('000' + CONVERT(VARCHAR(3), DATEPART(DAYOFYEAR, Date)), 3) AS INT) AS DateID,
UPPER(SUBSTRING(DATENAME(WEEKDAY, Date), 1, 3)) AS DayOfWeek,
CASE UPPER(SUBSTRING(DATENAME(WEEKDAY, Date), 1, 3))
WHEN 'SAT' THEN 1
WHEN 'SUN' THEN 2
WHEN 'MON' THEN 3
WHEN 'TUE' THEN 4
WHEN 'WED' THEN 5
WHEN 'THU' THEN 6
WHEN 'FRI' THEN 7
END AS DayOfWeekID,
-- This is the column that needs fixing
CAST(CAST(DATEPART(YEAR, Date) AS VARCHAR(4))
+ RIGHT('00' + CONVERT(VARCHAR(2), DATEPART(WEEK, Date)), 2) AS INT) AS RetailerWeek2
FROM
#TempDates
I use this code to create date dimension.
This contains the week for every day.
You can generate the week as you wish.
/*Creación de la tabla*/
create table Dimensiones.Fecha
(
FechaSK int not null,
Fecha date not null,
Año smallint not null,
Trimestre smallint not null,
Mes smallint not null,
Semana smallint not null,
Dia smallint not null,
DiaSemana smallint not null,
NTrimestre char(12) not null,
NMes char(15) not null,
NMes3L char(3) not null,
NSemana char(10) not null,
NDia char(6) not null,
NDiaSemana char(10) not null
constraint PK_DIM_TIEMPO PRIMARY KEY CLUSTERED
(
Fecha asc
)
)
/*Script de carga*/
DECLARE #FechaDesde as smalldatetime, #FechaHasta as smalldatetime
DECLARE #FechaAAAAMMDD int
DECLARE #Año as smallint, #Trimestre char(2), #Mes smallint
DECLARE #Semana smallint, #Dia smallint, #DiaSemana smallint
DECLARE #NTrimestre char(12), #NMes char(15)
DECLARE #NMes3l char(3)
DECLARE #NSemana char(10), #NDia char(6), #NDiaSemana char(10)
--Set inicial por si no coincide con los del servidor
SET DATEFORMAT dmy
SET DATEFIRST 1
BEGIN TRANSACTION
--Borrar datos actuales, si fuese necesario
TRUNCATE TABLE dimensiones.fecha
--Rango de fechas a generar: del 01/01/1990 al 31/12/Año actual+2
SELECT #FechaDesde = CAST('19900101' AS smalldatetime)
--SELECT #FechaHasta = CAST(CAST(YEAR(GETDATE())+2 AS CHAR(4)) + '1231' AS smalldatetime)
WHILE (#FechaDesde <= #FechaHasta) BEGIN
SELECT #FechaAAAAMMDD = YEAR(#FechaDesde)*10000+
MONTH(#FechaDesde)*100+
DATEPART(dd, #FechaDesde)
SELECT #Año = DATEPART(yy, #FechaDesde)
SELECT #Trimestre = DATEPART(qq, #FechaDesde)
SELECT #Mes = DATEPART(m, #FechaDesde)
SELECT #Semana = DATEPART(wk, #FechaDesde)
SELECT #Dia = RIGHT('0' + DATEPART(dd, #FechaDesde),2)
SELECT #DiaSemana = DATEPART(DW, #FechaDesde)
SELECT #NMes = DATENAME(mm, #FechaDesde)
SELECT #NMes3l = LEFT(#NMes, 3)
SELECT #NTrimestre = 'Trimestre ' + CAST(#Trimestre as CHAR(1))
SELECT #NSemana = 'Sem ' +CAST(#Semana AS CHAR(2)) + '/' + RIGHT(RTRIM(CAST(#Año as CHAR(4))),2)
SELECT #NDia = CAST(#Dia as CHAR(2)) + ' ' + RTRIM(#NMes)
SELECT #NDiaSemana = DATENAME(dw, #FechaDesde)
INSERT INTO Dimensiones.Fecha
(
FechaSK,
Fecha,
Año,
Trimestre,
Mes,
Semana,
Dia,
DiaSemana,
NTrimestre,
NMes,
NMes3L,
NSemana,
NDia,
NDiaSemana
) VALUES
(
#FechaAAAAMMDD,
#FechaDesde,
#Año,
#Trimestre,
#Mes,
#Semana,
#Dia,
#DiaSemana,
#NTrimestre,
#NMes,
#NMes3l,
#NSemana,
#NDia,
#NDiaSemana
)
--Incremento del bucle
SELECT #FechaDesde = DATEADD(DAY, 1, #FechaDesde)
END
COMMIT TRANSACTION
If you set the DATEFIRST to 6 then a week starts on saturday.
Then by using a CASE WHEN the year & week can be adjusted.
...
CAST(
CASE
WHEN DATEPART(week, [Date]) > 52
THEN (100*(YEAR([Date])+1))+1
ELSE (100* YEAR([Date])) + DATEPART(week, [Date])
END AS VARCHAR(6)) as RetailerWeek2
...
Example:
SET DATEFIRST 6; -- 6: Saturday
SELECT ##DATEFIRST AS datefirst;
SELECT [Date]
, DATEPART(week, [Date]) as WeekNr
, DATEPART(dw, [Date]) as WeekdayNr
, CAST(
CASE
WHEN DATEPART(week, [Date]) > 52
THEN (100*(YEAR([Date])+1))+1
ELSE (100* YEAR([Date])) + DATEPART(week, [Date])
END AS VARCHAR(6)) as AdjustedYearWeek
FROM (VALUES
(CAST('2019-12-28' AS DATE)),
(CAST('2020-01-01' AS DATE)),
(CAST('2020-12-31' AS DATE))
) q([Date]);
GO
| datefirst |
| :-------- |
| 6 |
Date | WeekNr | WeekdayNr | AdjustedYearWeek
:------------------ | -----: | --------: | :---------------
28/12/2019 00:00:00 | 53 | 1 | 202001
01/01/2020 00:00:00 | 1 | 5 | 202001
31/12/2020 00:00:00 | 53 | 6 | 202101
db<>fiddle here
I have this table in which I am storing TimeIn and Time Out of Employee.
When I get Total Hours any Employee have worked in certain day, it works fine date wise. But in out organization the issue is that a day is considered from 6 AM till 5:59 AM (next day).
Here is my table and sample data.
CREATE TABLE [dbo].[Attendance]
(
[Employee] [varchar](50) NULL,
[TimeIn] [datetime] NULL,
[TimeOut] [datetime] NULL
) ON [PRIMARY]
GO
INSERT [dbo].[Attendance] ([Employee], [TimeIn], [TimeOut]) VALUES (N'Lewis', CAST(N'2018-12-01 06:30:00.000' AS DateTime), CAST(N'2018-12-01 18:22:00.000' AS DateTime))
GO
INSERT [dbo].[Attendance] ([Employee], [TimeIn], [TimeOut]) VALUES (N'Lewis', CAST(N'2018-12-01 20:12:00.000' AS DateTime), CAST(N'2018-12-01 23:50:00.000' AS DateTime))
GO
INSERT [dbo].[Attendance] ([Employee], [TimeIn], [TimeOut]) VALUES (N'Lewis', CAST(N'2018-12-02 00:12:00.000' AS DateTime), CAST(N'2018-12-02 04:50:00.000' AS DateTime))
GO
INSERT [dbo].[Attendance] ([Employee], [TimeIn], [TimeOut]) VALUES (N'Lewis', CAST(N'2018-12-02 07:21:00.000' AS DateTime), CAST(N'2018-12-02 19:54:00.000' AS DateTime))
GO
Here is the query and output of the query I am executing.
SELECT Employee, CAST(COALESCE(TimeIn, TimeOut) AS DATE) DATE, DATEDIFF(HOUR, MIN(TimeIn), MAX(TimeOut)) [Hours Worked]
FROM [dbo].[Attendance]
GROUP BY Employee, CAST(COALESCE(TimeIn, TimeOut) AS DATE)
Output:
Employee DATE Hours Worked
----------------- ---------- ------------
Lewis 2018-12-01 17
Lewis 2018-12-02 19
What I want is to get the working hours calculated from 6 AM to 5:59 AM next day. So the expected output is as below:
Employee DATE Hours Worked
----------------- ---------- ------------
Lewis 2018-12-01 22:20
Lewis 2018-12-02 12:33
Hope this is possible..
You should probably have a calendar table which contains all the dates which you want to appear in your report. In the absence of that, we can just assume that all dates are covered by the time, and we can group by the time in, shifted earlier by 6 hours. The trick here is that we can shift all times backwards by 6 hours, to align everything with the usual 24 hour day. Something like this should work:
SELECT
Employee,
CONVERT(date, DATEADD(HOUR, -6, TimeIn)) AS DATE,
CONVERT(VARCHAR(10), DATEDIFF(HOUR, MIN(TimeIn), MAX(TimeOut))) + ':' +
CONVERT(VARCHAR(10), DATEDIFF(MINUTE, MIN(TimeIn), MAX(TimeOut)) % 60) AS [Hours Worked]
FROM Attendance
GROUP BY
Employee,
CONVERT(date, DATEADD(HOUR, -6, TimeIn));
Demo
How can I get the date of specific day ? Like if I have Thursday or month number ?
If I give 12 for instance I want to get the date of 12th day of this month. Or if I give 'Sun' or 'Sat' is it possible to get the dates of these days ?
DATEFROMPARTS function can construct a date from day, month and year.
DATEPARTS does the opposite - gives you the day, month, year, hour, etc. of a date. Or you can use functions like YEAR, MONTH and DAY.
You can deconstruct the value returned by GETDATE function and construct whatever date you want. Here is for example how to get the date for 12th day of the current month:
select DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 12)
Converting 'Sun' or 'Sat' to date is a bit more difficult. First, they aren't quite deterministic. If today is Friday, "Sunday this week" means "next Sunday" in some parts of the world and "last Sunday" in others. You should implement your own logic based on the value returned by DATEPART(dw, GETDATE()) (which will give you the day of the week).
To find the weekday of the current month
DECLARE #daynumber INT = 12
SELECT datename(weekday, dateadd(d, #daynumber - 1, getdate()))
To find the dates of the current month of a given weekday
DECLARE #dayname char(3) = 'sat'
;WITH CTE as
(
SELECt TOP
(datediff(D, eomonth(getdate(), -1),eomonth(getdate())))
dateadd(d,row_number()over(ORDER BY 1/0),
eomonth(getdate(),-1))date
FROM
(values(1),(2),(3),(4),(5),(6))x(x),
(values(1),(2),(3),(4),(5),(6))y(x)
)
SELECT day(date) monthday, date
FROM CTE
WHERE left(datename(weekday, date),3) = #dayname
select sysdatetime(); --2018-12-13 16:29:56.0560574
---If I give 12 for instance I want to get the date of 12th day of this month.
declare #numDate int = 12;
select dateadd(m, datediff(m,0,getdate()),#numDate - 1 ); --2018-12-12 00:00:00.000
--Or if I give 'Sun' or 'Sat' is it possible to get the dates of these days ?
declare #text nvarchar(20) = 'Sunday';
declare #dateStart date = dateadd(month, datediff(month, 0, sysdatetime()), 0),
#days int =( select (DAY(dateadd(dd,-1,DATEADD(m,1,cast(2018 as varchar(4)) + '-' + cast(12 as varchar(2)) +'-01')))));
declare #dateEnd date = DATEADD(day,#days-1,#dateStart);
;WITH CTE (Dates,EndDate) AS
(
SELECT #dateStart AS Dates,#dateEnd AS EndDate
UNION ALL
SELECT DATEADD(day,1,Dates),EndDate
FROM CTE
WHERE DATEADD(day,1,Dates) <= EndDate
)
SELECT CTE.Dates, DATENAME(DW, CTE.Dates)
FROM CTE
where DATENAME(DW, CTE.Dates) = #text;
Result:
Dates,Day
2018/12/2,Sunday
2018/12/9,Sunday
2018/12/16,Sunday
2018/12/23,Sunday
2018/12/30,Sunday
-- Here is how to get week day name to week day number
DECLARE #T TABLE (Dow INT, NameOfDay VARCHAR(15), ShortName CHAR(3));
WITH Days AS
(
SELECT TOP 7
ROW_NUMBER() OVER(PARTITION BY object_id ORDER BY object_id) AS RowNo
FROM
sys.all_columns
)
INSERT INTO #T
SELECT
RowNo,
DATENAME(WEEKDAY, RowNo - 1),
LEFT(DATENAME(WEEKDAY, RowNo - 1), 3)
FROM
Days
SELECT
*
FROM
#T;
-- Here is how to get start of period
SELECT
DATEADD(DAY, DATEDIFF(DAY, 0, GETDATE()), 0) AS StartOfDay,
DATEADD(WEEK, DATEDIFF(WEEK, 0, GETDATE()), 0) AS StartOfWeek,
DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()), 0) AS StartOfMonth,
DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()), 0) AS StartOfYear;
-- An example
WITH
StartPeriods AS
(
SELECT DATEADD(WEEK, DATEDIFF(WEEK, 0, GETDATE()), 0) AS StartOfWeek
),
SelectedDay AS
(
SELECT
Dow - 1 AS Dow,
(SELECT StartOfWeek FROM StartPeriods) AS StartOfWeek
FROM
#T
WHERE
ShortName = 'Wed'
)
SELECT
DATEADD(DAY, Dow, StartOfWeek)
FROM
SelectedDay;
I have following 2019 data.
Date Calendar_year Weekend_indicator
2019-01-01 2019 weekday
2019-01-01 2019 weekday
2019-01-02 2019 weekday
2019-01-02 2019 weekday
and so on.
I need to give one record a day and another a night value and have it repeated for the entire year's data so that it would look like this.
Date Calendar_year Weekend_indicator day_night
2019-01-01 2019 weekday Day
2019-01-01 2019 weekday Night
2019-01-02 2019 weekday Day
2019-01-02 2019 weekday Night
Here is my code.
DECLARE #Year AS INT,
#FirstDateOfYear DATETIME,
#LastDateOfYear DATETIME
-- You can change #year to any year you desire
SELECT #year = 2019
SELECT #FirstDateOfYear = DATEADD(yyyy, #Year - 1900, 0)
SELECT #LastDateOfYear = DATEADD(yyyy, #Year - 1900 + 1, 0)
-- Creating Query to Prepare Year Data;
WITH cte AS (
SELECT 1 AS DayID,
#FirstDateOfYear AS FromDate,
DATENAME(dw, #FirstDateOfYear) AS Dayname
UNION ALL
SELECT cte.DayID + 1 AS DayID,
DATEADD(d, 1 ,cte.FromDate),
DATENAME(dw, DATEADD(d, 1 ,cte.FromDate)) AS Dayname
FROM cte
WHERE DATEADD(d,1,cte.FromDate) < #LastDateOfYear
)
SELECT c.FromDate AS Date
,#Year as calendar_year
,CHOOSE(datepart(dw, c.FromDate), 'WEEKEND', 'WEEKDAY', 'WEEKDAY',
'WEEKDAY', 'WEEKDAY', 'WEEKDAY', 'WEEKEND') as weekend_indicator
FROM CTE c
CROSS JOIN ( values (1), (2) ) tb (FromDate)
WHERE DayName IN ('Saturday','Sunday')
or dayname not in ('Saturday', 'Sunday')
order by c.FromDate
OPTION (MaxRecursion 1000)
How do I assign the day and the night value and have it repeated?
This simple query might point you in the right direction:
USE TEMPDB
CREATE TABLE #T (DateCol DATE, Calender_Year INT)
INSERT INTO #T VALUES ('20180101', 2018 )
SELECT *
FROM #T
CROSS APPLY (VALUES ('Day'), ('Night') ) AS C (Val)
If you want just to change your code without rewriting it,
add one more row as below:
DECLARE #Year AS INT,
#FirstDateOfYear DATETIME,
#LastDateOfYear DATETIME
-- You can change #year to any year you desire
SELECT #year = 2019
SELECT #FirstDateOfYear = DATEADD(yyyy, #Year - 1900, 0)
SELECT #LastDateOfYear = DATEADD(yyyy, #Year - 1900 + 1, 0);
-- Creating Query to Prepare Year Data;
WITH cte AS (
SELECT 1 AS DayID,
#FirstDateOfYear AS FromDate,
DATENAME(dw, #FirstDateOfYear) AS Dayname
UNION ALL
SELECT cte.DayID + 1 AS DayID,
DATEADD(d, 1 ,cte.FromDate),
DATENAME(dw, DATEADD(d, 1 ,cte.FromDate)) AS Dayname
FROM cte
WHERE DATEADD(d,1,cte.FromDate) < #LastDateOfYear
)
SELECT c.FromDate AS Date
,#Year as calendar_year
,CHOOSE(datepart(dw, c.FromDate), 'WEEKEND', 'WEEKDAY', 'WEEKDAY',
'WEEKDAY', 'WEEKDAY', 'WEEKDAY', 'WEEKEND') as weekend_indicator,
case tb.FromDate when 1 then 'Day' else 'Night' end as day_night -----<<<<<<-----
FROM CTE c
CROSS JOIN ( values (1), (2) ) tb (FromDate)
WHERE DayName IN ('Saturday','Sunday')
or dayname not in ('Saturday', 'Sunday')
order by c.FromDate
OPTION (MaxRecursion 1000)
I use a table variable for create this solution.
DECLARE
#Year int
,#FirstDateOfYear date
,#LastDateOfYear date
,#date_loop date
SET #YEAR = 2019
SELECT
#FirstDateOfYear = DATEADD(yyyy, #Year - 1900, 0)
,#LastDateOfYear = DATEADD(yyyy, #Year - 1900 + 1, 0)
,#date_loop = DATEADD(yyyy, #Year - 1900, 0) --initialize variable for loop
DECLARE #date_table TABLE ([Date] date, [Calendar_year] int, [Weekend_indicator] varchar(10), [day_night] varchar(5))
WHILE #date_loop < #LastDateOfYear
BEGIN
INSERT #date_table
SELECT d.[Date], d.[Calendar_year], d.[Weekend_indicator], ca.[day_night]
FROM (
SELECT
#date_loop AS [Date]
,YEAR(#date_loop) AS [Calendar_year]
,CHOOSE(datepart(dw, #date_loop), 'weekend', 'weekday', 'weekday','weekday', 'weekday', 'weekday', 'weekend') AS [Weekend_indicator]) AS d
CROSS APPLY (
SELECT 'Day' AS [day_night]
UNION
SELECT 'Night'
) AS ca
SET #date_loop = DATEADD(day,1,#date_loop)
END
SELECT *
FROM #date_table
If you don't have a calendar or numbers table, you can use an ad-hoc table
Example
Declare #Date1 date = '2019-01-01'
Declare #Date2 date = '2019-12-31'
Select [Date] = d
,Calendar_year = datepart(YEAR,d)
,Weekend_indicator = case when datename(WEEKDAY,d) in ('Saturday','Sunday') then 'weekend' else 'weekday' end
,day_night
From (
Select Top (DateDiff(DAY,#Date1,#Date2)+1) D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),#Date1)
From master..spt_values n1,master..spt_values n2
) A
Cross Join (values ('Day'),('Night') ) b(day_night)
Order by D,day_night
Returns
Date Calendar_year Weekend_indicator day_night
2019-01-01 2019 weekday Day
2019-01-01 2019 weekday Night
2019-01-02 2019 weekday Day
2019-01-02 2019 weekday Night
...
2019-12-30 2019 weekday Day
2019-12-30 2019 weekday Night
2019-12-31 2019 weekday Day
2019-12-31 2019 weekday Night
Revised answer: I see an unused (VALUES ...) clause which could be used like so:
CROSS JOIN (VALUES ('Day'), ('Night') ) whatever(day_night)
DB Fiddle
I have a table in SQL Server which have following attributes.
ProjectID || Start_Date || End Date || Duration(Days)
1 10-Jan-2013 5
2 02-FEB 2013 16
3 26-Mar-2013 50
. . .
I want to find start dates based upon the qualified days (Monday-Friday). For example for End Date: 10 January start date will be 04 January as 5 and 6 January are Saturday and Sunday.
I want to know how this could be possible in T-SQL (Function,Custom T-SQL Block). Any guidance and help is highly appreciated.
This should do it:
WITH tblProjects2 AS (
SELECT ProjectId, DATEADD(DAY, -Duration, EndDate) AS StartDate FROM tblProjects
)
SELECT ProjectId,
CASE WHEN DATENAME(DW, StartDate) = 'Sunday' THEN DATEADD(day, -2, StartDate)
WHEN DATENAME(DW, StartDate) = 'Saturday' THEN DATEADD(day, -1, StartDate)
ELSE StartDate END AS ProperStartDate
FROM tblProjects2
The approach is rather simple - when your new date falls on a weekend, subtract 1 or 2 days, depending on whether it's Saturday or Sunday respectively.
Test case structure for tblProjects:
CREATE TABLE [dbo].[tblProjects](
[ProjectId] [int] NULL,
[StartDate] [date] NULL,
[EndDate] [date] NULL,
[Duration] [int] NULL
)
Test case data for same:
INSERT INTO tblProjects VALUES (1, NULL, '10-Jan-2013', 5);
INSERT INTO tblProjects VALUES (2, NULL, '02-FEB-2013', 16);
INSERT INTO tblProjects VALUES (3, NULL, '26-Mar-2013', 50);
EDIT - Same functionality, using a function:
CREATE FUNCTION dbo.getStartDate(#EndDate Date, #Duration int)
RETURNS DATE
AS
BEGIN
DECLARE #newDate DATE;
SET #newDate = DATEADD(day, -#Duration, #EndDate);
RETURN (CASE
WHEN DATENAME(DW, #newDate) = 'Sunday' THEN DATEADD(day, -2, #newDate)
WHEN DATENAME(DW, #newDate) = 'Saturday' THEN DATEADD(day, -1, #newDate)
ELSE #newDate END)
END;
Then you can rewrite the above query like this:
SELECT ProjectId, dbo.getStartDate(EndDate, Duration) AS StartDate
FROM tblProjects
A bit harder to read, but slightly faster
SELECT ProjectId,
DATEADD(d, -Duration - CASE DATEDIFF(d, +Duration, EndDate) % 7
WHEN 5 THEN 1
WHEN 6 THEN 2
ELSE 0 END
, EndDate)
FROM tblProjects