问题描述
我有一个sql Server表,其中包含可追溯到12个月的任务数据。
这是一个例子:
我正在尝试编写一个查询,该查询显示每个月的情况以及截至该月为止根据其评级打开的票数。输出示例如下:
我创建了以下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)
此查询的结果:
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
如果您查看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