【问题标题】:Oracle: Select rows when value in one column changesOracle:当一列中的值更改时选择行
【发布时间】:2020-04-15 00:15:04
【问题描述】:

我有下表:

 PLACE       USER_ID Date
---------- ---------- -----------------------------
        ABC      4     14/04/20 12:05:29,255000000  
        ABC      4     14/04/20 15:42:28,389000000  
        ABC      4     14/04/20 18:33:20,202000000  
        ABC      4     14/04/20 22:51:28,339000000    
        XYZ      4     14/04/20 11:07:23,335000000     
        XYZ      2     14/04/20 12:15:12,123000000    
        ABC      4     13/04/20 22:09:33,255000000   
        QWE      4     13/04/20 10:18:29,144000000 
        XYZ      2     14/04/20 10:05:47,255000000   

当地点更改我选择的 user_id 的日期顺序时,我需要获取行。 所以想要的结果应该是这样的(对于 user_id 4):

 PLACE       USER_ID           DATE
---------- ---------- -----------------------------
        ABC      4     14/04/20 12:05:29,255000000     
        XYZ      4     14/04/20 11:07:23,335000000 
        ABC      4     13/04/20 22:09:33,255000000    
        QWE      4     13/04/20 10:18:29,144000000 

我尝试使用最小日期,但在我的示例中,如果用户回到那个地方,我会丢失数据:

 SELECT MIN(DATE), PLACE FROM user_places WHERE USER_ID=4 GROUP BY PLACE

我得到的结果(缺少一行):

 PLACE       USER_ID           DATE
---------- ---------- -----------------------------
        XYZ      4     14/04/20 11:07:23,335000000 
        ABC      4     13/04/20 22:09:33,255000000    
        QWE      4     13/04/20 10:18:29,144000000 

提前致谢!

【问题讨论】:

    标签: sql oracle date gaps-and-islands


    【解决方案1】:

    在 Oracle 12.1 及更高版本中,对于 match_recognize 子句,像这样的间隙和孤岛问题是一件容易的事。例如:

    表格设置

    alter session set nls_timestamp_format = 'dd/mm/rr hh24:mi:ss,ff';
    
    create table user_places (place, user_id, date_) as 
      select 'ABC', 4, to_timestamp('14/04/20 12:05:29,255000000') from dual union all  
      select 'ABC', 4, to_timestamp('14/04/20 15:42:28,389000000') from dual union all  
      select 'ABC', 4, to_timestamp('14/04/20 18:33:20,202000000') from dual union all
      select 'ABC', 4, to_timestamp('14/04/20 22:51:28,339000000') from dual union all
      select 'XYZ', 4, to_timestamp('14/04/20 11:07:23,335000000') from dual union all
      select 'XYZ', 2, to_timestamp('14/04/20 12:15:12,123000000') from dual union all
      select 'ABC', 4, to_timestamp('13/04/20 22:09:33,255000000') from dual union all
      select 'QWE', 4, to_timestamp('13/04/20 10:18:29,144000000') from dual union all
      select 'XYZ', 2, to_timestamp('14/04/20 10:05:47,255000000') from dual
    ;
    
    commit;
    

    查询与输出

    select place, user_id, date_
    from   (select * from user_places where user_id = 4)
    match_recognize (
      order    by date_
      all rows per match
      pattern  (a {- b* -} )
      define   b as place = a.place
    )
    order by date_ desc   --   if needed
    ;
    
    PLACE  USER_ID  DATE_
    -----  -------  ---------------------------
    ABC          4  14/04/20 12:05:29,255000000
    XYZ          4  14/04/20 11:07:23,335000000
    ABC          4  13/04/20 22:09:33,255000000
    QWE          4  13/04/20 10:18:29,144000000
    

    这里有几点需要注意:

    • DATE 是保留关键字。不是一个好的列名。我用DATE_ 反而;注意结尾的下划线。
    • 我对值 4 进行了硬编码。当然,更好的做法是把它变成一个绑定变量
    • 如果您真的只需要一次为一个user_id 执行此操作,那么执行我所做的最有效 - 首先在子查询中过滤行。但是,如果您需要对同一查询中的所有用户 ID 执行此操作,则不需要子查询;您从表本身中选择,并且您需要在match_recognize 子句的顶部,order by date_ 之前添加partition by user_id

    【讨论】:

    • 这个match recognize 子句看起来确实是一个强大的功能(尽管不幸的是,这是高度特定于Oracle 的)。我想我记得你使用了一个图形行走解决方案,但在这个简单的例子中,它更容易理解。为此 +1。
    • 谢谢@mathguy,它完全符合我的要求。在我的情况下,我在我的专栏中使用“fecha”(西班牙语表示日期),但是当我问这个问题时,我翻译了列名。虽然在准备好的语句中使用此查询时出现问题,但出现“ora-00907 缺少右括号”的错误,但它在 sql developer 上运行良好。
    【解决方案2】:

    您可以在子查询中使用lag() 来检索“上一个”地点,然后过滤上一个地点与当前地点不同的行:

    select place, user_id, date
    from (
        select t.*, lag(place) over(partition by user_id order by date) lag_place
        from mytable t
    ) t
    where lag_place is null or place <> lag_place
    

    这将为您提供所有用户的预期输出。如果你只想要用户4,那么你可以在子查询中过滤(并且不需要partition by用户):

    select place, user_id, date
    from (
        select t.*, lag(place) over(order by date) lag_place
        from mytable t
        where user_id = 4
    ) t
    where lag_place is null or place <> lag_place
    

    【讨论】:

    • OP 请求了单个 user_id 的输出,值为 4。这可以通过 where 子句修复。最好将该子句放在子查询中 - 如果只需要一个小子集,则对 all 行进行分区和排序是没有意义的,并且不清楚优化器是否会在此推送谓词案子。而且,当然,在子查询中使用该过滤器,将不再需要按 user_id 进行分区,因为无论如何都会只有一个 user_id。
    • @mathguy:很公平。我在答案中添加了另一个查询。
    • 谢谢@GMB 它运行良好,但我意识到它比 mathguy 的回答要慢一些。
    • @NicoleC:一切都很好。我也喜欢 mathgy 的回答(我赞成)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-02-26
    • 1970-01-01
    • 1970-01-01
    • 2012-06-23
    • 1970-01-01
    • 2020-12-20
    • 2013-05-09
    相关资源
    最近更新 更多