【问题标题】:Sql Grouping by change DateSql 按更改日期分组
【发布时间】:2020-12-17 08:31:51
【问题描述】:

我有一个表格,它给出了一个对象的历史。每次更改一行。 对于我的任务,我只对一个更改的字段感兴趣:状态。这是一个表格示例,缩小到一个对象,省略了许多列:

OBJECT_ID STATUS CHANGE_DATE
173129 P 2020-11-05
173129 A 2020-10-25
173129 A 2020-03-15
173129 A 2019-08-29
173129 P 2019-08-29
173129 A 2019-06-07
173129 A 2018-12-02
173129 A 2018-09-01
173129 A 2018-07-29
173129 A 2018-03-13
173129 A 2018-02-15

我需要以某种方式对这些实体进行分组,以找出状态何时更改并忽略其他行。

所以我做的是:

select OBJECT_ID ,
       STATUS ,
       MIN(CHANGE_DATE ) AS
           CHANGE_DATE 
FROM HISTORIC
GROUP BY OBJECT_ID , STATUS 
order by OBJECT_ID  desc, CHANGE_DATE  desc

这工作了一段时间,但后来发现状态可以从 A(活动)多次更改为 P(待定),然后来回切换。 STATUS 还有几个可能的值。

对象最后一次挂起的日期对我来说更重要,而我的查询会在第一次状态更改时得到我。

我的预期结果可能是两件事之一:

  1. 包含所有状态更改行的表:
OBJECT_ID STATUS CHANGE_DATE
173129 P 2020-11-05
173129 A 2019-08-29
173129 P 2019-08-29
173129 A 2018-02-15
  1. 包含特定 STATUS 的最新状态更改的表格:
OBJECT_ID STATUS CHANGE_DATE
173129 P 2020-11-05
173129 A 2019-08-29

我有不少复杂的需求,比如

  • 在特定日期处于活动状态但同时被设置为待处理的所有对象

【问题讨论】:

  • 如果有人能为这个问题想出更好的标题,请随时更改。
  • 请向我们展示您对上述数据样本的预期结果。
  • 到目前为止你做了什么? LAG 解析函数是你的朋友;)

标签: sql oracle datetime gaps-and-islands


【解决方案1】:

您可以使用lag() 获得第一个结果集:

select *
from (
    select h.*,
        lag(status) over(partition by object_id order by change_date) as lag_status
    from historic h
) h
where lag_status is null or lag_status <> status

这个想法是将每一行的状态与“前”行的状态进行比较,并显示它们不相等的行。

请注意,这仅适用于您有一个明确定义每组object_ids 的行顺序的列。我使用了change_date,但在您的数据中有联系。由于您使用的是 Oracle,我假设这些日期有一个时间部分,这里没有显示,这可以打破关系。如果不是这种情况,则需要弄清楚可以使用哪一列来打破平局,并将其添加到lag()order by 子句中。

【讨论】:

  • 日期有一个时间部分,但它总是 00:00:00 所以我在这里省略了它。我什至没有意识到一天内更改了两次状态而无法订购。那会让我很伤心。数据通过不同系统的管道传递给我们,并且在某个地方有人认为变化的时间并不重要(facepalm
【解决方案2】:

包含所有状态更改行的表:

with previous_statuses$ as (
    select X.*,
        lag(status) over (partition by object_id order by change_date) as prev_status
    from historic X
)
select object_id, status, change_date
from previous_statuses$
where status != prev_status
    or prev_status is null
;

如果你只想要状态的最后一次改变,那么

with previous_statuses$ as (
    select X.*,
        lag(status) over (partition by object_id order by change_date) as prev_status
    from historic X
)
select object_id,
    max(status) keep (dense_rank last order by change_date),
    max(change_date)
from previous_statuses$
where status != prev_status
    or prev_status is null
group by object_id;

具有特定 STATUS 的最新状态更改的表:

with previous_statuses$ as (
    select X.*,
        lag(status) over (partition by object_id order by change_date) as prev_status
    from historic X
)
select object_id, status,
    max(change_date)
from previous_statuses$
where status != prev_status
    or prev_status is null
group by object_id, status;

【讨论】:

  • $ 约定是什么?您是否只将其用于 CTE 别名,或者..?
  • 感谢您付出额外的努力并满足我的所有期望
  • @kfinity,只是为了标记一些不同寻常的东西。在这种情况下,是的,在视觉上将 CTE 与常规表区分开来。
【解决方案3】:

从 Oracle 12 开始,您可以使用 MATCH_RECOGNIZE

每个状态的最新变化:

SELECT *
FROM   historic
MATCH_RECOGNIZE (
  PARTITION BY object_id
  ORDER     BY change_date
  MEASURES
    LAST( active.change_date )  AS last_active,
    LAST( pending.change_date ) AS last_pending
  PATTERN ( pending* active* pending* $ )
  DEFINE
    active     AS status = 'A',
    pending    AS status = 'P'
)

哪些输出:

对象 ID | LAST_ACTIVE | LAST_PENDING --------: | :----------------- | :----------------- 173129 | 2020-11-05 00:00:00 | 2019-08-29 00:00:00

或者直接使用GROUP BY:

SELECT object_id,
       status,
       MAX( CHANGE_DATE ) AS change_date
FROM   historic
GROUP BY
       object_id,
       status;

哪些输出:

对象 ID |状态 |改变日期 --------: | :----- | :----------------- 173129 |磷 | 2019-08-29 00:00:00 173129 |一个 | 2020-11-05 00:00:00

所有状态变化:

SELECT *
FROM   historic
MATCH_RECOGNIZE (
  PARTITION BY object_id
  ORDER     BY change_date
  MEASURES
    FIRST( status )     AS status,
    LAST( change_date ) AS change_date
  PATTERN ( same_status+ )
  DEFINE same_status AS FIRST( status ) = status
)

哪些输出:

对象 ID |状态 |改变日期 --------: | :----- | :----------------- 173129 |一个 | 2019-08-29 00:00:00 173129 |磷 | 2019-08-29 00:00:00 173129 |一个 | 2020-11-05 00:00:00

对于您的示例数据:

CREATE TABLE historic ( OBJECT_ID, STATUS, CHANGE_DATE ) AS
SELECT 173129, 'A', DATE '2020-11-05' FROM DUAL UNION ALL
SELECT 173129, 'A', DATE '2020-10-25' FROM DUAL UNION ALL
SELECT 173129, 'A', DATE '2020-03-15' FROM DUAL UNION ALL
SELECT 173129, 'A', DATE '2019-08-29' FROM DUAL UNION ALL
SELECT 173129, 'P', DATE '2019-08-29' FROM DUAL UNION ALL
SELECT 173129, 'A', DATE '2019-06-07' FROM DUAL UNION ALL
SELECT 173129, 'A', DATE '2018-12-02' FROM DUAL UNION ALL
SELECT 173129, 'A', DATE '2018-09-01' FROM DUAL UNION ALL
SELECT 173129, 'A', DATE '2018-07-29' FROM DUAL UNION ALL
SELECT 173129, 'A', DATE '2018-03-13' FROM DUAL UNION ALL
SELECT 173129, 'A', DATE '2018-02-15' FROM DUAL;

db小提琴here

【讨论】:

    猜你喜欢
    • 2017-02-15
    • 1970-01-01
    • 2010-10-17
    • 2014-10-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-18
    相关资源
    最近更新 更多