计算每月的公开事件

时间:2011-02-03 10:02:44

标签: sql

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

如果事件发生在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
....

2 个答案:

答案 0 :(得分:0)

这里一个有用的技术是创建一个“所有”日期的表(显然是无限的,所以我的意思是一个足够大的范围用于你的目的)或创建两个表:所有月份中的一个(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 <= 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)

答案 1 :(得分:0)

在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)

简单地说,它是如何运作的:

  • 最直接在“事件”表上工作的内部子选择,只是对表进行“规范化”(将NULL完成时间替换为当前时间)并添加月份差异列MonthDiff。如果您的案例中没有NULL,请相应删除ISNULL表达式。

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

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