J. Michiels
J. Michiels

Reputation: 335

Sum of value by month where data contains start and end dates

I have a simple table, containing projects with a certain value that is active between a start and end date. It looks like this:

+----+---------+------------+------------+-------+
| Id | Project | StartDate  |  EndDate   | Value |
+----+---------+------------+------------+-------+
|  1 | AAA     | 2018-01-01 | NULL       |   100 |
|  2 | AAA     | 2018-04-12 | NULL       |   50  |
|  3 | BBB     | 2018-01-01 | 2018-03-01 |   20  |
|  4 | BBB     | 2018-01-01 | NULL       |   200 |
+----+---------+------------+------------+-------+

I want to create a view that renders one record per month / project showing the sum of the Value column in that month. Example:

+----+-------+---------+-------+
| Id | Month | Project | Value |
+----+-------+---------+-------+
|  1 | JAN   | AAA     |   100 |
|  2 | FEB   | AAA     |   100 |
|  3 | MAR   | AAA     |   100 |
|  4 | APR   | AAA     |   150 |
|  5 | MAY   | AAA     |   150 |
|  6 | JUN   | AAA     |   150 |
|  7 | JAN   | BBB     |   220 |
|  8 | FEB   | BBB     |   220 |
|  9 | MAR   | BBB     |   220 |
| 10 | APR   | BBB     |   200 |
| 11 | MAY   | BBB     |   200 |
| 12 | JUN   | BBB     |   200 |
+----+-------+---------+-------+

The day of the month does not matter. For example Id 2 from the projects table shows that project AAA gets an additional value of 50 as of 2018-04-12. This means that the SUM of April and onward should be 150.

For project BBB, you see that a project is ended on 2018-03-01. This means that the sum of BBB should be decreased with 20 as of APRIL (not March!) because the project was still active in March.

I want to render the months until the current month (date of query execution). So in my example, I have executed this query somewhere in June 2018.

This has to be run on SQL Server 2012.

Here are the scripts for the table and some dummy data:

CREATE TABLE [TestProject] (
    [Id] INT IDENTITY(1,1) PRIMARY KEY,
    [Project] NVARCHAR(50) NOT NULL,
    [StartDate] DATE NOT NULL,
    [EndDate] DATE NULL,
    [Value] INT NOT NULL
)

INSERT INTO [TestProject] ([Project], [StartDate], [EndDate], [Value])
VALUES
('AAA','2018-01-01',NULL,100),
('AAA','2018-04-12',NULL,50),
('BBB','2018-01-01','2018-03-01',200),
('BBB','2018-01-01',NULL,20);

Upvotes: 3

Views: 1026

Answers (1)

Salman Arshad
Salman Arshad

Reputation: 272406

You just need to build a list of dates between MIN(StartDate) and today, rest is straight forward:

DECLARE @TestProject TABLE (Id INT IDENTITY(1, 1) PRIMARY KEY, Project NVARCHAR(50) NOT NULL, StartDate DATE NOT NULL, EndDate DATE NULL, Value INT NOT NULL);
INSERT INTO @TestProject VALUES
('AAA', '2018-01-01', NULL, 100),
('AAA', '2018-04-12', NULL, 50),
('BBB', '2018-01-01', '2018-03-01', 200),
('BBB', '2018-01-01', NULL, 20);

WITH cte AS (
    SELECT DATEADD(DAY, 1, EOMONTH((SELECT MIN(StartDate) FROM @TestProject), -1)) AS ym
    UNION ALL
    SELECT DATEADD(MONTH, 1, ym)
    FROM cte
    WHERE DATEADD(MONTH, 1, ym) <= CURRENT_TIMESTAMP
)

SELECT ym, Project, SUM(Value)
FROM cte
LEFT JOIN @TestProject ON DATEADD(DAY, 1, EOMONTH(StartDate, -1)) <= ym AND (
    EndDate IS NULL OR ym <= DATEADD(DAY, 1, EOMONTH(EndDate, -1))
)
GROUP BY ym, Project

In the above example, DATEADD(DAY, 1, EOMONTH(expr, -1)) function is used to generate start of month for specified date.

Upvotes: 1

Related Questions