我们可以使用 Oracle 分析,即 Oracle 中的“OVER ... PARTITION BY”子句来执行此操作。PARTITION BY 子句有点像 GROUP BY,但没有聚合部分。这意味着我们可以将行组合在一起(即对它们进行分区)并且它们作为单独的组对它们执行操作。当我们对每一行进行操作时,我们就可以访问上面前一行的列。这是 PARTITION BY 给我们的特性。(PARTITION BY 与为了性能而对表进行分区无关。)
那么我们如何输出不重叠的日期呢?我们首先根据 (ID,DFROM) 字段对查询进行排序,然后使用 ID 字段来创建分区(行组)。然后,我们使用如下表达式测试前一行的 TO 值和当前行的 FROM 值是否重叠:(在伪代码中)
max(previous.DTO, current.DFROM) as DFROM
如果没有重叠,这个基本表达式将返回原始的 DFROM 值,但如果有重叠,将返回之前的 TO 值。由于我们的行是有序的,我们只需要关注最后一行。如果前一行与当前行完全重叠,我们希望该行具有“零”日期范围。所以我们对 DTO 字段做同样的事情来获得:
max(previous.DTO, current.DFROM) as DFROM, max(previous.DTO, current.DTO) as DTO
一旦我们使用调整后的 DFROM 和 DTO 值生成了新结果集,我们就可以将它们聚合起来并计算 DFROM 和 DTO 的范围间隔。
请注意,数据库中的大多数日期计算都不包含在内,例如您的数据。所以像 DATEDIFF(dto,dfrom) 这样的东西不会包括 dto 实际指的日期,所以我们要先将 dto 向上调整一天。
我不再有权访问 Oracle 服务器,但我知道这可以通过 Oracle Analytics 实现。查询应该是这样的:(如果你让它工作,请更新我的帖子。)
SELECT id,
max(dfrom, LAST_VALUE(dto) OVER (PARTITION BY id ORDER BY dfrom) ) as dfrom,
max(dto, LAST_VALUE(dto) OVER (PARTITION BY id ORDER BY dfrom) ) as dto
from (
select id, dfrom, dto+1 as dto from my_sample -- adjust the table so that dto becomes non-inclusive
order by id, dfrom
) sample;
这里的秘密是LAST_VALUE(dto) OVER (PARTITION BY id ORDER BY dfrom)表达式,它返回当前行之前的值。所以这个查询应该输出不重叠的新 dfrom/dto 值。然后,子查询此操作(dto-dfrom)并求和总数是一件简单的事情。
使用 MySQL
我确实可以访问 mysql 服务器,所以我确实让它在那里工作。MySQL 没有像 Oracle 这样的结果分区(分析),所以我们必须使用结果集变量。这意味着我们使用@var:=xxx 类型的表达式来记住最后的日期值,并据此调整 dfrom/dto。相同的算法只是更长和更复杂的语法。我们还必须在 ID 字段更改时忘记最后一个日期值!
所以这里是示例表(你有相同的值):
create table sample(id int, dfrom date, dto date, networkDay int);
insert into sample values
(1,'2012-09-03','2012-09-07',5),
(1,'2012-09-03','2012-09-04',2),
(1,'2012-09-05','2012-09-06',2),
(1,'2012-09-06','2012-09-12',5),
(1,'2012-08-31','2012-09-04',3),
(2,'2012-09-04','2012-09-06',3),
(2,'2012-09-11','2012-09-13',3),
(2,'2012-09-05','2012-09-08',3);
在查询中,我们像上面一样输出未分组的结果集:变量@ld 是“last date”,变量@lid 是“last id”。每当@lid 发生变化时,我们都会将@ld 重置为null。仅供参考在 mysql 中, := 运算符是赋值发生的地方, = 运算符只是等于。
这是一个 3 级查询,但可以减少到 2 级。我使用了一个额外的外部查询以使内容更具可读性。最里面的查询很简单,它将dto列调整为不包含在内并进行正确的行排序。中间查询调整 dfrom/dto 值以使它们不重叠。外部查询简单删除未使用的字段,并计算区间范围。
set @ldt=null, @lid=null;
select id, no_dfrom as dfrom, no_dto as dto, datediff(no_dto, no_dfrom) as days from (
select if(@lid=id,@ldt,@ldt:=null) as last, dfrom, dto, if(@ldt>=dfrom,@ldt,dfrom) as no_dfrom, if(@ldt>=dto,@ldt,dto) as no_dto, @ldt:=if(@ldt>=dto,@ldt,dto), @lid:=id as id,
datediff(dto, dfrom) as overlapped_days
from (select id, dfrom, dto + INTERVAL 1 DAY as dto from sample order by id, dfrom) as sample
) as nonoverlapped
order by id, dfrom;
上面的查询给出了结果(注意 dfrom/dto 在这里不重叠):
+------+------------+------------+------+
| id | dfrom | dto | days |
+------+------------+------------+------+
| 1 | 2012-08-31 | 2012-09-05 | 5 |
| 1 | 2012-09-05 | 2012-09-08 | 3 |
| 1 | 2012-09-08 | 2012-09-08 | 0 |
| 1 | 2012-09-08 | 2012-09-08 | 0 |
| 1 | 2012-09-08 | 2012-09-13 | 5 |
| 2 | 2012-09-04 | 2012-09-07 | 3 |
| 2 | 2012-09-07 | 2012-09-09 | 2 |
| 2 | 2012-09-11 | 2012-09-14 | 3 |
+------+------------+------------+------+