Как правильно получить последнюю запись для каждого внешнего ключа таблицы истории? - PullRequest
3 голосов
/ 22 марта 2011

Мне всегда было интересно, как правильно найти последнюю запись в таблице истории определенного внешнего ключа?

Пример:

Мы предполагаем, что у нас есть такая таблица:

tHistory (ID | FK_User | State | Timestamp)

Как правильно было бы в Oracle читать последние записи для каждого идентификатора FK_User?Я всегда использовал для этого подвыбор, но он мне не подходит ... есть ли другие способы сделать это?

Ответы [ 3 ]

5 голосов
/ 22 марта 2011

Альтернативой может быть использование коррелированного подзапроса для определения последней даты ...

SELECT
  *
FROM
  tHistory
WHERE
  timestamp = (
    SELECT
      MAX(timestamp)
    FROM
      tHistory latest
    WHERE
      FK_User = tHistory.FK_User
  )
2 голосов
/ 22 марта 2011
SELECT  *
FROM    (
        SELECT  h.*,
                ROW_NUMBER() OVER 
                (PARTITION BY h.FK_User ORDER BY timestamp DESC) AS rn
        FROM    tHistory h
        )
WHERE   rn = 1
1 голос
/ 19 мая 2011

Хороший вопрос.

До версии 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

Вот пример с таблицей:

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

Сначала пре-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.

Если вы внимательно посмотрите на планы, вы увидите, что последний является наиболее эффективным. И это не требует встроенного просмотра.

Надеюсь, это поможет.

С уважением,
Роб.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...