Sql 查询以获取给定时间范围内具有给定状态的项目计数

Sql 查询以获取给定时间范围内具有给定状态的项目计数,sql,sql-server,tsql,Sql,Sql Server,Tsql,最近在工作中出现了一个有趣的SQL问题,在过去,如果没有多个步骤,我无法解决这个问题 假设您有一个简单的ItemStatus表,该表用于跟踪不同项目的状态。列为itemId、日期状态更改和状态。例如: ItemId DateStatusChanged Status 1 09/01/2016 New 1 10/15/2016 Complete 2 10/20/2016

最近在工作中出现了一个有趣的SQL问题,在过去,如果没有多个步骤,我无法解决这个问题

假设您有一个简单的ItemStatus表,该表用于跟踪不同项目的状态。列为itemId、日期状态更改和状态。例如:

ItemId      DateStatusChanged    Status
1           09/01/2016             New
1           10/15/2016             Complete
2           10/20/2016             New
2           10/25/2016             Complete
在项目发生更改之前,假定其状态相同。因此,第1项从9月1日开始是新的,直到10月15日才改变为完成。项目2在20月10日至25日期间是新的


因此,假设您希望查询该表,以获得2016年10月内有多少唯一项的状态为New anytime的计数,在本例中为2。是否有一个SQL查询可以返回此结果?

这是与Timeline相关的常见SQL问题之一;是的,有一个解决办法。如果记录也有终止日期,则可以编写一个更简单、更高效的查询,但当然,这意味着您在仅查看一条记录时不会推断那么多,但它也会产生潜在无效序列的问题,例如时间轴中的重叠状态或间隙。所以

select count(distinct ItemId)
  from ItemStatus is1
 where status = 'New'
   and DateStatusChanged < '2016-11-01' -- syntax may vary
   and not exists
       (select 1 
          from itemstatus is2
         where is2.itemid = is1.itemid
           and is2.status != 'New'
           and is2.datestatuschanged > is1.datestatuschanged
           and is2.datestatuschanged < '2016-10-01')

您可能需要调整一些您可以使用条件聚合使每一行都有一个新的完整日期。从这里开始,查询实际上相当简单

像这样的事情应该会给你指明正确的方向

select count(distinct ItemID) as ItemCount
from 
(
    select ItemID
        , max(case when Status = 'New' then DateStatusChanged end) as NewDate
        , Max(case when Status = 'Complete' then DateStatusChanged end) as CompleteDate
    from YourTable
    group by ItemID
) MyItems
where NewDate >= '2016-10-01'
    and CompleteDate >= '2016-10-01'
    and CompleteDate >= NewDate --just to ensure that is wasn't marked complete before it was marked new
您可以使用lead根据datestatuschanged的升序获取每个itemid的下一个状态更改日期。然后检查下一次更改或现有更改日期是否在给定日期之间,并计算这些项目

select count(distinct ItemID) 
from (select i.*
     ,lead(datestatuschanged) over(partition by itemid order by datestatuschanged) as next_change
      from itemstatus i
      ) x
where status = 'New'
and ( (next_change >= '2016-10-01' and next_change <= '2016-10-31')
      or 
      (datestatuschanged >= '2016-10-01' and datestatuschanged <= '2016-10-31')
    )

您可以尝试这样的查询

select ItemId from (
select itemid, status, RowN = row_number() over (partition by itemid order by status) from youritem where MONTH(datestatuschanged) = 10 and year(datestatuschanged) = 2016  ) as SourceTable
pivot(max(status) for RowN in ([1],[2])) p
where [1] = 'Complete' and [2] = 'New'
想法是将这两种状态转换为列,并只比较和选择所需的月份和年份

轻松适应不同的国家。。。在WHERE Status='New'的情况下,以下CTE将为您提供10月份每天每个项目的状态:

;WITH DATE_CTE ( aDate ) AS (
    SELECT CAST('2016-10-01' AS DATETIME)

    UNION ALL

    SELECT DATEADD(d, 1, cte.aDate)
      FROM DATE_CTE cte
     WHERE cte.aDate < CAST('2016-10-31' AS DATETIME)
)
SELECT i.itemid, dates.aDate, i.status 
  FROM DATE_CTE dates
        INNER JOIN itemstatus i 
            ON i.DateStatusChanged <= dates.aDate
        LEFT OUTER JOIN itemstatus i2 
            ON i.ItemId = i2.ItemId 
           AND i.DateStatusChanged < i2.DateStatusChanged 
           AND dates.aDate >= i2.DateStatusChanged
 WHERE i2.DateStatusChanged IS NULL
 ORDER BY i.itemid, dates.aDate
正确答案:

;WITH DATE_CTE ( aDate ) AS (
    SELECT CAST('2016-10-01' AS DATETIME)

    UNION ALL

    SELECT DATEADD(d, 1, cte.aDate)
      FROM DATE_CTE cte
     WHERE cte.aDate < CAST('2016-10-31' AS DATETIME)
)
SELECT COUNT(DISTINCT i.itemid)
  FROM DATE_CTE dates
        INNER JOIN itemstatus i 
            ON i.DateStatusChanged <= dates.aDate
        LEFT OUTER JOIN itemstatus i2 
            ON i.ItemId = i2.ItemId 
           AND i.DateStatusChanged < i2.DateStatusChanged 
           AND dates.aDate >= i2.DateStatusChanged
 WHERE i.Status = N'New'
   AND i2.DateStatusChanged IS NULL

很好的使用窗口功能。虽然我认为日期逻辑过于复杂。您是否只需要检查新记录的dateStatusChanged'2016-10-1?假设下一次更改是日期而不是日期时间非常酷,我以前不知道lead函数。我来玩玩。xQbert-唯一的问题是,新记录在2016-10-01之后可能会发生DateStatus更改,就像ItemId 2的情况一样。我基本上想要所有在10月31日或之前有新记录的项目,其中项目的下一个非新记录出现在10月1日之后或不存在。啊,是的,完美-这与我在伪SQL中的想法一致,但不能完全转换。我知道datestatuschanged需要某种占位符变量,其中status='New',因为需要检查在该日期之后但在10月1日之前存在问题的项目是否存在任何非新的状态更改记录。多谢!不过,我认为最后一行是一个输入错误——按原样运行该查询将返回1的计数。为了得到正确的计数2,这两个项目在2016年10月内的某个时间点都是新的,查询的最后一行应该是2.datestatuschanged<'2016-10-01'。这与我过去的解决方法类似,只是我使用服务器端代码动态构建查询对象,然后返回并查询该查询对象。感谢您展示全SQL解决方案!