【问题标题】:Calculating Open incidents per month计算每月未决事件
【发布时间】:2013-01-09 01:25:43
【问题描述】:

我们的系统中有事件,包括开始时间和结束时间以及项目名称(和其他信息)。 我们想要报告:每个项目每月有多少事件处于“开放”状态。 打开状态表示:未完成。

如果事件创建于 2009 年 12 月并于 2010 年 3 月关闭,则它应包含在 2009 年 12 月、2010 年 1 月和 2 月。

需要的结构应该是这样的:

Project   Year    Month     Count
-------  ------  -------   -------
Test       2009   December   2
Test       2010   January     10
Test       2010   February    12
....

【问题讨论】:

  • 当事件仍处于打开状态时,完成时间设置为多少?您使用的是哪个 RDBMS?

标签: sql


【解决方案1】:

在 SQL Server 中:

SELECT
  Project,
  Year = YEAR(TimeWhenStillOpen),
  Month = DATENAME(month, MONTH(TimeWhenStillOpen)),
  Count = COUNT(*)
FROM (
  SELECT
    i.Project,
    i.Incident,
    TimeWhenStillOpen = DATEADD(month, v.number, i.StartTime)
  FROM (
    SELECT
      Project,
      Incident,
      StartTime,
      FinishTime = ISNULL(FinishTime, GETDATE()),
      MonthDiff = DATEDIFF(month, StartTime, ISNULL(FinishTime, GETDATE()))
    FROM Incidents
  ) i
    INNER JOIN master..spt_values v ON v.type = 'P'
      AND v.number BETWEEN 0 AND MonthDiff - 1
) s
GROUP BY Project, YEAR(TimeWhenStillOpen), MONTH(TimeWhenStillOpen)
ORDER BY Project, YEAR(TimeWhenStillOpen), MONTH(TimeWhenStillOpen)

简而言之,它是如何工作的:

  • 最内部的子选择,直接作用于 Incidents 表,只是对表进行“规范化”(用当前时间替换 NULL 完成时间)并添加月差列 MonthDiff。如果在您的情况下不能有 NULL,只需相应地删除 ISNULL 表达式。

  • 外部子选择使用MonthDiff 将时间范围分解为一系列时间戳,这些时间戳对应于事件仍处于打开状态的月份,即不包括 FinishTime 月份。一个名为master..spt_values 的系统表也被用作现成的numbers table。

  • 最后,主选择只剩下对数据进行分组的任务。

【讨论】:

    【解决方案2】:

    这里一个有用的技术是创建一个包含“所有”日期的表(显然这将是无限的,所以我的意思是为您的目的提供足够大的范围)或创建两个表:所有月份之一(12 行)和另一个“所有”年份。

    假设您选择其中的第一个:

    create table all_dates (d date)
    

    并根据需要进行填充。我将按如下方式定义您的事件表

    create table incident
    (
        incident_id  int not null,
        project_id   int not null,
        start_date   date not null,
        end_date     date null
    )
    

    我不确定您使用的是什么 RDBMS,并且日期函数在它们之间有很大差异,因此下一点可能需要根据您的需要进行调整。

    select
      project_id,
      datepart(yy, all_dates.d) as "year",
      datepart(mm, all_dates.d) as "month",
      count(*) as "count"
    from
      incident,
      all_dates
    where
      incident.start_date <= all_dates.d and
      (incident.end_date >= all_dates.d or incident.end_date is null) 
    group by 
      project_id,
      datepart(yy, all_dates.d) year,
      datepart(mm, all_dates.d) month
    

    这不会像我们希望的那样工作,因为每个月发生事件的每一天都会统计。要解决这个问题,我们需要使用子查询或临时表,这实际上取决于 RDBMS...

    它的另一个问题是,对于未解决的事件,它会在你的 all_dates 表中显示它们与未来的所有月份。添加all_dates.d &lt;= today 解决了这个问题。同样,不同的 RDBMS 有不同的方法来回馈现在/今天/系统时间...

    另一种方法是使用 all_months 而不是 all_dates 表,其中仅包含月初的日期:

    create table all_months (first_of_month date)
    
    select
      project_id,
      datepart(yy, all_months.first_of_month) as "year",
      datepart(mm, all_months.first_of_month) as "month",
      count(*) as "count"
    from
      incident,
      all_months
    where
      incident.start_date <= dateadd(day, -1, dateadd(month, 1, first_of_month)
      (incident.end_date >= first_of_month or incident.end_date is null) 
    group by 
      project_id,
      datepart(yy, all_months.first_of_month),
      datepart(mm, all_months.first_of_month)
    

    【讨论】:

      猜你喜欢
      • 2016-02-26
      • 1970-01-01
      • 2018-02-01
      • 1970-01-01
      • 2016-12-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多