SQL Query to Count number of values in a column Per Month Conditionally - sql

id date sales req arivve
1 01/01/2015 100 accept deny
2 01/01/2015 100 deny deny
3 02/01/2015 100 accept accept
4 03/01/2015 100 accept deny
What I need is to count the number of 'accept' value in both req and arrive column per month range. This is the expected output
Month StartMonth EndMonth TotalSales reqCount arriveAcount
1 01/01/2015 01/31/2015 200 1 0
2 02/01/2015 02/28/2015 100 1 1
3 03/01/2015 03/31/2015 100 1 0
4 04/01/2015 04/30/2015 0 0 0
This is what I have already (input of start and end year is provided by user so the month tange is dynamic)
--step 1
select id, date, capacity, sales, req, arrive
into #First
from Sales
where date between #StartDate and #EndDate;
--step 2 b
WITH cte
AS
(SELECT
FORMAT(MONTH([schedule_date]), '0#')
+ '-' + CONVERT(VARCHAR(20), YEAR([schedule_date])) monthyear
,([capacity]), request_status, arrival_status
FROM #First)
SELECT
(SELECT DATEADD(MONTH, SUBSTRING(monthyear, 1, 2) - 1, DATEADD(YEAR, SUBSTRING(monthyear, 4, 4) - 1900, 0))) StartDate
,(SELECT DATEADD(DAY, -1, DATEADD(MONTH, CAST(SUBSTRING(monthyear, 1, 2) AS INT), DATEADD(YEAR, SUBSTRING(monthyear, 4, 4) - 1900, 0)))) EndDate
,SUM(capacity) totalsales
FROM cte
GROUP BY monthyear

Try:
select DATEPART(month, date)
, date as StartMonth
, dateadd(day, -1 , DATEADD(month, 1, date))
, sum(sales)
, sum(case when req = 'accept' then 1 else 0 end) reqCount
, sum(case when arivve = 'accept' then 1 else 0 end) arriveCount
from mytable
group by date

Related

My count CTE returning blanks, how can I get it return as 0?

CTE created to count the number of days left from today's date to end of current month. So my report for today (30 March 2021) did not count tomorrow's date 31 March 2021.
declare #DespatchTo Date = '03-30-2021'
WITH mycte AS
(
SELECT CAST(Convert(date,getdate()) AS DATETIME) DateValue
UNION ALL
SELECT DateValue + 1
FROM mycte
WHERE DateValue < DATEADD(d, -1, DATEADD(m, DATEDIFF(m, 0, #DespatchTo) + 1, 0)) --03-31-2021
)
SELECT SUN.Count as SunCount, SAT.Count as SatCount, WK.Count as WeekCount
FROM
(SELECT count(*) as Count
FROM mycte
WHERE DatePart("w",DateValue) = 1
group by DatePart("w",DateValue))
As SUN,
(SELECT count(*) as Count
FROM mycte
WHERE DatePart("w",DateValue) = 7
group by DatePart("w",DateValue))
As SAT,
(SELECT distinct SUM(COUNT(*)) OVER() AS Count
FROM mycte
WHERE DatePart("w",DateValue) > 1 AND DatePart("w",DateValue) < 7
group by DatePart("w",DateValue))
As WK
Which returns blank/null results. How can I return as 0?
here is what you need to do:
;WITH mycte AS (
SELECT GETDATE() DateValue
UNION ALL
SELECT DateValue + 1
FROM mycte
WHERE DateValue < EOMONTH(GETDATE())
)
select
count(case when datepart(dw, DateValue) = 1 then 1 end) SUN
, count(case when datepart(dw, DateValue) = 7 then 1 end) SAT
, count(case when datepart(dw, DateValue) between 2 and 6 then 1 end) WK
from mycte
if you want to exclude today, you can adjust cte :
;WITH mycte AS (
SELECT GETDATE() + 1 DateValue
WHERE GETDATE() <> EOMONTH(GETDATE())
UNION ALL
SELECT DateValue + 1
FROM mycte
WHERE DateValue < EOMONTH(GETDATE())
)
select
count(case when datepart(dw, DateValue) = 1 then 1 end) SUN
, count(case when datepart(dw, DateValue) = 7 then 1 end) SAT
, count(case when datepart(dw, DateValue) between 2 and 6 then 1 end) WK
from mycte

Multiple counts and merge columns

I current have a query that grabs the number of parts made per hour between two dates:
DECLARE #StartDate datetime
DECLARE #EndDate datetime
SET #StartDate = '10/10/2018'
SET #EndDate = '11/11/2018'
SELECT
CONVERT(VARCHAR(10), CAST(presstimes AS DATE), 111) AS ForDate,
DATEPART(HOUR, presstimes) AS OnHour,
COUNT(*) AS Totals
FROM
partmasterlist
WHERE
((presstimes >= #StartDate AND presstimes < dateAdd(d, 1, #EndDate))
AND (((presstimes IS NOT NULL))))
GROUP BY
CONVERT(VARCHAR(10), CAST(presstimes AS DATE), 111),
DATEPART(HOUR, presstimes)
ORDER BY
CONVERT(VARCHAR(10), CAST(presstimes AS DATE), 111) ASC;
Output:
Date Hour QTY
---------------------
2018/11/06 11 16
2018/11/06 12 20
2018/11/06 13 29
2018/11/06 14 26
Now I need to add another qty column to count where "trimmingtimes" is set.
I can't figure out how to full join the date and hour columns (e.g. presstimes might have 20qty for Hour 2, but trimmingtimes is NULL for Hour 2);
Input:
ID presstimes trimmingtimes
-----------------------------------------------------------------
1 2018-10-10 01:15:23.000 2018-10-10 01:15:23.000
2 2018-10-10 01:15:23.000 NULL
3 2018-10-10 02:15:23.000 NULL
4 NULL 2018-10-10 03:15:23.000
Output:
Date hour Press QTY T QTY
------------------------------------
10/10/18 1 2 1
10/10/18 2 1 0
10/10/18 3 0 1
I suspect you want something like this:
select convert(date, v.dt) as date,
datepart(hour, v.dt) as hour,
sum(ispress) as num_press,
sum(istrim) as num_trim
from partmasterlist pml cross apply
(values (pml.presstime, 1, 0), (pml.trimmingtime, 0, 1)
) v(dt, ispress, istrim)
group by convert(date, v.dt), datepart(hour, v.dt)
order by convert(date, v.dt), datepart(hour, v.dt);
You can add a where clause for a particular range.

SQL Query to Count number of values in a column Per Month

id date sales
1 01/01/2015 100
2 01/01/2015 100
3 02/01/2015 100
4 03/01/2015 100
What I need is to count the number of sales in a given date range (per month)which is based user input which are StartDate and EndDate. For Example the User inputs StartDate - 01/01/2015 and EndDate - 04/01/2015
the output would be like this
Month StartMonth EndMonth TotalSales
1 01/01/2015 01/31/2015 200
2 02/01/2015 02/28/2015 100
3 03/01/2015 03/31/2015 100
4 04/01/2015 04/30/2015 0
Started to something like this
set #Start_act = cast(DATEADD(month, DATEDIFF(month, 0, #StartDate), 0) as date)
set #End_act = cast(DATEADD(d, -1, DATEADD(m, DATEDIFF(m, 0, #Enddate)+1, 0)) as date)
set #counter = DATEDIFF(month, #Start_act, #End_act)
if(#counter = 1)
begin
set #counter = #counter
end
else
set #counter = #counter + 1
end
set #count = 0
CREATE TABLE #TempTableID
(
Month int,
StartMonth date,
EndMonth date,
TotalSales
)
while (#count <= #counter)
begin
set #count = #count + 1;
if(#count = 1)
begin
set #Start = #Start_act
set #End = cast(DATEADD(d, -1, DATEADD(m, DATEDIFF(m, 0, #Start_act)+1, 0)) as date)
set #plannedHorseCapacity = 123
end
else
begin
set #Start = cast(DATEADD(d, 1, #End)as date)
set #End = cast(DATEADD(d, -1, DATEADD(m, DATEDIFF(m, 0, #Start)+1, 0)) as date)
set #plannedHorseCapacity = 456
end
Insert into #TempTableID
(
Month
StartMonth
EndMonth
TotalSales
)
Values
(
#count,
#Start,
#End,
#TotalSales
)
if(#count > #counter)
begin
break
end
else
begin
continue
end
end
Select * from #TempTableID
You can use below script -
WITH cte
AS
(SELECT
CONVERT(VARCHAR(20), MONTH([date]))
+ '-' + CONVERT(VARCHAR(20), YEAR([date])) monthyear
,([sales])
FROM [sales])
--add where condition for from and to date here
SELECT
monthyear
,SUM(sales) totalsales
FROM cte
GROUP BY monthyear
Output will be
monthyear totalsales
1-2015 400
2-2015 50
3-2015 150
Option 2 With Start and End Date
WITH cte
AS
(SELECT
FORMAT(MONTH([date]), '0#')
+ '-' + CONVERT(VARCHAR(20), YEAR([date])) monthyear
,([sales])
FROM [sales])
--add where condition for from and to date here
SELECT
monthyear
,cast (SUBSTRING(monthyear, 1, 2) as int) [Month]
,SUBSTRING(monthyear, 4, 4) [Year]
,(SELECT
DATEADD(MONTH, SUBSTRING(monthyear, 1, 2) - 1, DATEADD(YEAR, SUBSTRING(monthyear, 4, 4) - 1900, 0)))
StartDate
,(SELECT
DATEADD(DAY, -1, DATEADD(MONTH, CAST(SUBSTRING(monthyear, 1, 2) AS INT), DATEADD(YEAR, SUBSTRING(monthyear, 4, 4) - 1900, 0))))
EndDate
,SUM(sales) totalsales
FROM cte
GROUP BY monthyear
Output will be
monthyear Month Year StartDate EndDate totalsales
01-2015 1 2015 2015-01-01 00:00:00.000 2015-01-31 00:00:00.000 400
02-2015 2 2015 2015-02-01 00:00:00.000 2015-02-28 00:00:00.000 50
03-2015 3 2015 2015-03-01 00:00:00.000 2015-03-31 00:00:00.000 50
11-2015 11 2015 2015-11-01 00:00:00.000 2015-11-30 00:00:00.000 100
EDIT - Sorting
If you have multiple year data, then the dates will not come in sequence to fix that add order by [StartDate] in last.
output will be
monthyear Month Year StartDate EndDate totalsales
01-2015 1 2015 2015-01-01 00:00:00 2015-01-31 00:00:00.000 400
02-2015 2 2015 2015-02-01 00:00:00 2015-02-28 00:00:00.000 50
03-2015 3 2015 2015-03-01 00:00:00 2015-03-31 00:00:00.000 50
11-2015 11 2015 2015-11-01 00:00:00 2015-11-30 00:00:00.000 100
01-2016 1 2016 2016-01-01 00:00:00 2016-01-31 00:00:00.000 125
11-2016 11 2016 2016-11-01 00:00:00 2016-11-30 00:00:00.000 55
You can achieve it like this,
;WITH [CTE_DATE]
AS
(
SELECT MONTH(#fromdt) AS [Month]
,DATEADD(mm, DATEDIFF(mm, 0, #fromdt), 0) AS StartMonth
,DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,#fromdt)+1,0)) AS EndMonth
UNION ALL
SELECT [Month] + 1 AS [Month]
,DATEADD(MONTH,1,StartMonth) AS StartMonth
,DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,DATEADD(MONTH,1,StartMonth))+1,0)) AS EndMonth
FROM [CTE_DATE] WHERE [Month] < MONTH(#todt)
)
SELECT *
,(SELECT SUM(sales) FROM yourtable where [date] between StartMonth and EndMonth) as TotalSales
FROM [CTE_DATE]
You can find end of month as below:
select [Month] = Month([date]), [date] as StartMonth,
EndMonth = convert(date, dateadd(month,datediff(month,0,'2017-02-11')+1,0)-1),
TotalSales from (
select [date], sum(sales) as totalSales
from #yoursales
group by [date]
) a
You could use a CTE like this
DECLARE #SampleData AS TABLE
(
id int,
[date] date,
sales int
)
INSERT INTO #SampleData
VALUES
( 1 , '2015-01-01', 100 ),
( 2 , '2015-01-01', 100 ),
( 3 , '2015-02-01', 100 ),
( 4 , '2015-03-01', 100 )
DECLARE #StartDate date = '2015-01-01'
DECLARE #EndDate date = '2015-08-01'
;WITH temp AS -- calendar table
(
SELECT dateadd(month, datediff(month,0,#StartDate),0) AS [StartMonthDate],
dateadd(day, -1 ,dateadd(month, datediff(month,0,#StartDate) + 1,0)) AS [EndMonthDate]
UNION ALL
SELECT dateadd(month, 1, t.[StartMonthDate]),
dateadd(day,-1,dateadd(month, 2, t.[StartMonthDate]))
FROM temp t
WHERE dateadd(month, 1, t.[StartMonthDate]) <= #EndDate
)
SELECT datepart(year,t.StartMonthDate) AS year,
datepart(month,t.StartMonthDate) AS month,
t.StartMonthDate,
t.EndMonthDate,
coalesce(ap.TotalSales,0) AS TotalSales
FROM temp t
CROSS APPLY
(
SELECT SUM(sales) AS TotalSales
FROM #SampleData sd
WHERE sd.[date] BETWEEN t.StartMonthDate AND t.EndMonthDate
) ap
OPTION (MAXRECURSION 0)
Demo link: http://rextester.com/LWUX67185
;With cte(id,date,sales)
AS
(
SELECT 1,'01/01/2015',100 UNION ALL
SELECT 2,'01/01/2015',100 UNION ALL
SELECT 3,'02/01/2015',100 UNION ALL
SELECT 4,'03/01/2015',100 UNION ALL
SELECT 5,'04/01/2015',NULL
)
SELECT [Id],CONVERT(VARCHAR(10),[DATE], 101) AS [DATE],
CONVERT(VARCHAR(10),[EndMonth],101) AS [EndMonth],
[SumOfSale] From
(
SELECT id,[DATE],EndMonth,SUM(sales) OVER(Partition by [DATE],EndMonth order by EndMonth) AS SumOfSale,
Row_NUmber ()OVER(Partition by [DATE],EndMonth order by id)As Seq From
(
SELECT id, CAST([DATE] AS DATE)[DATE], EOMONTH(date)AS EndMonth,ISNULL(sales,0)As Sales from cte
)Dt
)Final
WHERE Final.Seq=1
OutPut
Month StartMonth EndMonth TotalSales
1 01/01/2015 01/31/2015 200
2 02/01/2015 02/28/2015 100
3 03/01/2015 03/31/2015 100
4 04/01/2015 04/30/2015 0

DATEDIFF excluding summer months

We are running reports for a seasonal business, with expected lulls during the summer months. For some metrics, we'd essentially like to pretend that those months don't even exist.
Thus consider the default behavior of:
SELECT DATEDIFF(MONTH, '2015-05-01', '2015-06-01') -- answer = 1
SELECT DATEDIFF(MONTH, '2015-05-01', '2015-07-01') -- 2
SELECT DATEDIFF(MONTH, '2015-05-01', '2015-08-01') -- 3
SELECT DATEDIFF(MONTH, '2015-05-01', '2015-09-01') -- 4
We want to ignore June and July, so we would like those answers to look like this:
SELECT DATEDIFF(MONTH, '2015-05-01', '2015-06-01') -- answer = 1
SELECT DATEDIFF(MONTH, '2015-05-01', '2015-07-01') -- 1
SELECT DATEDIFF(MONTH, '2015-05-01', '2015-08-01') -- 1
SELECT DATEDIFF(MONTH, '2015-05-01', '2015-09-01') -- 2
What is the easiest way to accomplish this? I'd like a pure SQL solution, rather than something using TSQL, but writing a custom function such as NOSUMMER_DATEDIFF could also work.
Also, keep in mind the reports will span multiple years, so the solution should be able to handle that.
If you are only interested month differences, then I would suggest a trick here. Count the number of months since some date 0, but ignore the summer months. For example:
'2015-05-01' --> 2015 * 10 + 5 = 20155
'2015-06-01' --> 2015 * 10 + 6 = 20156
'2015-07-01' --> 2015 * 10 + 6 = 20156
'2015-08-01' --> 2015 * 10 + 6 = 20156
'2015-09-01' --> 2015 * 10 + 7 = 20157
This is a fairly easy calculation:
select (case when month(date2) <= 6 then year(date2) * 10 + month(date2)
when month(date2) in (7, 8) then year(date2) * 10 + 6
else year(date2) * 10 + (month(date2) - 2)
end)
For the difference:
select ((case when month(date2) <= 6 then year(date2) * 10 + month(date2)
when month(date2) in (7, 8) then year(date2) * 10 + 6
else year(date2) * 10 + (month(date2) - 2)
end) -
(case when month(date1) <= 6 then year(date1) * 10 + month(date1)
when month(date1) in (7, 8) then year(date1) * 10 + 6
else year(date1) * 10 + (month(date1) - 2)
end)
)
To able to achieve that, you have to "split" dates ranges to an "array" of dates for every single range of dates. CTE might be helpful in this case.
See:
--your table which holds dates ranges
DECLARE #dates TABLE(id INT IDENTITY(1,1), dFrom DATE, dTo DATE)
INSERT INTO #dates (dFrom, dTo)
VALUES('2015-05-01', '2015-06-01'),
('2015-05-01', '2015-07-01'),
('2015-05-01', '2015-08-01'),
('2015-05-01', '2015-09-01')
--summer month table
DECLARE #summermonths TABLE(summMonth INT)
INSERT INTO #summermonths(summMonth)
VALUES(6), (7)
--here Common Table Expressions is in action to "split" dates ranges to an array of dates for every single date range
;WITH CTE AS
(
SELECT id, DATEADD(MM, 0, dFrom) AS ndFrom, dTo, CASE WHEN MONTH(DATEADD(MM, 0, dFrom)) = 6 OR MONTH(DATEADD(MM, 0, dFrom)) = 7 THEN 0 ELSE 1 END AS COfMonth
FROM #dates
WHERE DATEADD(MM, 1, dFrom)<=dTo
UNION ALL
SELECT id, DATEADD(MM, 1, ndFrom) AS ndFrom, dTo, CASE WHEN MONTH(DATEADD(MM, 1, ndFrom)) = 6 OR MONTH(DATEADD(MM, 1, ndFrom)) = 7 THEN 0 ELSE 1 END AS COfMonth
FROM CTE
WHERE DATEADD(MM, 1, ndFrom)<=dTo
)
SELECT t1.id, t2.dFrom, t2.dTo, SUM(t1.COfMonth) AS MyDateDiff
FROM CTE AS t1 INNER JOIN #dates AS t2 ON t1.id = t2.id
GROUP BY t1.id, t2.dFrom , t2.dTo
Result:
id dFrom dTo MyDateDiff
1 2015-05-01 2015-06-01 1
2 2015-05-01 2015-07-01 1
3 2015-05-01 2015-08-01 2
4 2015-05-01 2015-09-01 3 --not 2, because of 5, 8, 9
Got it?
Note: a solution might be differ in case of dFrom and dTo is not the first date of month.

How to get the date for the previous month which are closed in the current month

Iam using SSRS Report builder application to create BI Report for my System which is tracking the numbers of incidents logged and closed based on each month.
the below is the table which i need to create the query
Month Logged Received Closed Remaining
January 200 220 150 70
February 150 220 200 20
March 110 130 100 30
April 200 230 200 30
and each column define as follow:
Logged= Open Incident in the Current Month for example open from 1/1/2014 to 31/1/2014 (Contain only the current month data )
Received = Logged incident+ the remaining from the previous months which are still open not close for example the month febreuary will be 150 for the current moth+70 from previous month remaining will give me total 220 which is received.
Closed= incident which are opened in the current month and closed in the current month + the remaining from the previous month which closed in this month
Remaining= Received – closed
the code which i used is not giving me the close incident for the previous months also its only giving me which were closed in the current month
the below is the code which i used for my query:
SELECT group_id, YEAR(Opendate) AS Year, MONTH(Opendate) AS Month,
COUNT(CASE WHEN Month(Closedate) = Month(Opendate)
AND Month(closedate)> Month (opendate) THEN 1 ELSE NULL END) AS closed,
COUNT(*) AS Logged,
FROM Incidents
WHERE (Opendate >= #YearStart) AND (Opendate <= #YearEnd)
GROUP BY YEAR(Opendate), MONTH(Opendate), group_id
ORDER BY Year, Month,group_id
Logged is working fine the closed, Received and remaining i am stuck on it.
I tried to use Union and got the Logged and Closed Data
Select count(*) logged,year(opendate) as year1,MONTH(opendate) as
month1,'Logged' as status1
From Incidents
where opendate is not null
GROUP BY year(opendate),MONTH(opendate)
UNION
Select count(*) closed,year(Closedate) as year1,MONTH(Closedate) as
month1,'All_Closed' as status1
From Incidents
where Closedate is not null
GROUP BY year(Closedate),MONTH(Closedate)
UNION
Select count(*) Remaining,year(opendate) as year1,MONTH(opendate) as
month1,'Current_Month_Not_Closed' as status1
From Incidents
where Month(Closedate) > MONTH(Opendate)
GROUP BY year(opendate),MONTH(opendate)
UNION
Select count(*) Month_Closed,year(opendate) as year1,MONTH(opendate) as
month1,'Current_Month_Close' as status1
From Incidents
where MONTH(Closedate) = MONTH(Opendate)
GROUP BY year(opendate),MONTH(opendate)
order by year1,month1
the data which I received are as follow:
logged | year1 | month1 | status1
-------+-------+--------+-------------------------
1093 | 2014 | 1 | Logged
1089 | 2014 | 1 | All_Closed
997 | 2014 | 1 | Current_Month_Close
96 | 2014 | 1 | Current_Month_Not_Closed
1176 | 2014 | 2 | Logged
1176 | 2014 | 2 | All_Closed
91 | 2014 | 2 | Current_Month_Not_Closed
1085 | 2014 | 2 | Current_Month_Close
1340 | 2014 | 3 | Logged
1327 | 2014 | 3 | All_Closed
107 | 2014 | 3 | Current_Month_Not_Closed
1232 | 2014 | 3 | Current_Month_Close
116 | 2014 | 4 | Current_Month_Not_Closed
1320 | 2014 | 4 | Current_Month_Close
1424 | 2014 | 4 | All_Closed
1441 | 2014 | 4 | Logged
1167 | 2014 | 5 | Current_Month_Close
105 | 2014 | 5 | Current_Month_Not_Closed
1277 | 2014 | 5 | Logged
1283 | 2014 | 5 | All_Closed
To have a reliable data a calendar table as anchor can help, and is needed in case the tickets can be alive for months from their opening date or there can be a month without ticket created.
For example with the fake data
CREATE TABLE Incidents (
id int identity(1, 1)
, group_id nvarchar(100)
, Opendate Datetime
, Closedate Datetime
)
INSERT INTO Incidents
VALUES ('Service Desk', '20140107', '20140120')
, ('Service Desk', '20140117', '20140123')
, ('Service Desk', '20140127', '20140313')
, ('Service Desk', '20140310', '')
-- from an OP comment the open tickets have the Closedate '' (1900-01-01)
without a calendar table (or a temp, or a CTE) there is no way to add february in the resultset, even if the third record is both "Received" and "Remaining" in that month.
To create a calendar there are several way, in this case we need the some information about months but nothing about the days, so those are not generated.
declare #YearStart date = '20140101'
declare #YearEnd date = '20140430'
;WITH D(N) AS (
SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3
UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7
UNION ALL SELECT 8 UNION ALL SELECT 9
)
SELECT EOM
= DATEADD(D, -1, DATEADD(M, u.N + 10 * t.N + 1
, DATEADD(Y, DATEDIFF(Y, 0, #YearStart), 0)))
, pMonth = u.N + 10 * t.N
FROM D u
CROSS JOIN D t
WHERE u.N + 10 * t.N <= DATEDIFF(M, #YearStart, #YearEnd)
Here EOM is the date of the end of the month, it'll be used to check if the incidents are closed in the month and pMonth is the progressive month starting from #YearStart.
Now we need to prepare the data in the incident table to be used
SELECT ID
, OpenDate
, Closedate = COALESCE(NULLIF(Closedate, ''), '99991231')
, pOpenDate = DATEDIFF(M, #YearStart, OpenDate)
, pClosedate = DATEDIFF(M, #YearStart
, COALESCE(NULLIF(Closedate, ''), '99991231'))
FROM Incidents
the Closedate need to always have a value higher than the OpenDate, for this is used the constant date 9999-12-31, pOpenDate and pClosedate, as pMonth before, are the progressive month starting from #YearStart respectively of OpenDate and Closedate.
Putting those togheter it's possible to create the main query
declare #YearStart date = '20140101'
declare #YearEnd date = '20140430'
;WITH D(N) AS (
SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3
UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7
UNION ALL SELECT 8 UNION ALL SELECT 9
), CM AS (
SELECT EOM
= DATEADD(D, -1, DATEADD(M, u.N + 10 * t.N + 1
, DATEADD(Y, DATEDIFF(Y, 0, #YearStart), 0)))
, pMonth = u.N + 10 * t.N
FROM D u
CROSS JOIN D t
WHERE u.N + 10 * t.N <= DATEDIFF(M, #YearStart, #YearEnd)
), I AS (
SELECT ID
, OpenDate
, Closedate = COALESCE(NULLIF(Closedate, ''), '99991231')
, pOpenDate = DATEDIFF(M, #YearStart, OpenDate)
, pClosedate = DATEDIFF(M, #YearStart
, COALESCE(NULLIF(Closedate, ''), '99991231'))
FROM Incidents
)
SELECT MONTH(CM.EOM) [Month]
, Logged = SUM(CASE WHEN pOpenDate = pMonth
THEN 1
ELSE 0
END)
, Received = Count(i.id)
, Closed = SUM(CASE WHEN pClosedate = pMonth
AND i.Closedate < CM.EOM
THEN 1
ELSE 0
END)
, Remaining = SUM(CASE WHEN i.Closedate > CM.EOM
THEN 1
ELSE 0
END)
FROM CM
INNER JOIN I ON CM.pMonth
BETWEEN i.pOpenDate AND i.pClosedate
WHERE CM.EOM <= #YearEnd
GROUP BY CM.EOM
ORDER BY CM.EOM
SQLFiddle Demo
using a JOIN to get the month from the calendar table between #YearStart and #YearEnd and all the incident alive in the month. Their attribute are calculated with the CASE logic, in case of Received if a ticket is alive it's received so no logic is needed.
All the CASE can be transformed in BIT logic
declare #YearStart date = '20140101'
declare #YearEnd date = '20140430'
;WITH D(N) AS (
SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3
UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7
UNION ALL SELECT 8 UNION ALL SELECT 9
), CM AS (
SELECT EOM
= DATEADD(D, -1, DATEADD(M, u.N + 10 * t.N + 1
, DATEADD(Y, DATEDIFF(Y, 0, #YearStart), 0)))
, pMonth = u.N + 10 * t.N
FROM D u
CROSS JOIN D t
WHERE u.N + 10 * t.N <= DATEDIFF(M, #YearStart, #YearEnd)
), I AS (
SELECT ID
, OpenDate
, Closedate = COALESCE(NULLIF(Closedate, ''), '99991231')
, pOpenDate = DATEDIFF(M, #YearStart, OpenDate)
, pClosedate = DATEDIFF(M, #YearStart
, COALESCE(NULLIF(Closedate, ''), '99991231'))
FROM Incidents
)
SELECT MONTH(CM.EOM) [Month]
, Logged = SUM(1 - CAST(pOpenDate - pMonth AS BIT))
, Received = Count(i.id)
, Closed = SUM(1 - CAST(pClosedate - pMonth AS BIT))
, Remaining = SUM(0 + CAST(i.pClosedate / (CM.pMonth + 1) AS BIT))
FROM CM
INNER JOIN I ON CM.pMonth
BETWEEN i.pOpenDate AND i.pClosedate
WHERE CM.EOM <= #YearEnd
GROUP BY CM.EOM
ORDER BY CM.EOM;
SQLFiddle Demo
The bit logic is base on how the CAST to BIT works:
0 go to 0
everything else go to 1
based on that (with A and B integer):
1 - CAST(A - B AS BIT) is 1 when A = B
CAST(A / (B + 1) AS BIT) is 1 when A > B (the 0 + is to force an implicit cast to INT as BIT cannot be SUMmed)
Received would be the number of tickets that were opened before the end of the month, and not closed before the start of the month.
count(case when OpenDate <= #EndOfMonth and
(#StartOfMonth >= CloseDate or CloseDate is null) then 1 end)
as Received
Closed is straightforward:
count(case when CloseDate between #StartOfMonth and #EndOfMonth
then 1 end) as Closed
You should be able to figure out how to calculate the start and end of a month using Google.