简体   繁体   中英

Count and group data by Year/month

I have a SQL Server table that contains task data going back 12 months.

Here's an example:

在此处输入图像描述

I am trying to write a query that displays each month and the count of tickets that were open up until that month by their rating. Output example below:

222

I created the following SQL statement to count by day:

SELECT 
    created, 
    COUNT(CASE WHEN rating = ‘high’ THEN 1 ELSE NULL END) AS high,
    COUNT(CASE WHEN rating = ‘med’ THEN 1 ELSE NULL END) AS med,
    COUNT(CASE WHEN rating = ‘low’ THEN 1 ELSE NULL END) AS low 
FROM 
    taskDB 
GROUP BY 
    created 
ORDER BY 
    created ASC

I am not sure how to group by month and get the proper count up until that month? Is there a better way to do this? My end goal is to display this data as a timeline chart where the yAxis is ticket count and xAxis is the date(yea/month). There will be a line for each “rating”.

UPDATE 8/14/2020

I tried out a couple of the answers there and they seem to count the amount of tickets open only for each month and not from each month + all prior months. I created a SQL script with some test data so everyone can see what i'm working with:

GO
CREATE TABLE [dbo].[taskDB](
    [ticket] [varchar](50) NULL,
    [created] [date] NULL,
    [closed] [date] NULL,
    [rating] [varchar](50) NULL
) ON [PRIMARY]
GO
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating]) VALUES (N'023345', CAST(N'2019-09-01' AS Date), CAST(N'2020-01-17' AS Date), N'Low')
GO
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating]) VALUES (N'023346', CAST(N'2019-08-01' AS Date), CAST(N'2019-08-03' AS Date), N'Critical')
GO
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating]) VALUES (N'023347', CAST(N'2019-09-01' AS Date), CAST(N'2019-09-20' AS Date), N'Critical')
GO
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating]) VALUES (N'023348', CAST(N'2019-08-01' AS Date), CAST(N'2020-08-06' AS Date), N'Critical')
GO
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating]) VALUES (N'023349', CAST(N'2020-08-01' AS Date), CAST(N'2020-08-05' AS Date), N'Medium')
GO
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating]) VALUES (N'023350', CAST(N'2019-08-01' AS Date), CAST(N'2019-08-05' AS Date), N'Medium')
GO
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating]) VALUES (N'023351', CAST(N'2019-12-22' AS Date), CAST(N'' AS Date), N'High')
GO
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating]) VALUES (N'023352', CAST(N'2019-11-07' AS Date), CAST(N'2020-08-05' AS Date), N'Medium')
GO
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating]) VALUES (N'023353', CAST(N'2020-08-02' AS Date), CAST(N'' AS Date), N'Low')
GO
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating]) VALUES (N'023354', CAST(N'2019-08-02' AS Date), CAST(N'2019-08-05' AS Date), N'Medium')
GO
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating]) VALUES (N'023355', CAST(N'2019-010-02' AS Date), CAST(N'' AS Date), N'Low')
GO
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating]) VALUES (N'023356', CAST(N'2019-08-02' AS Date), CAST(N'2019-08-05' AS Date), N'Critical')
GO
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating]) VALUES (N'023357', CAST(N'2019-08-06' AS Date), CAST(N'2020-07-05' AS Date), N'Critical')
GO
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating]) VALUES (N'023358', CAST(N'2019-10-04' AS Date), CAST(N'' AS Date), N'Low')
GO
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating]) VALUES (N'023359', CAST(N'2019-12-02' AS Date), CAST(N'2020-02-25' AS Date), N'High')
GO
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating]) VALUES (N'023360', CAST(N'2019-08-05' AS Date), CAST(N'2019-08-05' AS Date), N'Medium')
GO
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating]) VALUES (N'023361', CAST(N'2020-08-02' AS Date), CAST(N'' AS Date), N'High')
GO
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating]) VALUES (N'023362', CAST(N'2019-09-02' AS Date), CAST(N'2019-10-06' AS Date), N'Critical')
GO
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating]) VALUES (N'023363', CAST(N'2019-10-03' AS Date), CAST(N'2019-11-08' AS Date), N'High')
GO
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating]) VALUES (N'023365', CAST(N'2019-10-03' AS Date), CAST(N'2019-12-08' AS Date), N'N/A')
GO
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating]) VALUES (N'023364', CAST(N'2019-11-03' AS Date), CAST(N'2019-11-05' AS Date), N'High')
GO
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating]) VALUES (N'023366', CAST(N'2020-06-03' AS Date), CAST(N'' AS Date), N'High')
GO
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating]) VALUES (N'023368', CAST(N'2019-08-03' AS Date), CAST(N'2019-08-05' AS Date), N'High')
GO
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating]) VALUES (N'023367', CAST(N'2019-11-03' AS Date), CAST(N'' AS Date), N'N/A')
GO
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating]) VALUES (N'023371', CAST(N'2019-08-03' AS Date), CAST(N'2019-08-05' AS Date), N'N/A')
GO
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating]) VALUES (N'023370', CAST(N'2019-08-03' AS Date), CAST(N'2019-08-05' AS Date), N'Critical')
GO

