【问题标题】:What is the proper way to get the last entry for each foreign key of a history table?获取历史表的每个外键的最后一个条目的正确方法是什么?
【发布时间】:2011-05-19 17:42:53
【问题描述】:

我一直想知道在某个外键的历史表中找到最后一个条目的正确方法是什么?

例子:

我们假设我们有一个这样的表:

tHistory(ID | FK_User | State | Timestamp)

Oracle 中读取每个 FK_User ID 的最新条目的正确方法是什么?我总是为此使用子选择,但我觉得它不正确……还有其他方法吗?

【问题讨论】:

    标签: sql database oracle


    【解决方案1】:

    另一种方法是使用相关子查询来确定最后日期...

    SELECT
      *
    FROM
      tHistory
    WHERE
      timestamp = (
        SELECT
          MAX(timestamp)
        FROM
          tHistory latest
        WHERE
          FK_User = tHistory.FK_User
      )
    

    【讨论】:

    • 通过合理的索引(在这种情况下为 {fk_user,timestamp} ),这实际上既快速又可读。
    • 如果用户有两个或多个相同的时间戳,这可能会导致该用户出现多行。根据结果​​的使用方式,这可能会导致问题。
    • @honeyp0t - 绝对正确。这只是其自身考虑的一种方式。使用时间戳数据不太可能,因为它几乎是一个连续值,但你是对的;根据数据的行为,“最后一次”可以与多条记录相关。那么问题就变成了“如果是这种情况,是否应该针对那个时间戳返回一条或所有记录?”
    【解决方案2】:
    SELECT  *
    FROM    (
            SELECT  h.*,
                    ROW_NUMBER() OVER 
                    (PARTITION BY h.FK_User ORDER BY timestamp DESC) AS rn
            FROM    tHistory h
            )
    WHERE   rn = 1
    

    【讨论】:

    • @nino:是的,我做到了。您确定没有将子选择 (scalar = (SELECT …)) 与内联视图 (SELECT FROM (SELECT …)) 混淆吗?
    • 在术语上同意 Quassnoi 的说法,行话的一个坑(以及为什么我尝试使用更多的英语单词,而不是更少的技术词)
    • 如果时间戳可能为空,那么我建议 DESC NULLS LAST
    • @EvilTeach:这不是默认的吗?
    • 相信你会发现,默认情况下 DESC 会把 NULL 放在首位。在 ASC 排序中,它们出现在最后。试试看,告诉我你发现了什么。
    【解决方案3】:

    好问题。

    在版本 8i 之前,您需要使用子查询,为此您需要访问 thistory 表两次。 引入分析函数后,您可以使用内联视图中的 ROW_NUMBER 分析函数跳过第二个表访问,就像 Quassnoi 向您展示的那样。

    但是,从第 9 版开始,Oracle 中有一种更好、性能更高的方法。 只需按 FK_USER 分组并使用聚合函数 FIRST 或 LAST。

    第一:http://download.oracle.com/docs/cd/B10501_01/server.920/a96540/functions45a.htm#SQLRF00641 最后:http://download.oracle.com/docs/cd/B10501_01/server.920/a96540/functions57a.htm#83735

    这是一个带有 table 历史的示例:

    SQL> create table thistory (id,fk_user,state,timestamp)
      2  as
      3  select 1, 'Me', 'D', sysdate from dual union all
      4  select 2, 'Me', 'C', sysdate-1 from dual union all
      5  select 3, 'Me', 'B', sysdate-2 from dual union all
      6  select 4, 'Me', 'A', sysdate-3 from dual union all
      7  select 5, 'You', 'B', sysdate-11 from dual union all
      8  select 6, 'You', 'A', sysdate-12 from dual
      9  /
    
    Table created.
    
    SQL> exec dbms_stats.gather_table_stats(user,'thistory')
    
    PL/SQL procedure successfully completed.
    
    SQL> alter session set statistics_level = all
      2  /
    
    Session altered.
    
    SQL> set serveroutput off
    

    首先是带有子查询的 pre-8i 变体:

    SQL> select *
      2    from thistory
      3   where timestamp =
      4         ( select max(timestamp)
      5             from thistory latest
      6            where fk_user = thistory.fk_user
      7         )
      8  /
    
            ID FK_USER S TIMESTAMP
    ---------- ------- - -------------------
             1 Me      D 19-05-2011 11:20:48
             5 You     B 08-05-2011 11:20:48
    
    2 rows selected.
    
    SQL> select * from table(dbms_xplan.display_cursor(null,null,'allstats last'))
      2  /
    
    PLAN_TABLE_OUTPUT
    --------------------------------------------------------------------------------------------------------------------------
    SQL_ID  306v8p42zdz34, child number 0
    -------------------------------------
    select *   from thistory  where timestamp =        ( select max(timestamp)            from thistory latest
           where fk_user = thistory.fk_user        )
    
    Plan hash value: 2894184026
    
    ----------------------------------------------------------------------------------------------------------------------
    | Id  | Operation            | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
    ----------------------------------------------------------------------------------------------------------------------
    |*  1 |  HASH JOIN           |          |      1 |      2 |      2 |00:00:00.01 |       7 |  1155K|  1155K|  478K (0)|
    |   2 |   VIEW               | VW_SQ_1  |      1 |      2 |      2 |00:00:00.01 |       3 |       |       |          |
    |   3 |    HASH GROUP BY     |          |      1 |      2 |      2 |00:00:00.01 |       3 |       |       |          |
    |   4 |     TABLE ACCESS FULL| THISTORY |      1 |      6 |      6 |00:00:00.01 |       3 |       |       |          |
    |   5 |   TABLE ACCESS FULL  | THISTORY |      1 |      6 |      6 |00:00:00.01 |       4 |       |       |          |
    ----------------------------------------------------------------------------------------------------------------------
    
    Predicate Information (identified by operation id):
    ---------------------------------------------------
    
       1 - access("TIMESTAMP"="VW_COL_1" AND "FK_USER"="THISTORY"."FK_USER")
    
    
    22 rows selected.
    

    现在解析函数变体:

    SQL> select *
      2    from ( select h.*
      3                , row_number() over (partition by h.fk_user order by timestamp desc) as rn
      4             from thistory h
      5         )
      6   where rn = 1
      7  /
    
            ID FK_USER S TIMESTAMP                   RN
    ---------- ------- - ------------------- ----------
             1 Me      D 19-05-2011 11:20:48          1
             5 You     B 08-05-2011 11:20:48          1
    
    2 rows selected.
    
    SQL> select * from table(dbms_xplan.display_cursor(null,null,'allstats last'))
      2  /
    
    PLAN_TABLE_OUTPUT
    --------------------------------------------------------------------------------------------------------------------------
    SQL_ID  b7zscht24wa2s, child number 0
    -------------------------------------
    select *   from ( select h.*               , row_number() over (partition by h.fk_user order by timestamp desc)
    as rn            from thistory h        )  where rn = 1
    
    Plan hash value: 2357375523
    
    --------------------------------------------------------------------------------------------------------------------------
    | Id  | Operation                | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
    --------------------------------------------------------------------------------------------------------------------------
    |*  1 |  VIEW                    |          |      1 |      6 |      2 |00:00:00.01 |       3 |       |       |          |
    |*  2 |   WINDOW SORT PUSHED RANK|          |      1 |      6 |      6 |00:00:00.01 |       3 |  9216 |  9216 | 8192  (0)|
    |   3 |    TABLE ACCESS FULL     | THISTORY |      1 |      6 |      6 |00:00:00.01 |       3 |       |       |          |
    --------------------------------------------------------------------------------------------------------------------------
    
    Predicate Information (identified by operation id):
    ---------------------------------------------------
    
       1 - filter("RN"=1)
       2 - filter(ROW_NUMBER() OVER ( PARTITION BY "H"."FK_USER" ORDER BY INTERNAL_FUNCTION("TIMESTAMP") DESC )<=1)
    
    
    21 rows selected.
    

    最后是使用聚合函数的最有效变体:

    SQL> select max(id) keep (dense_rank last order by timestamp) id
      2       , fk_user
      3       , max(state) keep (dense_rank last order by timestamp) state
      4       , max(timestamp)
      5    from thistory
      6   group by fk_user
      7  /
    
            ID FK_USER S MAX(TIMESTAMP)
    ---------- ------- - -------------------
             1 Me      D 19-05-2011 11:20:48
             5 You     B 08-05-2011 11:20:48
    
    2 rows selected.
    
    SQL> select * from table(dbms_xplan.display_cursor(null,null,'allstats last'))
      2  /
    
    PLAN_TABLE_OUTPUT
    --------------------------------------------------------------------------------------------------------------------------
    SQL_ID  9gcbnuf776q27, child number 0
    -------------------------------------
    select max(id) keep (dense_rank last order by timestamp) id      , fk_user      , max(state) keep
    (dense_rank last order by timestamp) state      , max(timestamp)   from thistory  group by fk_user
    
    Plan hash value: 76026975
    
    --------------------------------------------------------------------------------------------------------------------
    | Id  | Operation          | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
    --------------------------------------------------------------------------------------------------------------------
    |   1 |  SORT GROUP BY     |          |      1 |      2 |      2 |00:00:00.01 |       3 |  9216 |  9216 | 8192  (0)|
    |   2 |   TABLE ACCESS FULL| THISTORY |      1 |      6 |      6 |00:00:00.01 |       3 |       |       |          |
    --------------------------------------------------------------------------------------------------------------------
    
    
    14 rows selected.
    

    如果您仔细查看计划,您会发现最后一个是最有效的。 而且它不需要内联视图。

    希望这会有所帮助。

    问候,
    抢。

    【讨论】:

      猜你喜欢
      • 2023-03-25
      • 1970-01-01
      • 2023-04-11
      • 2022-09-23
      • 1970-01-01
      • 1970-01-01
      • 2014-12-14
      • 1970-01-01
      • 2021-09-28
      相关资源
      最近更新 更多