按年/月计数和分组数据

问题描述

我有一个sql Server表,其中包含可追溯到12个月的任务数据。

这是一个例子:

enter image description here

我正在尝试编写一个查询,该查询显示每个月的情况以及截至该月为止根据其评级打开的票数。输出示例如下:

222

我创建了以下sql语句以按天计数:

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

我不确定如何按月份分组并获得正确的计数,直到那个月?有一个更好的方法吗?我的最终目标是将这些数据显示为时间轴图,其中yAxis是工单计数,xAxis是日期(是/月)。每个“评分”都有一行。

UPDATE 8/14/2020

我在那里尝试了几个答案,他们似乎只计算每个月的开票数量,而不是每个月+之前所有月份的开票数量。我用一些测试数据创建了一个sql脚本,这样每个人都可以看到我正在使用的内容

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],[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],[rating]) VALUES (N'023347',CAST(N'2019-09-20' AS Date),[rating]) VALUES (N'023348',CAST(N'2020-08-06' AS Date),[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],[rating]) VALUES (N'023350',CAST(N'2019-08-05' AS Date),[rating]) VALUES (N'023351',CAST(N'2019-12-22' AS Date),CAST(N'' AS Date),N'High')
GO
INSERT [dbo].[taskDB] ([ticket],[rating]) VALUES (N'023352',CAST(N'2019-11-07' AS Date),[rating]) VALUES (N'023353',CAST(N'2020-08-02' AS Date),[rating]) VALUES (N'023354',CAST(N'2019-08-02' AS Date),[rating]) VALUES (N'023355',CAST(N'2019-010-02' AS Date),[rating]) VALUES (N'023356',[rating]) VALUES (N'023357',CAST(N'2019-08-06' AS Date),CAST(N'2020-07-05' AS Date),[rating]) VALUES (N'023358',CAST(N'2019-10-04' AS Date),[rating]) VALUES (N'023359',CAST(N'2019-12-02' AS Date),CAST(N'2020-02-25' AS Date),[rating]) VALUES (N'023360',[rating]) VALUES (N'023361',[rating]) VALUES (N'023362',CAST(N'2019-09-02' AS Date),CAST(N'2019-10-06' AS Date),[rating]) VALUES (N'023363',CAST(N'2019-10-03' AS Date),CAST(N'2019-11-08' AS Date),[rating]) VALUES (N'023365',CAST(N'2019-12-08' AS Date),N'N/A')
GO
INSERT [dbo].[taskDB] ([ticket],[rating]) VALUES (N'023364',CAST(N'2019-11-03' AS Date),CAST(N'2019-11-05' AS Date),[rating]) VALUES (N'023366',CAST(N'2020-06-03' AS Date),[rating]) VALUES (N'023368',[rating]) VALUES (N'023367',[rating]) VALUES (N'023371',[rating]) VALUES (N'023370',N'Critical')
GO

我尝试了@GMB的以下内容,该内容很接近,但似乎没有给我正确的结果,因为存在负数,空白的闭合字段又返回为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)

查询的结果:

enter image description here

UPDATE 8/14/2020

此时,@ iceblade修改后的查询似乎是最正确的。它唯一不能解释的是,如果在同一月打开和关闭票证,我认为应该算在内。这是查询

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,@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

以及基于上述数据的查询输出

enter image description here

如果您查看2019/8,有2张重要的门票已打开并在当月之后保持打开状态,但3张重要的门票在同一月打开和关闭。我相信这些应该算在内。

UPDATE 8/17/2020

发布的查询@iceblade已被编辑,并确认产生正确的结果。答案已相应标记

解决方法

您需要一个日历表/视图,其中应包含最近十二个月的日期范围,例如

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

,然后进行联接检查重叠的日期范围:

-- 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,month(dateadd(month,@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

添加基于GMB的变体,不加入日历,可能对您的实际数据更有效。这只是将截止日期修改为下个月:

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)

有一个微小的区别,#2将跳过没有门票的几个月,但我怀疑这是存在的。

这似乎是您想要的,请参见fiddle

,

一个选项使用横向联接和条件聚合:

select 
    year(x.dt) yyyy,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,(rating,closed,-1)
) as x(rating,cnt)
where x.dt is not null
group by year(x.dt),month(x.dt)

通过将其转到子查询并在外部查询中使用where子句,可以在给定时间段内根据需要进行过滤。

,

根据您的新输入,我创建了一个表变量,其中包含第一张票和最后一张票之间的所有年份/月份,为此我使用了这篇文章: better way to generate months/year table 然后,我更新每个类别,计算关闭日期>每月第一天的门票。这应该给您想要的结果。

已更新2020年8月17日-修改了查询,以包括在月底之前关闭的票证。

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,month int,Low int,Medium int,High int,Critical int,NA int)

Insert into @openTicketsByMonth(firstDayOfMonth,firstDayNextMonth,@ToDate) + 1) 
                                                  dateadd(month,@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
,

按年份和月份而不是整个日期分组。

   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),2)
ORDER BY convert(varchar(max),2) ASC
,

使用WIth子句,其中将日期转换为必需的 格式,然后按该格式化日期分组。

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