I tried the following from @GMB which is close but does not seem to give me the correct results as there is negative numbers and the blank closed fields are coming back as 1900-01-01.

select 
    year(x.dt) yyyy,
    month(x.dt) mm,
    sum(sum(case when x.rating = 'low'    then cnt else 0 end)) 
        over(order by year(x.dt), month(x.dt)) low,
    sum(sum(case when x.rating = 'medium' then cnt else 0 end)) 
        over(order by year(x.dt), month(x.dt)) medium,
    sum(sum(case when x.rating = 'high'   then cnt else 0 end)) 
        over(order by year(x.dt), month(x.dt)) high
from [TestDB].[dbo].[taskDB] t
cross apply (values
    (rating, created, 1),
    (rating, closed, -1)
) as x(rating, dt, cnt)
where x.dt is not null
group by year(x.dt), month(x.dt)
order by year(x.dt), month(x.dt)

The results for this query:

在此处输入图像描述

UPDATE 8/14/2020

The revised query by @iceblade seems to be the most correct at this point. The only thing it does not account for is if a ticket was opened and closed in the same month, i believe it should be counted. Here is the query:

declare @FromDate datetime, 
        @ToDate datetime;

SET @FromDate = (Select min(created) From [dbo].[taskDB]);
SET @ToDate = (Select max(created) From [dbo].[taskDB]);

declare @openTicketsByMonth table (firstDayNextMonth datetime, year int, month int, Low int, Medium int, High int, Critical int, NA int)

Insert into @openTicketsByMonth(firstDayNextMonth, year, month)

Select top  (datediff(month, @FromDate, @ToDate) + 1) 
              dateadd(month, number + 1, @FromDate),
              year(dateadd(month, number, @FromDate)),
              month(dateadd(month, number, @FromDate))
              from [master].dbo.spt_values 
              where [type] = N'P' order by number;

update R
Set R.Low = (Select count(1) from [dbo].[taskDB] where rating = 'Low' and created < R.firstDayNextMonth and (closed >= R.firstDayNextMonth or closed = '' or closed is null)),
    R.Medium = (Select count(1) from [dbo].[taskDB] where rating = 'Medium' and created < R.firstDayNextMonth and (closed >= R.firstDayNextMonth or closed = '' or closed is null)),
    R.High = (Select count(1) from [dbo].[taskDB] where rating = 'High' and created < R.firstDayNextMonth and (closed >= R.firstDayNextMonth or closed = '' or closed is null)),
    R.Critical = (Select count(1) from [dbo].[taskDB] where rating = 'Critical' and created < R.firstDayNextMonth and (closed >= R.firstDayNextMonth or closed = '' or closed is null)),
    R.NA = (Select count(1) from [dbo].[taskDB] where rating = 'N/A' and created < R.firstDayNextMonth and (closed >= R.firstDayNextMonth or closed = '' or closed is null))
From @openTicketsByMonth R

