当一个的结束时间是另一个的开始时间时合并行(Oracle)

Posted

技术标签:

【中文标题】当一个的结束时间是另一个的开始时间时合并行(Oracle)【英文标题】:Combine rows when the end time of one is the start time of another (Oracle) 【发布时间】:2011-09-14 17:41:50 【问题描述】:

我似乎无法弄清楚这个查询。我需要将多行时间连续的状态组合成一个状态。

除了我使用的是 Oracle 10 而不是 SQL Server:Combine rows when the end time of one is the start time of another

示例数据:

name      start_inst         end_inst            code     subcode
Person1 9/12/2011 10:55 9/12/2011 11:49           161   50
Person1 9/12/2011 11:49 9/12/2011 11:55           107   28
Person1 9/12/2011 11:55 9/12/2011 12:07           161   50
Person1 9/12/2011 12:07 9/12/2011 12:26           161   50
Person1 9/12/2011 12:26 9/12/2011 12:57           161   71
Person1 9/12/2011 12:57 9/12/2011 13:07           161   71
Person1 9/12/2011 13:07 9/12/2011 13:20            52   50

我想得到以下输出:

name       start_inst       end_inst            code     subcode
Person1 9/12/2011 10:55     9/12/2011 11:49     161     50
Person1 9/12/2011 11:49     9/12/2011 11:55     107     28
Person1 9/12/2011 11:55     9/12/2011 12:26     161     50
Person1 9/12/2011 12:26     9/12/2011 13:07     161     71
Person1 9/12/2011 13:07     9/12/2011 13:20     52      50

这里是示例 SQL:

CREATE TABLE Data (
    name varchar2(132 BYTE) not null,
    start_inst DATE not null,
    end_inst DATE not null,    
code number(3) not null,
subcode number(3) not null
);
INSERT INTO Data(name,start_inst,end_inst, code, code2) VALUES('Person1','9/12/2011 10:55','9/12/2011 11:49',161, 50);
INSERT INTO Data(name,start_inst,end_inst, code, code2) VALUES('Person1','9/12/2011 11:49','9/12/2011 11:55',107,28);
INSERT INTO Data(name,start_inst,end_inst, code, code2) VALUES('Person1','9/12/2011 11:55','9/12/2011 12:07',161,50);
INSERT INTO Data(name,start_inst,end_inst, code, code2) VALUES('Person1','9/12/2011 12:07','9/12/2011 12:26',161,50);
INSERT INTO Data(name,start_inst,end_inst, code, code2) VALUES('Person1','9/12/2011 12:26','9/12/2011 12:57',161,71);
INSERT INTO Data(name,start_inst,end_inst, code, code2) VALUES('Person1','9/12/2011 12:57','9/12/2011 13:07',161,71);
INSERT INTO Data(name,start_inst,end_inst, code, code2) VALUES('Person1','9/12/2011 13:07','9/12/2011 13:20',52,50);

提前致谢!

【问题讨论】:

为了使您的示例 SQL 正确执行,您可以进行以下更改: a) 在 INSERT 语句之前添加 ALTER SESSION SET NLS_DATE_FORMAT = 'MM/DD/YYYY HH24:MI'; 以便正确解释日期字符串(或显式使用具有该格式的 TO_DATE INSERT 语句),并且,b)将 INSERT 语句更改为使用“subcode”而不是“code2”,以便它们与 CREATE TABLE 语句一致。 关心投票并接受对您有帮助的答案? 【参考方案1】:

也许是这个? (我没有运行它的 SQL 机器)

WITH
  sequenced_data AS
(
  SELECT
    ROW_NUMBER() OVER (PARTITION BY name                ORDER BY start_inst) NameSequenceID,
    ROW_NUMBER() OVER (PARTITION BY name, code, subcode ORDER BY start_inst) NameStateSequenceID,
    *
  FROM
    data
)
SELECT
  name,
  MIN(start_inst) start_inst,
  MAX(end_inst)   end_inst,
  code,
  subcode
FROM
  sequenced_data
GROUP BY
  name,
  code,
  subcode,
  NameSequenceID - NameStateSequenceID

【讨论】:

谢谢。我正在处理的实际查询比这要多得多,但这是我坚持的部分。我很感激。【参考方案2】:

这是一个使用递归查询而不是分析函数的解决方案(正如@wildplasser 所建议的那样):

SELECT   name, code, subcode, MIN(start_inst) AS start_inst, MAX(end_inst) AS end_inst
FROM     (SELECT     name,
                     start_inst,
                     end_inst,
                     code,
                     subcode,
                     MIN(CONNECT_BY_ROOT (start_inst)) AS root_start
          FROM       data d
          CONNECT BY PRIOR name = name 
                 AND PRIOR end_inst = start_inst 
                 AND PRIOR code = code 
                 AND PRIOR subcode = subcode
          GROUP BY   name, start_inst, end_inst, code, subcode)
GROUP BY name, code, subcode, root_start;

最内层查询中的connect by 子句导致数据以分层方式返回。 connect_by_root 为我们提供了每个分支根的值。因为我们没有一个很好的 start with 子句候选者,所以我们将多次获得所有子行(其中end_inst 等于另一行的start_inst 并且所有其他列都相同):一次作为根,然后一次(或多次)作为一个分支。取根的min 会消除这些额外的行,同时为我们提供一个值以在外部查询中进行分组。

在外部查询中,我们执行另一个group by 来合并行。不同之处在于,在这种情况下,我们还有root_start 来识别哪些行是连续的,因此需要合并。

【讨论】:

【参考方案3】:

这是另一种方法:

SELECT
    name,
    min(start_inst) AS start_inst,
    max(end_inst) AS end_inst,
    code,
    subcode
FROM
    (
        SELECT
            A.*,
            COUNT
            (
                CASE WHEN start_inst = previous_end_inst THEN NULL
                ELSE 1
                END
            )
            OVER
            (
                ORDER BY
                    start_inst,
                    name,
                    code,
                    subcode
            ) AS group_number
        FROM
            (
                SELECT
                    name,
                    start_inst,
                    end_inst,
                    LAG
                    (
                      end_inst
                    )
                    OVER
                    (
                        PARTITION BY
                            name,
                            code,
                            subcode
                        ORDER BY
                            start_inst
                    ) AS previous_end_inst,
                    code,
                    subcode
                FROM
                    data
            ) A
        ) B
GROUP BY
    name,
    code,
    subcode,
    group_number
ORDER BY
    group_number

基本上:

    对于每一行,子查询 A 查找给定名称、代码和子代码的上一个结束时间。

    对于每一行,子查询 B 计算“组号”——前行的运行计数(按 start_inst、名称、代码和子代码的顺序),其中在步骤 1 中计算的上一个结束时间不是等于开始时间。

    外部查询按组号聚合。

无论好坏,这种方法与@stevo 不同,如果一条记录的结束时间和下一条记录的开始时间之间存在“间隙”,则这种方法将创建一个新的“组”。例如,如果您要像这样在 12:57 和 13:00 之间创建一个间隙......

UPDATE data
SET start_inst = TO_DATE('9/12/2011 13:00', 'MM/DD/YYYY HH24:MI')
WHERE start_inst = TO_DATE('9/12/2011 12:57', 'MM/DD/YYYY HH24:MI');

...上面的查询会像这样返回两行...

NAME                 START_INST       END_INST               CODE    SUBCODE
-------------------- ---------------- ---------------- ---------- ----------
.
.
.
Person1              09/12/2011 12:26 09/12/2011 12:57        161         71
Person1              09/12/2011 13:00 09/12/2011 13:07        161         71
.
.
.

...而@stevo 的查询将返回这样的一行...

NAME                 START_INST       END_INST               CODE    SUBCODE
-------------------- ---------------- ---------------- ---------- ----------
.
.
.
Person1              12/09/2011 12:26 12/09/2011 13:07        161         71
.
.
.

希望这会有所帮助。

【讨论】:

当您说 Stevo 的解决方案时,您是指他从另一个答案中获取的那个? ;) 在这种情况下,另一种解决方案对我来说效果更好。不过,我保留了这个以供将来参考。谢谢! 一个非常聪明的解决方案,具有非常有趣的技术,应该对其他一些类似的问题很有用。不过,我认为代码中有一个错误,它在 COUNT 中显示 ORDER BY start_inst, name, code, subcode,它应该显示为 ORDER BY name, code, subcode, start_inst,因此模仿了下面 LAG 设置的顺序。对于示例数据(由上面的@brianc 修改),结果没有区别,但是对于我用于解决问题的真实数据,这种更改对于获得正确的结果是必要的。谢谢! @mprost,感谢您的评论。 COUNT 中的 ORDER BY 是经过深思熟虑的,以便我们(也)可以使用它来获得原始海报问题中最终结果的所需顺序。也许您面临的问题略有不同?要模仿LAG,您可以将PARTITION BY 添加到COUNT 并将name, code, subcode 从其ORDER BY 移到那里。不过,您可能需要更改最外层查询中的 ORDER BY... @brianc 好的,我知道区别在哪里,在我的数据中,我可以同时激活 n 个代码子代码,我必须考虑到这一点。如果您INSERT INTO Data VALUES('Person1','9/12/2011 12:00','9/12/2011 12:01',99,99),从而在 OP 的数据中创建这种情况,您会看到从 11:55 和 12:07 开始的 161-50 行不再属于同一组,尽管是连续的,但我对您的修改代码将它们放回同一组中。所以你是对的,你的代码没有错误,只是数据不同。感谢您的解决方案并花时间解决我的问题。【参考方案4】:

适应desm的查询,我认为这应该可以工作

WITH
  sequenced_data AS
(
SELECT
ROW_NUMBER() OVER (PARTITION BY name                ORDER BY start_inst) NameSequenceID,
ROW_NUMBER() OVER (PARTITION BY name, code, subcode ORDER BY start_inst)     NameStateSequenceID,
d.*
FROM
data d
) 
SELECT
  name,
  to_char(MIN(start_inst),'DD/MM/YYYY HH24:MI') start_inst,
  to_char(MAX(end_inst),'DD/MM/YYYY HH24:MI')   end_inst,
  code,
  subcode
FROM
  sequenced_data
GROUP BY
  name,
  code,
  subcode,
  NameSequenceID - NameStateSequenceID
ORDER BY name,start_inst

【讨论】:

谢谢。我正在处理的实际查询比这要多得多,但这是我坚持的部分。我很感激。 我实际上建议不要这样做。不需要将日期时间转换为服务器上的字符串。如果结果在服务器内部使用,保留日期可以更容易地进行操作。如果结果正在客户端中使用,则传回一个不会被误解的数据类型,使用更少的空间,如果客户端界面发生更改,则更容易格式化等等。除非有非常充分的理由,否则字符串格式化通常是最好的 ekpt 客户端。 @dems 我同意这一点,只是想说明结果【参考方案5】:

您可以通过递归查询来做到这一点(在 oracle,IIRC 中使用 CONNECT BY / PRIOR)我在这个线程中为 Postgres 做了同样的事情:Get total time interval from multiple rows if sequence not broken

它可能需要进行一些修改以使其适合 oracle 语法。

【讨论】:

以上是关于当一个的结束时间是另一个的开始时间时合并行(Oracle)的主要内容,如果未能解决你的问题,请参考以下文章

合并时间表行,开始时间和结束时间之间没有间隔

如果基于开始日期的行之间没有更改,则合并员工历史记录

Oracle SQL 选择具有开始和结束日期的行,如果某些重叠合并行

敏捷8结束其实是另一个新的开始

如何使用 VBSCript 在 Excel 中查找合并单元格的开始行和结束行?

ORA-03113 文件通信结束