检查更改历史记录中连续y个月的值是否大于x

时间:2012-09-02 23:35:30

标签: sql tsql

我有成员级别更改的表。它具有所有成员级别的历史变化以及发生变化时的日期。例如,我可以列出成员编号5的更改:

select * from memberlevelhistory where member = 5

结果:

member  changedate  level
5       2012-04-01  2
5       2012-03-01  3
5       2012-02-01  2
5       2011-02-01  6
5       2011-02-01  6
5       2010-03-15  6
5       2010-02-01  5
5       2010-01-01  5
5       2009-10-01  4
5       2009-08-27  2
5       2009-08-01  1

历史记录表中的最后一个条目是当前级别。

问题: 如何在3个月或更长时间内列出所有级别高于或等于3的成员?

这是问题的简化版本。为了让它变得更有趣,我只需要在这3个月期间内没有低于起始水平的成员。因此,如果一个成员在4个月期间开始了4级并且在上个月只是3级,那么该成员将从列表中排除。

任何帮助,即使是简化的问题也非常感谢。

扩展版本:

我还需要在过去6个月的窗口内发生> = 3个月级别> = 3的这段时间。

2 个答案:

答案 0 :(得分:2)

这可能是not exists的情况。如果在接下来的三个月内没有其他条目的级别低于当前选定的记录,则条目有效。这解决了这两个要求。每个成员的最后一个条目可能存在问题,其中级别适当,但跨度未知。我已决定删除这些记录,但您可能还有其他想法。

Sql Fiddle with example is here

select distinct mlh.member
  from memberlevelhistory mlh
 where mlh.level >= 3
   and not exists
       (
         select null
           from memberlevelhistory mlh2
          where mlh2.member = mlh.member
            and mlh2.changedate >= mlh.changedate
            and mlh2.changedate < dateadd(month, 3, mlh.changedate)
            and mlh2.level < mlh.level
       )
    -- The last entry might have appropriate level
    -- But we cannot tell how long it lasted,
    -- So we are going to remove it.
   and exists
       (
         select null
           from memberlevelhistory mlh3
          where mlh3.member = mlh.member
            and mlh3.changedate > mlh.changedate
       )

编辑:

我已经重写了不存在()到左连接并添加了“持续到今天的最后一个条目”。

Sql Fiddle with example is here.

select distinct mlh.member
  from memberlevelhistory mlh
  left join memberlevelhistory mlh2
    on mlh2.member = mlh.member
   and mlh2.changedate >= mlh.changedate
   and mlh2.changedate < dateadd(month, 3, mlh.changedate)
   and mlh2.level < mlh.level
 where mlh.level >= 3
   and mlh2.member is null
   and datediff(month, mlh.changedate, getdate()) >= 3

查询重写:

; with ranges as 
(
  select mlh.member, mlh.changedate StartRange, min(isnull(mlh2.changedate, getdate())) EndDate
    from memberlevelhistory mlh
    left join memberlevelhistory mlh2
      on mlh2.member = mlh.member
     and mlh2.changedate >= mlh.changedate
     and mlh2.level < mlh.level
   where mlh.level >= 3
  group by mlh.member, mlh.changedate
 having datediff (month, min(isnull(mlh2.changedate, getdate())), getdate()) <= 6
    and datediff (month, mlh.changedate, min(isnull(mlh2.changedate, getdate()))) >= 3
)
select distinct member
  from ranges

And Sql Fiddle is here

我认为100和101应该被包括在内,因为它们都有3个月的良好运行,并且这是在3月之前的六个月之前。

我所做的是在有人跑得好的情况下产生范围,然后测试这个范围持续3个月或更长时间以及过去六个月的结束日期。

更新:如果我最终做对了,那么持续时间在过去六个月内需要持续三个月。计算可能会将更改截断为当前日期 - 六个月。使用它作为起点,并找到范围的结束点作为第一个mlh具有较低级别和较高日期作为结束点1具有足够的信息来计算持续时间。

; with ranges as 
(
  select mlh.member, 
  -- If good range starts more than six months before today
  -- truncate it to today - 6 months
         case when datediff (month, mlh.changedate, getdate()) > 6
              then dateadd(month, -6, getdate())
              else mlh.changedate 
              end StartRange,
  -- First bad mlh after current changedate
         min(isnull(mlh2.changedate, getdate())) EndRange
    from memberlevelhistory mlh
    left join memberlevelhistory mlh2
      on mlh2.member = mlh.member
     and mlh2.changedate >= mlh.changedate
     and mlh2.level < mlh.level
   where mlh.level >= 3
  group by mlh.member, mlh.changedate
  -- As above, limit good range to max six months before today
  -- And only get those lasting at least three months
 having datediff (month, case when datediff(month, mlh.changedate, getdate()) > 6
                              then dateadd(month, -6, getdate())
                              else mlh.changedate 
                          end, 
                         min(isnull(mlh2.changedate, getdate()))) >= 3
)
select distinct member
  from ranges

Sql Fiddle with example is here

答案 1 :(得分:0)

SELECT * FROM (
  SELECT MEMBER, CHANGEDATE, LEVEL, COUNT(LEVEL) AS COUNT
  FROM MEMBERLEVELHISTORY
  GROUP BY MEMBER, CHANGEDATE, LEVEL)
WHERE LEVEL >= 3
AND COUNT >= 3;

这应该对我们的简化版本有所帮助,但为了连续分组结果超出了我的意思!