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_dateend_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(或任何其他数据库)。此外,您应该将ssnpcn 更改为您正在使用的任何键(可能是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 选择具有开始和结束日期的行,如果某些重叠合并行的主要内容,如果未能解决你的问题,请参考以下文章

SQL Server连续日期 - 将多行汇总为连续的开始和结束日期行,而不包含CTE,循环,... s

sql 选择命令,从日期到日期

在 Oracle 中生成具有 2 个日期之间时间间隔的行

在Oracle中选择具有最近日期和时间的记录[重复]

SQL选择具有最大和最小日期的行

Oracle SQL 查询为包含相同 ID 的行提取最大日期的数据