select  year,
        month,
        Low,
        Medium,
        High,
        Critical,
        NA 
from @openTicketsByMonth

and the output from the query based on the data above:

在此处输入图像描述

If you look at 2019/8 there are 2 tickets that are critical that were opened and stayed open past that month but there are 3 critical tickets that were opened and closed in the same month. I believe those should be counted.

UPDATE 8/17/2020

The query @iceblade posted has been edited and is confirmed to produce the proper results. Answer has been marked accordingly.

You need a calendar table/view with the date ranges for the last twelve months, eg

"year/month"  month_begin    month_end
     2019/08   2019-08-01   2019-08-31

and then a join checking for overlapping date ranges:

-- based on @iceblade's answer
declare @FromDate date, 
        @ToDate date;

SET @FromDate = (Select min(created) From [dbo].[taskDB]);
SET @ToDate = (Select max(created) From [dbo].[taskDB]);

declare @calendar table (Month_begin date, month_end date, year int, month int)

Insert into @calendar(Month_begin, month_end, year, month)

Select top  (datediff(month, @FromDate, @ToDate) + 1) 
              dateadd(month, number, @FromDate),
              dateadd(d,-1,dateadd(month, number + 1, @FromDate)),
              year(dateadd(month, number, @FromDate)),
              month(dateadd(month, number, @FromDate))
              from [master].dbo.spt_values 
              where [type] = N'P' order by number;
              
              
select c.year,c.month,
   COUNT(CASE WHEN rating = 'Low' THEN 1 ELSE NULL END) as low,
   COUNT(CASE WHEN rating = 'Medium' THEN 1 ELSE NULL END) as med,
   COUNT(CASE WHEN rating = 'High' THEN 1 ELSE NULL END) as high,
   COUNT(CASE WHEN rating = 'Critical' THEN 1 ELSE NULL END) as critical,
   COUNT(CASE WHEN rating = 'N/A' THEN 1 ELSE NULL END) as na
FROM taskDB as t join @calendar as c 
  -- overlapping periods
  on t.created <= c.month_end
 and (t.closed >= c.month_begin  or t.closed is null)
GROUP BY c.year,c.month
ORDER BY c.year,c.month

Adding a varition based on GMB's, no join to a calendar and probably more efficient for your actual data. This just modifies the closing date to next month:

select 
    year(x.dt) yyyy,
    month(x.dt) mm,
    sum(sum(case when x.rating = 'Low'    then cnt else 0 end)) 
        over(order by year(x.dt), month(x.dt)
             rows unbounded preceding) low,
    sum(sum(case when x.rating = 'Medium' then cnt else 0 end)) 
        over(order by year(x.dt), month(x.dt)
             rows unbounded preceding) medium,
    sum(sum(case when x.rating = 'High'   then cnt else 0 end)) 
        over(order by year(x.dt), month(x.dt)
             rows unbounded preceding) high,
    sum(sum(case when x.rating = 'Critical'   then cnt else 0 end)) 
        over(order by year(x.dt), month(x.dt)
             rows unbounded preceding) critical,
    sum(sum(case when x.rating = 'N/A'   then cnt else 0 end)) 
        over(order by year(x.dt), month(x.dt)
             rows unbounded preceding) na
from taskDB t
cross apply (values
    (rating, created, 1),
    -- closed in next month
    (rating, dateadd(m,1,closed), -1)
   ) as x(rating, dt, cnt)
where dt <= getdate() -- no rows past today
group by year(x.dt), month(x.dt)
order by year(x.dt), month(x.dt)

There's a minor difference, #2 will skip months with no tickets, but I doubt this exists.

This seems to be what you want, see fiddle

One option uses a lateral join and conditional aggregation:

select 
    year(x.dt) yyyy,
    month(x.dt) mm,
    sum(sum(case when x.rating = 'low'    then cnt else 0 end)) 
        over(order by year(x.dt), month(x.dt)) low,
    sum(sum(case when x.rating = 'medium' then cnt else 0 end)) 
        over(order by year(x.dt), month(x.dt)) medium,
    sum(sum(case when x.rating = 'high'   then cnt else 0 end)) 
        over(order by year(x.dt), month(x.dt)) high,
