我在构建一个查询时遇到了一些麻烦,该查询会根据每月存在的时间将我的项目分组到月份范围内.我正在使用PostgreSQL.
例如,我有一个数据表,如下所示:
Name Period(text)
Ana 2010/09
Ana 2010/10
Ana 2010/11
Ana 2010/12
Ana 2011/01
Ana 2011/02
Peter 2009/05
Peter 2009/06
Peter 2009/07
Peter 2009/08
Peter 2009/12
Peter 2010/01
Peter 2010/02
Peter 2010/03
John 2009/05
John 2009/06
John 2009/09
John 2009/11
John 2009/12
Run Code Online (Sandbox Code Playgroud)
我希望结果查询是这样的:
Name Start End
Ana 2010/09 2011/02
Peter 2009/05 2009/08
Peter 2009/12 2010/03
John 2009/05 2009/06
John 2009/09 2009/09
John 2009/11 2009/12
Run Code Online (Sandbox Code Playgroud)
有没有办法实现这个目标?
这是一个聚合问题,但有一个问题 - 您需要为每个名称定义相邻月份的组.
假设对于给定名称,月份永远不会出现多次,您可以通过为每个句点分配"月份"编号并减去序号来完成此操作.这些值将是连续几个月的常量.
select name, min(period), max(period)
from (select t.*,
(cast(left(period, 4) as int) * 12 + cast(right(period, 2) as int) -
row_number() over (partition by name order by period)
) as grp
from names t
) t
group by grp, name;
Run Code Online (Sandbox Code Playgroud)
这是一个说明这一点的SQL小提琴.
注意:重复也不是真正的问题.你会jsut使用dense_rank()而不是row_number().
我不知道是否有更简单的方法(可能有)但我现在想不到一个:
with parts as (
select name,
to_date(replace(period,'/',''), 'yyyymm') as period
from names
), flagged as (
select name,
period,
case
when lag(period,1, (period - interval '1' month)::date) over (partition by name order by period) = (period - interval '1' month)::date then null
else 1
end as group_flag
from parts
), grouped as (
select flagged.*,
coalesce(sum(group_flag) over (partition by name order by period),0) as group_nr
from flagged
)
select name, min(period), max(period)
from grouped
group by name, group_nr
order by name, min(period);
Run Code Online (Sandbox Code Playgroud)
第一个公用表expression(parts)simple将句点更改为日期,以便可以在算术表达式中使用它.
flagged每当当前行与前一行之间的间隙(以月为单位)不是1时,第二个CTE()就会分配一个标志.
然后,第三个CTE累积这些标志,以为每个连续的行数定义唯一的组号.
然后,最终选择只会获得每个组的开始和结束时段.我没有费心将期间转换回原始格式.
SQLFiddle示例还显示了flaggedCTE 的中间结果:http://sqlfiddle.com/#!15/8c0aa /