MSSQL:考虑日期范围的 DISTINCT

MSSQL: DISTINCT that takes date-ranges into consideration

我有一个 table 看起来不太像:

 K_PKEY          D_FROM               D_TO                 PERC
============ ==================== ==================== ===========
0013         01-JAN-2009 00:00:00 31-JUL-2011 00:00:00
0013         01-AUG-2011 00:00:00 31-DEC-2011 00:00:00
0013         01-JAN-2012 00:00:00 31-MAR-2012 00:00:00
0013         01-APR-2012 00:00:00 31-DEC-2012 00:00:00   75.000000
0013         01-JAN-2013 00:00:00 31-JAN-2013 00:00:00   50.000000
0013         01-FEB-2013 00:00:00 28-FEB-2013 00:00:00   50.000000
0013         01-MAR-2013 00:00:00 31-AUG-2013 00:00:00   75.000000
0013         01-SEP-2013 00:00:00 31-MAY-2015 00:00:00   75.000000
0013         01-JUN-2015 00:00:00 31-DEC-2100 00:00:00

我正在尝试构建一个考虑特定日期范围的 DISTICT 查询。

这是我想出的:

SELECT DISTINCT k_pkey, MIN(d_from), MAX(d_to), perc FROM my_table GROUP BY k_pkey

它没有按照我想要的方式工作,我明白为什么。 MIN() 和 MAX() 结合 DISTINCT 在全局范围内工作,这对于该类型的查询来说是很自然的。这导致:

K_PKEY           D_FROM               D_TO                 PERC
============ ==================== ==================== ===========
0013         01-JAN-2009 00:00:00 31-DEC-2100 00:00:00
0013         01-APR-2012 00:00:00 31-MAY-2015 00:00:00   75.000000
0013         01-JAN-2013 00:00:00 28-FEB-2013 00:00:00   50.000000

我想要实现的是保持时间顺序并仅组合(可以说)彼此相邻的范围。

K_PKEY           D_FROM               D_TO                 PERC
============ ==================== ==================== ===========
0013         01-JAN-2009 00:00:00 31-MAR-2012 00:00:00
0013         01-APR-2012 00:00:00 31-DEC-2012 00:00:00   75.000000
0013         01-JAN-2013 00:00:00 28-FEB-2013 00:00:00   50.000000
0013         01-MAR-2013 00:00:00 31-MAY-2015 00:00:00   75.000000
0013         01-JUN-2015 00:00:00 31-DEC-2100 00:00:00

是否可以使用一个 sql 查询(如果可能我不想使用 sql 过程)?有什么建议吗?

您正试图根据日期和 PERC 相同的日期将相邻的行合并在一起。这个想法是使用 left join 来确定哪些值开始一个新的范围。然后,使用累计和来计算每行的启动次数。后一个值可用于分组。

在SQLServer 2012+中,可以直接做累加。在早期版本中,您将使用 outer apply.

生成的查询如下所示:

select k_pkey, min(d_from) as d_from, max(d_to) as d_to, perc
from (select t.*,
             sum(IsGroupStart) over (partition by k_pkey, perc order by d_from) as grp
      from (select t.*,
                   (case when t_prev.k_pkey is null then 1 else 0 end) as IsGroupStart
            from t left join
                 t tprev
                 on tprev.k_pkey = t.k_pkey and
                    (tprev.perc = t.perc or tprev.perc is null and t.perc is null) and
                    tprev.d_to = dateadd(day, -1, t.d_from)
           ) t
      ) t
group by grp, k_pkey, perc;