根据一列/SQL Oracle 中的值创建时间间隔

Posted

技术标签:

【中文标题】根据一列/SQL Oracle 中的值创建时间间隔【英文标题】:Create time intervals based on values in one column / SQL Oracle 【发布时间】:2020-11-02 14:05:41 【问题描述】:

我需要创建从表中返回时间间隔的查询,该查询具有(几乎)每天的属性。 原始表格如下所示:

Person | Date       | Date_Type
-------|------------|----------
Sam    | 01.06.2020 |  Vacation
Sam    | 02.06.2020 |  Vacation
Sam    | 03.06.2020 |  Work
Sam    | 04.06.2020 |  Work
Sam    | 05.06.2020 |  Work
Frodo  | 01.06.2020 |  Work
Frodo  | 02.06.2020 |  Work
.....

所需的应该是这样的:

Person | Date_Interval         | Date_Type
-------|-----------------------|----------
Sam    | 01.06.2020-02.06.2020 |  Vacation
Sam    | 03.06.2020-05.06.2020 |  Work
Frodo  | 01.06.2020-02.06.2020 |  Work
.....

将不胜感激任何想法:)

【问题讨论】:

那么,如果缺少日期,应该如何处理呢?是否应该假定 Date_Type 与那个人当时最近的 Date_Type 相同?是否应将缺少数据的间隔视为“单独的日期类型”并在输出中显示?还是应该忽略它们 - 只显示具有相同 Date_Type 的连续日期的间隔? 另外:您的 Oracle 版本是什么?在 11.2 版之前,“tabibitosan 方法”(在 Gordon Linoff 和 GMB 的答案中说明)是最有效的,但从 12.1 版开始,使用match_recognize 可以更有效地解决此类问题。 第一个 - 在 GMB cmets 下回答你第二个 - 我有 12c,但可能我很快就得用 Postgre 重写它,所以..) 好的,所以如果你必须在另一个数据库版本的SQL下重写,你最好不要match_recognize;如果 GMB 答案中的假设对您来说没问题,那么这确实是最好的答案。 (两个 ROW_NUMBER 调用的差异,即使日期不连续也能获得“连续行”间隔。) 【参考方案1】:

这听起来像是一个孤岛问题。这是一种方法:

select person, min(date) startdate, max(date) enddate, date_type
from (
    select t.*,
        row_number() over(partition by person order by date) rn1,
        row_number() over(partition by person, date_type order by date) rn2
    from mytable t
) t
group by person, date_type, rn1 - rn2

如果不是所有日期都是连续的,这也有效(因为你说你有几乎所有日期,我知道你没有所有日期)。

【讨论】:

似乎工作得很好,谢谢!现在我也知道这种普遍的问题 :) 顺便说一句,我做对了吗,在(rn1-rn2) 中,每个时期我们都会得到不同的结果,一个人有多少并不重要? 我不关注。如果“缺失日期”夹在两个带有数据的日期之间,并且具有相同的 Date_Type,则缺失的日期将包含在输出中显示的间隔中。如果缺失日期两侧数据的日期具有不同的 Date_Type,则“缺失日期”将不包含在输出的间隔中。这似乎是如此不一致,以至于我很难想到一个业务用例,这将是理想的处理方式。 哦。你是对的。我想我的任务可以做出这些假设。实际上不应该有很多像这样的滞后(仅取决于 HR 数据的正确性),如果发现任何滞后 - 我会告诉他们:)【参考方案2】:

这是一种孤岛问题。

要获得具有相同date_type 的相邻日期,您可以减去一个序列。它将在相邻的日子里保持不变。然后就可以聚合了:

select person, date_type, min(date), max(date)
from (select t.*,
             row_number() over (partition by person, date_type
                                             order by date) as seqnum
      from t
     ) t
group by person, date_type, (date - seqnum);

【讨论】:

您对 OP 有任何问题,关于错过的日期吗?他提到了它们,但他没有告诉我们必须如何处理它们,并且样本数据没有任何示例。在您的回答中,您假设必须以特定方式处理它们,这可能是也可能不是 OP 需要的;无论如何,如果您能明确说明您的假设,那将非常有帮助。 试过这个,如果我们一个人有几个不同的时期,它似乎不起作用,例如:type1,然后type2,然后再type1。此解决方案将为所有 type1 日期创建一个间隔。在这种情况下,具有两个 row_nums 的 @GMB 版本效果更好。 @DenisKa - Gordon 的解决方案并不比 GMB 的更简单(但它对丢失日期做出了不同的假设!) - 除了 Gordon 有时不注意细节。要更正此答案,请将 Date_Type 添加到 ROW_NUMBER 函数的 PARTITION BY 子句。 是的。评论后我也试过了:)也适用于正确的分区。非常感谢您的分析! @DenisKa 。 . .我怀疑这个版本接近你真正想要完成的。【参考方案3】:

最简单的方法之一是使用MATCH_RECOGNIZE进行逐行比较和聚合:

SELECT *
FROM   table_name
MATCH_RECOGNIZE (
  PARTITION BY Person
  ORDER     BY "DATE"
  MEASURES
    FIRST( "DATE" )    AS start_date,
    LAST( "DATE")      AS end_date,
    FIRST( Date_Type ) AS date_type
  ONE ROW PER MATCH
  PATTERN ( successive_dates+ )
  DEFINE
    SUCCESSIVE_DATES AS (
          FIRST( Date_Type ) = NEXT( Date_Type )
      AND MAX( "DATE" ) + INTERVAL '1' DAY = NEXT( "DATE")
    )
);

其中,对于样本数据:

CREATE TABLE table_name ( Person, "DATE", Date_Type ) AS
SELECT 'Sam',   DATE '2020-06-01', 'Vacation' FROM DUAL UNION ALL
SELECT 'Sam',   DATE '2020-06-02', 'Vacation' FROM DUAL UNION ALL
SELECT 'Sam',   DATE '2020-06-03', 'Work' FROM DUAL UNION ALL
SELECT 'Sam',   DATE '2020-06-04', 'Work' FROM DUAL UNION ALL
SELECT 'Sam',   DATE '2020-06-05', 'Work' FROM DUAL UNION ALL
SELECT 'Frodo', DATE '2020-06-01', 'Work' FROM DUAL UNION ALL
SELECT 'Frodo', DATE '2020-06-02', 'Work' FROM DUAL;

输出:

人 | START_DATE | END_DATE | DATE_TYPE :----- | :----------------- | :----------------- | :-------- 佛罗多 | 2020-06-01 00:00:00 | 2020-06-01 00:00:00 |工作 山姆 | 2020-06-01 00:00:00 | 2020-06-01 00:00:00 |假期 山姆 | 2020-06-03 00:00:00 | 2020-06-04 00:00:00 |工作

db小提琴here

【讨论】:

感谢您的回答!我也会尝试比较,但不会使用)),因为我们似乎很快就会切换到另一个数据库......

以上是关于根据一列/SQL Oracle 中的值创建时间间隔的主要内容,如果未能解决你的问题,请参考以下文章

根据另一列中的值从一列中减去值(SQL)

oracle如何根据另一张表中的一行的值来选择一列

oracle 根据某一行的值转化成列?

sql一列有多值查询,根据多个只查询我想要的数据

SQL Server2008 触发器中,根据一个表修改另外一个表

在 pandas 中,如何根据一列中的唯一值创建列,然后根据另一列中的值填充它?