JCoder23
JCoder23

Reputation: 551

SQL query to calculate days worked per Month

Im stuck on a SQL query. Im using SQL Server.

Given a table that contains Jobs with a start and end date. These jobs can span days or months. I need to get the total combined number of days worked each month for all jobs that intersected those months.

Jobs

-----------------------------------
JobId | Start  | End    | DayRate |
-----------------------------------
1     | 1.1.13 | 2.2.13 | 2500    |
2     | 5.1.13 | 5.2.13 | 2000    |
3     | 3.3.13 | 2.4.13 | 3000    |

The results i need are:

Month | Days
--------------
Jan   | 57
Feb   | 7
Mar   | 28
Apr   | 2

Any idea how i would right such a query ?

I would also like to work out the SUM for each month based on multiplying the dayrate by number of days worked for each job, how would i add this to the results ?

Thanks

Upvotes: 2

Views: 2252

Answers (3)

t-clausen.dk
t-clausen.dk

Reputation: 44316

This only have 1 recursive call instead of 1 for each row. I imagine this will perform better than the chosen answer when you have large amount of data.

declare @t table(JobId int, Start date, [End] date, DayRate int)
insert @t values
(1,'2013-01-01','2013-02-02', 2500),(2,'2013-01-05','2013-02-05', 2000),(3,'2013-03-03', '2013-04-02',3000)

;WITH a AS 
(

SELECT min(Start) s, max([End]) e
FROM @t
), b AS
(
SELECT s, e from a
UNION ALL
SELECT dateadd(day, 1, s), e
FROM b WHERE s <> e
)
SELECT 
     MONTH(b.s) AS [Month] 
    ,YEAR(b.s) AS [Year]
    ,COUNT(*) AS [Days]
    ,SUM(DayRate) MonthDayRate
FROM b
join @t t
on b.s between t.Start and t.[End]
GROUP BY MONTH(b.s),YEAR(b.s)
OPTION (MAXRECURSION 0)

Result:

Month   Year    Days    MonthDayRate
1       2013    58      131500
2       2013    7       15000
3       2013    29      87000
4       2013    2       6000

Upvotes: 0

Nenad Zivkovic
Nenad Zivkovic

Reputation: 18559

You can use recursive CTE to extract all days from start to end for each JobID and then just group by month (and year I guess).

;WITH CTE_TotalDays AS 
(
    SELECT [Start] AS DT, JobID FROM dbo.Jobs
    UNION ALL
    SELECT DATEADD(DD,1,c.DT), c.JobID FROM CTE_TotalDays c
    WHERE  c.DT < (SELECT [End] FROM Jobs j2 WHERE j2.JobId  = c.JobID)
)
SELECT 
     MONTH(DT) AS [Month] 
    ,YEAR(DT) AS [Year]
    ,COUNT(*) AS [Days] 
FROM CTE_TotalDays
GROUP BY MONTH(DT),YEAR(DT)
OPTION (MAXRECURSION 0)

SQLFiddle DEMO

PS: There are 58 days in Jan in your example and not 57 ;)

Upvotes: 2

Igor Borisenko
Igor Borisenko

Reputation: 3866

You can do it using following approach:

/* Your table with periods */
declare @table table(JobId int, Start date, [End] date, DayRate money)

INSERT INTO @table (JobId , Start, [End], DayRate)
VALUES
(1, '20130101','20130202', 2500),
(2,'20130105','20130205', 2000),
(3,'20130303','20130402' , 3000 )

/* create table where stored all possible dates
   if this code are supposed to be executed often you can create 
   table with dates ones to avoid overhead of filling it */
declare @dates table(d date)

declare @d date='20000101'
WHILE @d<'20500101'
    BEGIN 
        INSERT INTO @dates (d) VALUES (@d)
        SET @d=DATEADD(DAY,1,@d)
    END;

/* and at last get desired output */

SELECT YEAR(d.d) [YEAR], DATENAME(month,d.d) [MONTH], COUNT(*) [Days]
FROM @dates d
     CROSS JOIN @table t
WHERE d.d BETWEEN t.Start AND t.[End]
GROUP BY YEAR(d.d), DATENAME(month,d.d)

Upvotes: 0

Related Questions