jpm*_*c26 0 oracle oracle-11g-r2
考虑下表:
T_ID | T_START | T_END
-----+---------+------
1 | 0.25 | 0.5
2 | 0.8 | 1
3 | 0.4 | 0.6
4 | 0.2 | 0.3
5 | 0.7 | 0.8
Run Code Online (Sandbox Code Playgroud)
T_ID
是独特的。每行代表一个连续范围的数字,是 0 到 1 的子集。T_START
小于T_END
。
我需要确定未包含在 0 和 1 之间的任何范围。请注意,某些范围确实重叠。端点的排他性与我的用例无关;我只需要确定差距的端点是什么。(因此,不考虑单点差距。)
对于这个特定的数据集,我希望结果是
GAP_START | GAP_END
----------+--------
0 | 0.2
0.6 | 0.7
Run Code Online (Sandbox Code Playgroud)
实际数据集很大,并且将聚合到其他一些数据上(数十万行,每个聚合组可能有 100 行),因此性能很重要。(不过,性能不佳但可能会改进的答案是受欢迎的。)
我曾考虑尝试首先确定覆盖范围是什么,然后尝试反转它,但我什至无法弄清楚如何计算覆盖范围。简单GROUP BY
是不够的,因为我们有重叠范围链,它们会合并为一个范围,即使并非所有范围都相互重叠。我认为递归查询可能会有所帮助,但我还没有弄清楚它的逻辑。
我用这个示例数据集创建了一个 SQLFiddle 。
可悲的是(而且毫无成效),我不能随意修改底层表示。
经过大量的挖掘,我发现了这篇关于对日期范围执行此操作的博客文章,结果证明它在数字范围上同样有效,对处理 0 和 1 端点进行了一些修改。
从那里获取查询并将列重命名为更清楚一点,我们有
SELECT * FROM
(SELECT MAX(T_END) OVER (ORDER BY T_START) GAP_START,
LEAD(T_START) OVER (ORDER BY T_START) GAP_END
FROM T)
WHERE GAP_START < GAP_END
Run Code Online (Sandbox Code Playgroud)
这需要相当多的解释。一步步:
T_END
的,所有行T_START
小于或等于当前行的T_START
。这为我们提供了与该行范围开始重叠的所有范围的最大范围结束。T_START
的第一行的T_START
。这为我们提供了下一个最大的范围开始。T_END
大于或等于下一个的任何行T_START
。这意味着最大的重叠范围结束延伸到或超出下一个范围开始。换句话说,这一行和下一行之间没有间隙,要么是因为这一行在下一行开始的地方结束,要么是因为其他重叠的行覆盖了两者之间的所有点。但是,这并不能完全满足 0 到 1 范围内所有间隙的要求。它只选取所有行的最小范围开始和最大范围结束之间的间隙。要检查 0 和 1 范围末端的间隙,我们可以使用以下查询。
对于从 0 开始的间隙:
SELECT 0 AS GAP_START, MIN(T_START) AS GAP_END
FROM T
HAVING MIN(T_START) > 0
Run Code Online (Sandbox Code Playgroud)
对于以 1 结尾的间隙:
SELECT MAX(T_END) AS GAP_START, 1 AS GAP_END
FROM T
HAVING MAX(T_END) < 1
Run Code Online (Sandbox Code Playgroud)
如果端点没有间隙,这两个查询都不会返回任何行。
将它们全部放在一个查询中:
SELECT * FROM
(SELECT MAX(T_END) OVER (ORDER BY T_START) GAP_START,
LEAD(T_START) OVER (ORDER BY T_START) GAP_END
FROM T)
WHERE GAP_START < GAP_END
UNION ALL
SELECT 0 AS GAP_START, MIN(T_START) AS GAP_END
FROM T
HAVING MIN(T_START) > 0
UNION ALL
SELECT MAX(T_END) AS GAP_START, 1 AS GAP_END
FROM T
HAVING MAX(T_END) < 1
Run Code Online (Sandbox Code Playgroud)
结果与问题中所述完全相同,忽略顺序。查询的 SQLFiddle。