from taskDB t
cross apply (values
    (rating, created, 1),
    (rating, closed, -1)
) as x(rating, dt, cnt)
where x.dt is not null
group by year(x.dt), month(x.dt)
order by year(x.dt), month(x.dt)

You can filter as needed on a given period by turning this to a subquery and using a where clause in the outer query.

Based on your new input, I created a table variable with all the year/month between the first and last ticket, I used this article for that: better way to generate months/year table Then I update each category, counting the tickets that have a closed date > first day of each month. This should give you the desired result.

Updated 8/17/2020 - Modified query to include tickets that were closed before the end of the month.

declare @FromDate datetime, 
        @ToDate datetime;

SET @FromDate = (Select min(created) From [dbo].[taskDB]);
SET @ToDate = (Select max(created) From [dbo].[taskDB]);

declare @openTicketsByMonth table (firstDayOfMonth datetime, firstDayNextMonth datetime, year int, month int, Low int, Medium int, High int, Critical int, NA int)

Insert into @openTicketsByMonth(firstDayOfMonth, firstDayNextMonth, year, month)

Select top  (datediff(month, @FromDate, @ToDate) + 1) 
                                                  dateadd(month, number, @FromDate),
              dateadd(month, number + 1, @FromDate),
             year(dateadd(month, number, @FromDate)),
              month(dateadd(month, number, @FromDate))
              from [master].dbo.spt_values 
              where [type] = N'P' order by number;

update R
Set R.Low = (Select count(1) from [dbo].[taskDB] where rating = 'Low' and created < R.firstDayNextMonth and (closed >= R.firstDayOfMonth or closed = '' or closed is null)),
    R.Medium = (Select count(1) from [dbo].[taskDB] where rating = 'Medium' and created < R.firstDayNextMonth and (closed >= R.firstDayOfMonth or closed = '' or closed is null)),
    R.High = (Select count(1) from [dbo].[taskDB] where rating = 'High' and created < R.firstDayNextMonth and (closed >= R.firstDayOfMonth or closed = '' or closed is null)),
    R.Critical = (Select count(1) from [dbo].[taskDB] where rating = 'Critical' and created < R.firstDayNextMonth and (closed >= R.firstDayOfMonth or closed = '' or closed is null)),
    R.NA = (Select count(1) from [dbo].[taskDB] where rating = 'N/A' and created < R.firstDayNextMonth and (closed >= R.firstDayOfMonth or closed = '' or closed is null))
From @openTicketsByMonth R

select  year,
        month,
        Low,
        Medium,
        High,
        Critical,
        NA 
from @openTicketsByMonth

Group by year and month instead of the full date.

   select convert(varchar(max), year(created)) + '/' + right('0' + convert(varchar(max), month(created)),2) as Created
       , COUNT(CASE WHEN rating = ‘high’ THEN 1 ELSE NULL END) as high,
    COUNT(CASE WHEN rating = ‘med’ THEN 1 ELSE NULL END) as med,
    COUNT(CASE WHEN rating = ‘low’ THEN 1 ELSE NULL END) as low FROM taskDB 
    GROUP BY convert(varchar(max), year(created)) + '/' + right('0' + convert(varchar(max), month(created)),2)
ORDER BY convert(varchar(max), year(created)) + '/' + right('0' + convert(varchar(max), month(created)),2) ASC

Make use of WIth clause in which convert the date in the required format and then group on that formatted date.

With TempTaskDB As (    
SELECT convert(varchar(20), datepart(year, created)) + '/' + convert(varchar(20), datepart(month, created)) as CreatedDate,rating
from taskDB)
Select CreatedDate,
COUNT(CASE WHEN rating = ‘high’ THEN 1 ELSE NULL END) AS high,
    COUNT(CASE WHEN rating = ‘med’ THEN 1 ELSE NULL END) AS med,
    COUNT(CASE WHEN rating = ‘low’ THEN 1 ELSE NULL END) AS low
from TempTaskDB
group by CreatedDate
Order by CreatedDate Asc

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM