当一个的结束时间是另一个的开始时间时合并行(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 选择具有开始和结束日期的行,如果某些重叠合并行