How do I calculate amount of time in each week between two dates - SQL Server - sql

[EDITED TO SIMPLIFY]
I have 500+ records. All of which have a reference number, a start date, an end date and a total machining time.
Ref StartDate EndDate MachineTimeHours
123 24/01/2020 30/01/2020 28
321 25/02/2020 27/02/2020 18
Starting at the start date, I need to calculate how many machining hours fall into 1 week and how many fall into the next.
Our working days are Monday to Thursday 8 Hours & Friday 4 Hours.
Ref 321 has a start of 25/2 which is a Tuesday and a finish date of 27/2 which is a Thursday in the same week. This will calculate as all 18 hours being in the same week.
Ref 123 has a start of 24/01. This is a Friday in Week 4 of 2020.
Based on my rules, that would be 4 hours in week 4 and 24 Hours in week 5.
I have a table called 'DatesList' which has all days on it (as well as week number and working hours).
I need my table to list each record for each week and I'll do the grouping on a separate report.
In effect I'd like
Ref StartDate EndDate MachineTimeHours Week Hours
123 24/01/2020 30/01/2020 28 4 4
123 24/01/2020 30/01/2020 28 5 24
321 25/02/2020 27/02/2020 18 9 18

You can start with creating some reference tables.
For the example those are just temporary tables.
Reference data:
--
-- Reference tables
--
CREATE TABLE #ref_calendar
(
CalDate DATE PRIMARY KEY,
DayOfWeek SMALLINT NOT NULL,
WeekNr SMALLINT NOT NULL,
IsHoliday BIT NOT NULL DEFAULT 0
);
DECLARE #year int = 2020;
SET DATEFIRST 1; -- 1: monday start
;WITH RCTE_DATES AS
(
SELECT
DATEFROMPARTS(#year, 1, 1) AS caldate
UNION ALL
SELECT dateadd(day, 1, caldate)
FROM RCTE_DATES
WHERE caldate <= DATEFROMPARTS(#year, 12, 31)
)
INSERT INTO #ref_calendar (CalDate, DayOfWeek, WeekNr)
SELECT
caldate,
DATEPART(weekday, caldate) AS DayOfWeek,
DATEPART(week, caldate) AS WeekNr
FROM rcte_dates c
WHERE NOT EXISTS
(
SELECT 1
FROM #ref_calendar ref
WHERE ref.CalDate = c.caldate
)
OPTION (MAXRECURSION 366);
CREATE TABLE #ref_workhours
(
Id INT IDENTITY(1,1) PRIMARY KEY,
DayOfWeek SMALLINT NOT NULL,
WorkHours DECIMAL(4,2) NOT NULL,
ActiveFrom DATE NOT NULL DEFAULT GetDate(),
ActiveTill DATE
);
INSERT INTO #ref_workhours
(DayOfWeek, WorkHours) VALUES
(1, 8.0), (2, 8.0), (3, 8.0), (4, 8.0), (5, 4.0),
(6, 0), (7, 0);
Some sample data:
--
-- Sample data
--
CREATE TABLE YourDateRangeTable
(
Id INT IDENTITY(1,1) PRIMARY KEY,
JobNumber INT NOT NULL,
PartNumber VARCHAR(8) NOT NULL,
Machine CHAR(3) NOT NULL,
StartDate DATE NOT NULL,
EndDate DATE NOT NULL
);
INSERT INTO YourDateRangeTable
(JobNumber, PartNumber, Machine, StartDate, EndDate) values
(12345, 'XYZ321', 'DL8', '2020-01-24', '2020-01-30');
Then you can run a query that uses the reference tables.
SELECT JobNumber, PartNumber, Machine
, YEAR(cal.CalDate) AS [Year]
, cal.WeekNr AS [Week]
, SUM(w.WorkHours) AS [Hours]
FROM YourDateRangeTable t
JOIN #ref_calendar cal
ON cal.CalDate >= t.StartDate
AND cal.CalDate < t.EndDate
JOIN #ref_workhours w
ON w.DayOfWeek = cal.DayOfWeek
GROUP BY JobNumber, PartNumber, Machine
, YEAR(cal.CalDate), cal.WeekNr;
Returns:
JobNumber PartNumber Machine Year Week Hours
12345 XYZ321 DL8 2020 4 4.00
12345 XYZ321 DL8 2020 5 24.00
A test on db<>fiddle here

You can get all the detes of two given date and also weeknumber and a case statement for the working hour. Based on the result from the inner query write an outer query which will give the sum of total working hour.
Here is the given query.
DECLARE #MinDate DATE = '20200124',
#MaxDate DATE = '20200130'
--Fri Week 4 = 4 hours
--Mon Week 5 = 8 hours
--Tue Week 5 = 8 hours
--Wed Week 5 = 8 hours
Select WeekNo, SUM(WorkingHour) as TotalWorkingHour from(
Select [DATE], DATEPART(WEEK, [DATE]) - DATEPART(WEEK, DATEADD(MM, DATEDIFF(MM,0,[DATE]), 0))+ 1 as WeekNo,
DATENAME(weekday, [DATE]) as WeekDay, Case DATENAME(weekday, [DATE])
when 'Friday' then 4
when 'Monday' then 8
when 'Tuesday' then 8
when 'Wednesday' then 8
else 0
end as WorkingHour from(
SELECT TOP (DATEDIFF(DAY, #MinDate, #MaxDate) + 1)
Date = DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.object_id) - 1, #MinDate)
FROM sys.all_objects a
CROSS JOIN sys.all_objects b
)a
)b group by WeekNo
It will give the result as below:
WeekNo TotalWorkingHour
------------------------
4 4
5 24
You can find the demo Here.

Related

How to get 1st and 3rd Saturday and all Sunday between 2 dates using sql

Given a date range, I'd like to return all of the Saturdays and Sundays that fall within that range, with these conditions:
Include Saturdays only if their ordinal position is either the 1st or 3rd Saturday within the month they fall (not within the entire range).
Include all Sundays, along with the ordinal position of that Sunday within the month it falls.
So for example, if the start date is Aug 15, 2021 and the end date is Sep 20, 2021, the output would be:
Dates Saturday Number (in its own month)
---------- ---------------
2021-08-21 3
2021-09-04 1
2021-09-18 3
Dates Sunday Number (in its own month)
---------- ---------------
2021-08-15 3
2021-08-22 4
2021-08-29 5
2021-09-05 1
2021-09-12 2
2021-09-19 3
Then I can take the date range in total (37 days), and subtract the Sundays (6), and the 1st and 3rd Saturdays from each month (3), to end at 28.
Tried this query
DECLARE #sd DATETIME = '2021-08-15' DECLARE #ed DATETIME =
'2021-09-20'
--find first saturday WHILE DATEPART(dw, #sd)<>7 BEGIN SET #sd = DATEADD(dd,1,#sd) END
--get next saturdays ;WITH Saturdays AS (
--initial value SELECT #sd AS MyDate, 1 AS SatNo UNION ALL
--recursive part SELECT DATEADD(dd,7,MyDate) AS MyDate, CASE WHEN SatNo + 1 =6 THEN 1 ELSE SatNo+1 END AS SatNo FROM Saturdays
WHERE DATEADD(dd,7,MyDate)<=#ed
) SELECT * FROM Saturdays WHERE SatNo IN (1,3) OPTION(MAXRECURSION 0)
it does not work properly.
Also Tried this solution
Get number of weekends between two dates in SQL
for calculate week days, but I want only 1st and 3 Saturday and all Sundays
Get a calendar table; it makes this type of business problem a breeze. Here's a simpler one:
CREATE TABLE dbo.Calendar
(
TheDate date PRIMARY KEY,
WeekdayName AS (CONVERT(varchar(8), DATENAME(WEEKDAY, TheDate))),
WeekdayInstanceInMonth tinyint
);
;WITH x(d) AS -- populate with 2020 -> 2029
(
SELECT CONVERT(date, '20200101')
UNION ALL
SELECT DATEADD(DAY, 1, d)
FROM x
WHERE d < '20291231'
)
INSERT dbo.Calendar(TheDate)
SELECT d FROM x
OPTION (MAXRECURSION 0);
;WITH c AS
(
SELECT *, rn = ROW_NUMBER() OVER
(PARTITION BY YEAR(TheDate), MONTH(TheDate), WeekdayName
ORDER BY TheDate)
FROM dbo.Calendar
)
UPDATE c SET WeekdayInstanceInMonth = rn;
Now your query is easy:
DECLARE #start date = '20210815', #end date = '20210920';
SELECT Dates = TheDate,
[Saturday Number] = WeekdayInstanceInMonth
FROM dbo.Calendar
WHERE TheDate >= #start
AND TheDate <= #end
AND WeekdayName = 'Saturday'
AND WeekdayInstanceInMonth IN (1,3);
SELECT Dates = TheDate,
[Sunday Number] = WeekdayInstanceInMonth
FROM dbo.Calendar
WHERE TheDate >= #start
AND TheDate <= #end
AND WeekdayName = 'Sunday';
Results (db<>fiddle example here):
Dates Saturday Number
---------- ---------------
2021-08-21 3
2021-09-04 1
2021-09-18 3
Dates Sunday Number
---------- ---------------
2021-08-15 3
2021-08-22 4
2021-08-29 5
2021-09-05 1
2021-09-12 2
2021-09-19 3
And to get just the number 28:
DECLARE #start date = '20210815', #end date = '20210920';
SELECT DATEDIFF(DAY, #start, #end) + 1
-
(SELECT COUNT(*)
FROM dbo.Calendar
WHERE TheDate >= #start
AND TheDate <= #end
AND WeekdayName = 'Saturday'
AND WeekdayInstanceInMonth IN (1,3))
-
(SELECT COUNT(*)
FROM dbo.Calendar
WHERE TheDate >= #start
AND TheDate <= #end
AND WeekdayName = 'Sunday');
Assuming datefirst is set for Sunday:
(
day(dt) +
datepart(weekday, dateadd(dt, 1 - day(dt))) % 7
) / 7 as SaturdayNumber,
(
day(dt) - 1 +
datepart(weekday, dateadd(dt, 1 - day(dt)))
) / 7 as SundayNumber
For all dates this essentially computes a week number (0-5) within the month, relative to Saturday/Sunday.

For each quarter between two dates, add rows quarter by quarter in SQL SERVER

I have a table, with types int, datetime, datetime:
id start date end date
-- ---------- ----------
1 2019-04-02 2020-09-17
2 2019-08-10 2020-08-10
Here is create/insert:
CREATE TABLE dbo.something
(
id int,
[start date] datetime,
[end date] datetime
);
INSERT dbo.something(id,[start date],[end date])
VALUES(1,'20190402','20200917'),(2,'20190810','20200810');
What is a SQL query that can produce these results:
id Year Quarter
-- ---- ----------
1 2019 2
1 2019 3
1 2019 4
1 2020 1
1 2020 2
1 2020 3
2 2019 3
2 2019 4
2 2020 1
2 2020 2
2 2020 3
Just use a recursive CTE. This version switches to counting quarters from year 0:
with cte as (
select id,
year(start_date) * 4 + datepart(quarter, start_date) - 1 as yyyyq,
year(end_date) * 4 + datepart(quarter, end_date) - 1 as end_yyyyq
from t
union all
select id, yyyyq + 1, end_yyyyq
from cte
where yyyyq < end_yyyyq
)
select id, yyyyq / 4 as year, (yyyyq % 4) + 1 as quarter
from cte;
Here is a db<>fiddle.
If you cannot make another reference table/etc, you can use DATEDIFF (and DATEPART) using quarters, and then some simple date arithmetic.
The logic below is simply to find, for each startdate, the first quarter and then the number of additional quarters to get to the maximum. Then do a SELECT where the additional quarters are added to the startdate, to get each quarter.
The hardest part of the query to understand imo is the WITH numberlist section - all this does is generate a series of integers between 0 and the maximum number of quarters difference. If you already have a numbers table, you can use that instead.
Key code part is below, and here's a full DB_Fiddle with some additional test data.
CREATE TABLE #yourtable (id int, startdate date, enddate date)
INSERT INTO #yourtable (id, startdate, enddate) VALUES
(1, '2019-04-02', '2020-09-17'),
(2, '2019-08-10', '2020-08-20')
; WITH number_list AS
-- list of ints from 0 to maximum number of quarters
(SELECT n
FROM (SELECT ones.n + 10*tens.n AS n
FROM (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) ones(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) tens(n)
) AS a
WHERE n <= (SELECT MAX(DATEDIFF(quarter,startdate,enddate)) FROM #yourtable)
)
SELECT id,
YEAR(DATEADD(quarter, number_list.n, startdate)) AS [Year],
DATEPART(quarter, DATEADD(quarter, number_list.n, startdate)) AS [Quarter]
FROM (SELECT id, startdate, DATEDIFF(quarter,startdate,enddate) AS num_additional_quarters FROM #yourtable) yt
CROSS JOIN number_list
WHERE number_list.n <= yt.num_additional_quarters
DROP TABLE #yourtable
First create a date dimension table which contains date, corresponding quarter and year. Then use below query to get the result. Tweak column and table name according to your schema.
with q_date as
(
select 1 as id, '2019-04-02' :: date as start_date, '2020-09-17' :: date as end_date
UNION ALL
select 2 as id, '2019-08-10' :: date as start_date, '2020-08-10' :: date as end_date
)
select qd.id, dd.calendar_year, dd.calendar_quarter_number
from dim_date dd, q_date qd
where dd.date_dmk between qd.start_date and qd.end_date
group by qd.id, dd.calendar_year, dd.calendar_quarter_number
order by qd.id, dd.calendar_year, dd.calendar_quarter_number;

Grouping sql rows by weeks

I have a table
DATE Val
01-01-2020 1
01-02-2020 3
01-05-2020 2
01-07-2020 8
01-13-2020 3
...
I want to summarize these values by the following Sunday. For example, in the above example:
1-05-2020, 1-12-2020, and 1-19-2020 are Sundays, so I want to summarize these by those dates.
The final result should be something like
DATE SUM
1-05-2020 6 //(01-01-2020 + 01-02-2020 + 01-05-2020)
1-12-2020 8
1-19-2020 3
I wasn't certain if the best place to start would be to create a temp calendar table, and then try to join backwards based on that? Or if there was an easier way involving DATEDIFF. Any help would be appreciated! Thanks!
Here's a solution that uses DATEADD & DATEPART to calculate the closest Sunday.
With a correction for a different setting of ##datefirst.
(Since the datepart weekday values are different depending on the DATEFIRST setting)
Sample data:
create table #TestTable
(
Id int identity(1,1) primary key,
[Date] date,
Val int
);
insert into #TestTable
([Date], Val)
VALUES
('2020-01-01', 1)
, ('2020-01-02', 3)
, ('2020-01-05', 2)
, ('2020-01-07', 8)
, ('2020-01-13', 3)
;
Query:
WITH CTE_DATA AS
(
SELECT [Date], Val
, DATEADD(day,
((7-(##datefirst+datepart(weekday, [Date])-1)%7)%7),
[Date]) AS Sunday
FROM #TestTable
)
SELECT
Sunday AS [Date],
SUM(Val) AS [Sum]
FROM CTE_DATA
GROUP BY Sunday
ORDER BY Sunday;
Date | Sum
:--------- | --:
2020-01-05 | 6
2020-01-12 | 8
2020-01-19 | 3
db<>fiddle here
Extra:
Apparently the trick of adding the difference of weeks from day 0 to day 6 also works independently from the DATEFIRST setting.
So this query will return the same result for the sample data.
WITH CTE_DATA AS
(
SELECT [Date], Val
, CAST(DATEADD(week, DATEDIFF(week, 0, DATEADD(day, -1, [Date])), 6) AS DATE) AS Sunday
FROM #TestTable
)
SELECT
Sunday AS [Date],
SUM(Val) AS [Sum]
FROM CTE_DATA
GROUP BY Sunday
ORDER BY Sunday;
The subtraction of 1 day makes sure that if the date is already a Sunday that it isn't calculated to the next Sunday.
Here is a way to do it:
nb:1-13-2020 wont show cuz its not a sunday
with cte as
(
select cast('01-01-2020'as Date) as Date, 1 as Val
union select '01-02-2020' , 3
union select '01-05-2020' , 2
union select '01-07-2020' , 8
)
select Date, max(dateadd(dd,number,Date)), sum(distinct Val) as SUM
from master..spt_values a inner join cte on Date <= dateadd(dd,number,Date)
where type = 'p'
and year(dateadd(dd,number,Date))=year(Date)
and DATEPART(dw,dateadd(dd,number,Date)) = 7
group by Date
Output:
Date (No column name) SUM
2020-01-01 2020-12-26 1
2020-01-02 2020-12-26 3
2020-01-05 2020-12-26 2
2020-01-07 2020-12-26 8
Here is a simple solution. Putting your values into a temporary table and viewing the results on that table:
DECLARE #dates TABLE
(
mDATE DATE,
Val INT,
Sunday DATE
)
INSERT INTO #dates (mDATE,Val) VALUES
('01-01-2020',1),('01-02-2020',3),('01-05-2020',2),('01-07-2020',8),('01-13-2020',3)
UPDATE #dates
SET Sunday = dateadd(week, datediff(week, 0, mDATE), 6)
SELECT Sunday,SUM(Val) AS Val FROM #dates
GROUP BY Sunday
OUTPUT:
Sunday Val
2020-01-05 4
2020-01-12 10
2020-01-19 3

Find numbers of weeks in date range(from date-To date) in SQL SERVER

I have two dates FromDate and ToDate. Start is always Monday and ToDate is always Friday.
I want to find number of weeks separately not just count.
I want to show week1, week2, week3, etc report in given date range. (ie Weekly product counts).
I have tried this :
SELECT * FROM WeekDate
WHERE DATEDIFF(Week,GETDATE(),'2013-09-20')=0
It gives all values not just for week 0.
Can any one help me about this?
Table structure
CREATE TABLE [dbo].[WeekDate]
(
[id] [int] IDENTITY(1,1) NOT NULL,
[Counter] [int] NULL,
[CDate] [datetime] NOT NULL
) ON [PRIMARY]
and data is
1 1 2013-09-17 00:00:00.000
3 2 2013-09-18 00:00:00.000
4 6 2013-09-19 00:00:00.000
5 2 2013-09-23 00:00:00.000
6 3 2013-09-24 00:00:00.000
7 4 2013-10-01 00:00:00.000
Hard to say without example of output, but could be something like this:
select
sum(Counter),
datediff(day, #FromDate, CDate) / 7 as ddiff
from WeekDate
-- if you need to filter out rows
where CDate >= #FromDate and CDate <= #ToDate
group by datediff(day, #FromDate, CDate) / 7
sql fiddle demo
I think you want sum of product by weekly and if so then try this as you said i have taken start date as Monday and end date as Friday
declare #StartDate datetime,
#EndDate datetime;
select #StartDate = '2013-09-16',
#EndDate = '2013-10-04';
;with cte as
(
select #StartDate StartDate,
DATEADD(wk, DATEDIFF(wk, 0, #StartDate), 6) EndDate
union all
select dateadd(ww, 1, StartDate),
dateadd(ww, 1, EndDate)
from cte
where dateadd(ww, 1, StartDate)<= #EndDate
)
select (Select COUNT(Cdate) from WeekDate where Cdate between StartDate and EndDate) count1 , StartDate , EndDate
from cte

Need Sql query output like this format

I have one time sheet table when i fetch the records it will display like this for one week.
This record is from one week from June 10 to June 16
EmpId Monday Tuesday Wednesday Thursday Friday Saturday Sunday StartDate EndDate
1 08:00 08:12 00:00 04:00 00:00 03:00 00:00 05/10/2013 05/16/2013
Need the output like this
Empid Monday startdate EndDate
1 08:00 05/10/2013 05/10/2013
1 08:12 05/11/2013 05/11/2013
1 04:00 05/13/2013 05/13/2013
1 03:00 05/15/2013 05/15/2013
This is basically an unpivot query. Because of the time fields, this version chooses to do it explicitly (using cross join and case) rather than using unpivot:
select t.*
from (select h.empid,
(case when n = 0 then Monday
when n = 1 then Tuesday
when n = 2 then Wednesday
when n = 3 then Thursday
when n = 4 then Friday
when n = 5 then Saturday
when n = 6 then Sunday
end) as hours,
(startdate + n) as StartDate,
(startdate + n) as EndDate
from hours h join
(select 0 as n union all
select 1 union all
select 2 union all
select 3 union all
select 4 union all
select 5 union all
select 6
) n
) t
where hours > 0;
You can see the SQLFiddle here. And there is no problem running this on larger amounts of data.
This should get you started:
declare #Hours as Table ( EmpId Int, Monday Time, Tuesday Time, Wednesday Time,
Thursday Time, Friday Time, Saturday Time, Sunday Time, StartDate Date, EndDate Date );
insert into #Hours
( EmpId, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday, StartDate, EndDate )
values
( 1, '08:00', '08:12', '00:00', '04:00', '00:00', '03:00', '00:00', '20130510', '20130516' );
select * from #Hours;
with Week as (
-- Build a table of all of the dates in the work week.
select StartDate as WorkDate, EndDate
from #Hours
union all
select DateAdd( day, 1, WorkDate ), EndDate
from Week
where WorkDate < EndDate )
-- Output the result.
select EmpId,
case DatePart( weekday, W.WorkDate )
when 1 then H.Monday
when 2 then H.Tuesday
when 3 then H.Wednesday
when 4 then H.Thursday
when 5 then H.Friday
when 6 then H.Saturday
when 7 then H.Sunday
end as Hours,
WorkDate as StartDate, WorkDate as EndDate,
DatePart( weekday, W.WorkDate ) as DayOfWeek
from Week as W inner join
#Hours as H on H.StartDate <= W.WorkDate and W.WorkDate <= H.EndDate;
with Week as (
-- Build a table of all of the dates in the work week.
select StartDate as WorkDate, EndDate
from #Hours
union all
select DateAdd( day, 1, WorkDate ), EndDate
from Week
where WorkDate < EndDate ),
DaysHours as (
-- Build a table of the hours assigned to each date.
select EmpId,
case DatePart( weekday, W.WorkDate )
when 1 then H.Monday
when 2 then H.Tuesday
when 3 then H.Wednesday
when 4 then H.Thursday
when 5 then H.Friday
when 6 then H.Saturday
when 7 then H.Sunday
end as Hours,
WorkDate as StartDate, WorkDate as EndDate
from Week as W inner join
#Hours as H on H.StartDate <= W.WorkDate and W.WorkDate <= H.EndDate )
-- Output the non-zero hours.
select EmpId, Hours, StartDate, EndDate
from DaysHours
where Hours <> Cast( '00:00' as Time );
This works for a single row, but you will need to make some changes if your dataset grows.