SQL 定位连续的值区间,你想确定哪些行代表了一系列在时间上连续的项目。考虑下述视图 V
的结果集,它包含了项目编号以及各自的起止日期。
SQL 定位连续的值区间 问题描述
你想确定哪些行代表了一系列在时间上连续的项目。考虑下述视图 V
的结果集,它包含了项目编号以及各自的起止日期。
除了第一行,其他每一行的 PROJ_START
应该等于前一行的 PROJ_END
(“前一行”的定义是其 PROJ_ID
等于当前行的 PROJ_ID
减 1)。仔细查看视图 V
的前 5 行,PROJ_ID
分别等于 1 到 3 的行属于同一“组”,因为每一行的 PROJ_END
都等于后一行的 PROJ_START
。我们希望找出一系列连续项目的日期区间,因此希望返回满足“当前行的 PROJ_END
等于下一行的 PROJ_START
”这一条件的所有行。如果整个结果集只包含前 5 行,那么我们希望返回的只是最前面的 3 行。(对于视图 V
全部 14 行数据而言)最终的结果集应该如下所示。
我们从结果集中剔除掉了 PROJ_ID
为 4、5、9、10 和 14 的行,因为这些行的 PROJ_END
不等于下一行的 PROJ_START
。
SQL 定位连续的值区间 解决方案
DB2、MySQL、PostgreSQL 和 SQL Server
使用自连接找出包含连续值的行。
Oracle
上述解决方案也适用于 Oracle。除此之外,Oracle 还有另一个解决方案,即利用窗口函数 LEAD OVER
去查看“下一行”的 BEGIN_DATE
,这样就不必自连接了。
SQL 定位连续的值区间 扩展知识
DB2、MySQL、PostgreSQL 和 SQL Server
通过视图 V
的自连接,每一行都可以与其他行进行比较。考虑查询 PROJ_ID
为 1 和 4 时得到的部分结果集。
仔细检查以上结果集,我们就会明白为什么 PROJ_ID 1
被包含在最终结果集中,而 PROJ_ID4
却没有。这是因为对于 V1_ID 4
而言,不存在与 V1_END
值相等的 V2_BEGIN
。
如果改变一下筛选条件,PROJ_ID 4
也可以被认为是相邻的项目。考虑如下所示的结果集。
如果“相邻的项目”是指其开始日期与另一个项目的结束日期相同,那么 PROJ_ID 4
也应该被包含在结果集里。按照最初的筛选条件,由于前向比较(比较 PROJ_END
和下一行的 PROJ_START
)的存在,PROJ_ID 4
被排除在外了,但如果做后向比较(比较 PROJ_START
和前一行的 PROJ_END
),那么 PROJ_ID 4
就应该被包含在结果集中。
不妨修改一下上述解决方案,把 PROJ_ID 4
也包含进去:只需增加一个筛选条件,确保 PROJ_START
和 PROJ_END
的相邻关系都被纳入检查范围就行了,而不是仅仅检查 PROJ_END
。下面的查询展示了上述改动,该查询产生了一个包含 PROJ_ID 4
的结果集(DISTINCT
是必要的,因为一些行同时满足两个条件)。
Oracle
上述自连接方案当然适用于本问题,不过窗口函数 LEAD OVER
用于解决这一类问题更合适。LEAD OVER
函数可以不执行自连接就能查看其他行的数据(尽管该函数要求必须对结果集进行排序)。对于 PROJ_ID1
和 PROJ_ID4
,考虑内嵌视图(第 3 ~ 5 行)的结果集。
仔细检查上面的代码片段及其结果集,不难理解为什么 PROJ_ID 4
会被从最终结果集里排除掉。这是因为 PROJ_END
的日期 2005 年 1 月 5 日与下一个项目的开始日期 2005 年 1 月 6 日不同。
LEAD OVER
函数用于解决这一类问题非常方便,尤其在只遍历部分结果的时候。使用窗口函数时,要记得它们在 FROM
和 WHERE
子句之后才会被评估,因此前面的查询里的 LEAD OVER
函数必须被放入一个内嵌视图才行。否则,LEAD OVER
函数的作用对象就变成了只含有 PROJ_ID 1
和 PROJ_ID 4
的结果集,因为执行过 WHERE
子句之后,其他行都会被过滤掉。
现在,如果改变一下筛选条件,我们也能把 PROJ_ID 4
纳入最终的结果集。考虑视图 V
的前 5 行数据。
如果我们认为 PROJ_ID 4
事实上是相邻的(因为 PROJ_ID 4
的 PROJ_START
等于 PROJ_ID 3
的 PROJ_END
),而且只有 PROJ_ID 5
应该被排除在外,那么前述解决方案就不正确了,至少是不全面的。
如果我们认为 PROJ_ID 4
应该被包含进来,只需额外加上 LAG OVER
函数,并在 WHERE
子句中也增加一个过滤条件即可。
这样一来,PROJ_ID 4
也被纳入最终结果集了,只有 PROJ_ID 5
会被排除在外。当然,如果真的要对本解决方案做出上述代码改动,一定仔细确认需求。