Oracle SQL 选择具有开始和结束日期的行,如果某些重叠合并行
Posted
技术标签:
【中文标题】Oracle SQL 选择具有开始和结束日期的行,如果某些重叠合并行【英文标题】:Oracle SQL select rows with start and end dates and if some overlap merge row 【发布时间】:2016-03-01 15:15:59 【问题描述】:我需要选择具有开始日期和结束日期的行,如果某些日期重叠,请检查该行的其余部分是否相同,然后将这些行与 min(startdate) 和 max(startdate) 合并?我想我首先需要对重叠的行进行分组,然后我可以按此分组。
每一行都有一个 ID、start_date、end_date 和一些数据。有些行日期范围重叠,有些行不重叠,我想合并那些具有相同 ID、数据且日期范围重叠的行。
当仅尝试建议答案的前两行时,我得到了问题中最后的三行。
id valid_from valid_to
900101 06-MAY-13 02-FEB-14
900101 03-FEB-14 23-JUL-14
900102 01-JAN-10 01-DEC-10
900102 01-JAN-11 23-JAN-13
900102 01-AUG-11 23-JAN-15
900102 01-SEP-11 15-DEC-14
运行后应该是:
id valid_from valid_to
900101 06-MAY-13 02-FEB-14
900101 03-FEB-14 23-JUL-14
900102 01-JAN-10 01-DEC-10
900102 01-JAN-11 23-JAN-15
底部三行合并的地方。
建议的代码只返回最上面的两行:
900101 06-MAY-13 02-FEB-14
900101 06-MAY-13 23-JUL-14
900101 03-FEB-14 23-JUL-14
【问题讨论】:
您的表格和示例数据是什么(请包括 DDL 和 DML 语句)?您的预期结果集是什么?你试过什么? 你可能会在gaps-and-islands得到一些想法。 认为我们需要更多信息。所以它不仅仅是 select min(start_dt), max(end_date), col_one, col_two from mytable group by col_one, col_two 感谢您的提醒!我添加了更多信息,我会检查缝隙和岛屿! @AlexPoole 我没有说文学作品很容易找到。 :-) 也许这个问题会更容易。 【参考方案1】:如果您使用start_date
和end_date
编写表,您可能会从Richard Snodgrass 的Developing Time-Oriented Database Applications in SQL 中受益。人们研究像你这样的问题已经 20 多年了,这是对工作程序员的学术文献的一个很好的介绍。您可以在亚马逊上获得二手副本或阅读它for free online(在“书籍”部分)。
第 6.5 节解决了您的具体问题。例如给定这张表:
ssn | pcn | start_date | end_date
----------+--------+------------+-----------
111223333 | 120033 | 1996-01-01 | 1996-06-01
111223333 | 120033 | 1996-04-01 | 1996-10-01
111223333 | 120033 | 1996-04-01 | 1996-10-01
111223333 | 120033 | 1996-10-01 | 1998-01-01
111223333 | 120033 | 1997-12-01 | 1998-01-01
您可以使用此 SQL 合并相邻/重叠的时间段并删除重复项(稍微改编自本书以使用 CTE 而不是临时表):
WITH temp AS (
SELECT ssn, pcn, start_date, end_date
FROM incumbents
)
SELECT DISTINCT f.ssn, f.pcn, f.start_date, l.end_date
FROM temp AS f,
temp AS l
WHERE f.start_date < l.end_date
AND f.ssn = l.ssn
AND f.pcn = l.pcn
AND NOT EXISTS (SELECT 1
FROM temp AS m
WHERE m.ssn = f.ssn
AND m.pcn = f.pcn
AND f.end_date < m.start_date
AND m.start_date < l.start_date
AND NOT EXISTS (SELECT 1
FROM temp AS t1
WHERE t1.ssn = f.ssn
AND t1.pcn = f.pcn
AND t1.start_date < m.start_date
AND m.start_date <= t1.end_date))
AND NOT EXISTS (SELECT 1
FROM temp AS t2
WHERE t2.ssn = f.ssn
AND t2.pcn = f.pcn
AND ((t2.start_date < f.start_date
AND f.start_date <= t2.end_date)
OR (t2.start_date <= l.end_date
AND l.end_date < t2.end_date)))
这是 Postgres 方言,但我相信您可以将其适应 Oracle(或任何其他数据库)。此外,您应该将ssn
和pcn
更改为您正在使用的任何键(可能是id
,只要允许相同的id
在不同时间出现在多个记录中)。
【讨论】:
感谢推荐书和代码!但是当我尝试使用添加到问题中的两行时,我得到了三行(也添加了)。 我最初离开了底部的NOT EXISTS
子句,所以我会确保您在刷新浏览器后复制整个查询。
我自己在 Postgres 数据库中进行了测试,结果似乎对我来说是正确的。【参考方案2】:
这将在使用分层查询的 Oracle 中工作,并且只会查询原始数据两次
WITH d AS
(
--
SELECT DATE '2016-01-01' effective_start_date, DATE '2016-02-01' - 1 effective_end_date, 1 contract_id
FROM dual
UNION ALL --
SELECT DATE '2016-02-01', DATE '2016-04-01' - 1, 1
FROM dual
UNION ALL --
SELECT DATE '2016-04-01', DATE '2016-04-30', 1
FROM dual
UNION ALL --
SELECT DATE '2016-06-01', DATE '2016-07-01' - 1, 1
FROM dual
UNION ALL -- gap
SELECT DATE '2016-07-01' + 1, DATE '2016-07-31', 1
FROM dual
UNION ALL --
-- other contract
SELECT DATE '2016-02-01', DATE '2016-03-01' - 1, 3
FROM dual
UNION ALL --
SELECT DATE '2016-03-01', DATE '2016-03-31', 3
FROM dual
--
),
q1 AS
(
-- walk the chain backwards and get the "root" start
SELECT d.*, connect_by_root effective_start_date contract_start, LEVEL
FROM d
CONNECT BY PRIOR contract_id = contract_id
AND PRIOR effective_end_date + 1 = effective_start_date),
q2 AS
(
-- walk the chain forward and get the "root" end
SELECT d.*, connect_by_root effective_end_date contract_end, LEVEL
FROM d -
CONNECT BY PRIOR contract_id = contract_id
AND PRIOR effective_start_date = effective_end_date + 1)
-- join the forward and backward data to get the contiguous contract start and ed
SELECT DISTINCT MIN(a.contract_start) contract_start, MAX(b.contract_end) contract_end, a.contract_id
FROM q1 a
JOIN q2 b
ON a.contract_id = b.contract_id
AND a.effective_start_date = b.effective_start_date
GROUP BY a.effective_start_date, a.effective_end_date, a.contract_id
它给出了想要的结果
+-----+----------------+--------------+------------ --+ | |合同_START |合同_END |合同 ID | +-----+----------------+--------------+------------ --+ | 1 | 2016-01-01 | 2016-04-30 | 1 | | 2 | 2016-06-01 | 2016-06-30 | 1 | | 3 | 2016-07-02 | 2016-07-31 | 1 | | 4 | 2016-02-01 | 2016-03-31 | 3 | +-----+----------------+--------------+------------ --+【讨论】:
以上是关于Oracle SQL 选择具有开始和结束日期的行,如果某些重叠合并行的主要内容,如果未能解决你的问题,请参考以下文章