Более эффективный способ получить последнее значение на основе составного индекса? - PullRequest
4 голосов
/ 07 марта 2019

У меня есть таблица SAMPLE_TABLE со следующими столбцами, в которой CAR_TYPE, COLOR и CAR_BRAND составляют составной индекс.

 VALUE_ID      VALUE      CAR_TYPE     COLOR     SUBMIT_DT      CAR_BRAND
   1            10        Sedan        Blue      3/7/2019       Ford
   2            70        Sedan        Blue      3/6/2019       Ford
   3            20        Sedan        Blue      3/5/2019       Ford
   4            77         SUV         Red       3/7/2019       Volvo
   5           100         SUV         Red       3/1/2019       Volvo

Можно ли написать более эффективный способ запроса значения, соотнесенного с последним SUBMIT_DT?В будущем TABLE будет иметь миллионы строк данных, поэтому мне нужно будет найти запрос с наименьшим временем выполнения / затратами, которые можно запросить.

Например, ниже приведено то, что я хотел бы получить в своем наборе результатов при запросе к седану "Синий Форд":

 VALUE
 10

Ниже приведено то, что я имею до сих пор:

 SELECT value 
   FROM (
          SELECT *
            FROM TABLE
           WHERE CAR_TYPE = rCar_Type
             AND COLOR = rColor
             AND CAR_BRAND = rCar_Brand
            ORDER by submit_dt desc
        )
   WHERE rownum = 1;

Разве это неэффективно?

Заранее спасибо

Ответы [ 6 ]

4 голосов
/ 08 марта 2019

Ух ты ... уже есть много ответов, но я думаю, что некоторые из них пропустили то, что я думаю, в чем смысл твоего вопроса.

В вашей таблице будут миллионы строк, и ваш составной индекс (CAR_TYPE, COLOR, CAR_BRAND) будет не очень избирательным. Вы ищете способ получить одну строку с последним SUBMIT_DT для данной записи в вашем составном индексе, не просматривая ВСЕ совпадения из этого индекса.

Ответ: добавьте SUBMIT_DT DESC к вашему составному индексу

Давайте настроим тест:

create table matt_objects as select * from dba_objects;

-- This is our analog of your composite index
create index matt_objects_n1 on matt_objects ( object_type, owner );

exec dbms_stats.gather_table_stats(user,'MATT_OBJECTS');

Теперь давайте проследим это утверждение:

select object_name
from   matt_objects
where  object_type = 'TABLE'
and    owner = 'INV'
order by last_ddl_time desc
fetch first 1 row only;
---------------------------------------------------------------------------------------------------------
| Id  | Operation                             | Name            | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                      |                 |     1 |    88 |    17   (6)| 00:00:01 |
|*  1 |  VIEW                                 |                 |     1 |    88 |    17   (6)| 00:00:01 |
|*  2 |   WINDOW SORT PUSHED RANK             |                 |   162 |  7290 |    17   (6)| 00:00:01 |
|   3 |    TABLE ACCESS BY INDEX ROWID BATCHED| MATT_OBJECTS    |   162 |  7290 |    16   (0)| 00:00:01 |
|*  4 |     INDEX RANGE SCAN                  | MATT_OBJECTS_N1 |   162 |       |     3   (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("from$_subquery$_002"."rowlimit_$$_rownumber"<=1)
   2 - filter(ROW_NUMBER() OVER ( ORDER BY INTERNAL_FUNCTION("LAST_DDL_TIME") DESC )<=1)
   4 - access("OBJECT_TYPE"='TABLE' AND "OWNER"='INV')

Результат (из автотрассировки): 72 согласованных буфера чтения получает

Теперь давайте заменим ваш составной индекс на тот, который поможет нам больше:

drop index matt_objects_n1;

create index matt_objects_n1 on matt_objects ( object_type, owner, last_ddl_time desc );

exec dbms_stats.gather_table_stats(user,'MATT_OBJECTS');

.. и давайте снова проследим ту же фразу:

--------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name            | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |                 |     1 |    88 |    54   (2)| 00:00:01 |
|   1 |  SORT ORDER BY                 |                 |     1 |    88 |    54   (2)| 00:00:01 |
|*  2 |   VIEW                         |                 |     1 |    88 |    53   (0)| 00:00:01 |
|*  3 |    WINDOW NOSORT STOPKEY       |                 |   162 |  7290 |    53   (0)| 00:00:01 |
|   4 |     TABLE ACCESS BY INDEX ROWID| MATT_OBJECTS    |   162 |  7290 |    53   (0)| 00:00:01 |
|*  5 |      INDEX RANGE SCAN          | MATT_OBJECTS_N1 |   162 |       |     3   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter("from$_subquery$_002"."rowlimit_$$_rownumber"<=1)
   3 - filter(ROW_NUMBER() OVER ( ORDER BY SYS_OP_DESCEND("LAST_DDL_TIME"))<=1)
   5 - access("OBJECT_TYPE"='TABLE' AND "OWNER"='INV')

Результат (из автотрассировки): 5 последовательных операций чтения

Этот индекс очень помог. Заметили, что план отличается? «WINDOW SORT PUSHED RANK» заменен на «WINDOW NOSORT STOPKEY». С учетом того, что индекс уже отсортирован так, как вы хотите (в порядке убывания), Oracle знает, что он может читать строки индекса по порядку и останавливаться после первого, выполняя запрос с гораздо меньшими усилиями.

Интересно отметить, что стоимость 2-го запроса выше, чем стоимость 1-го запроса, хотя производительность 2-го запроса более чем в 10 раз выше. Это просто говорит о том, что «стоимость» является приблизительной, и иногда ее следует брать с крошкой соли.

3 голосов
/ 07 марта 2019

Хорошо, запрос, который вы написали, не может быть точно назван «неэффективным», но «бесполезным» в этом контексте, поскольку он возвратит одну, случайную строку. Вы, вероятно, пропускаете ORDER BY в подзапросе.

В любом случае: посмотрите, как это ведет себя:

select value
  from (select row_number() over (partition by car_type, color, car_brand
                                  order by submit_dt desc) rn,
               value
        from sample_table
        where car_type = rcar_type
          and color = rcolor
          and car_brand = rcar_brand
       )
where rn = 1;       

Не забудьте создать индекс для столбцов, используемых в предложении WHERE.

2 голосов
/ 07 марта 2019

Просто используйте FETCH FIRST:

SELECT *
FROM TABLE
WHERE CAR_TYPE = rCar_Type
  AND COLOR = rColor
  AND CAR_BRAND = rCar_Brand
ORDER BY submit_dt DESC
FETCH FIRST 1 ROW ONLY

Если ваша версия базы данных 12c.

2 голосов
/ 07 марта 2019

Вы ищете последнее значение для car_type, color, car_brand. Oracle предлагает KEEP LAST для этого:

SELECT MAX(value) KEEP (DENSE_RANK LAST ORDER BY submit_dt)
  FROM table
 WHERE car_type = :rcar_type
   AND color = :rcolor
   AND car_brand = :rcar_brand;
2 голосов
/ 07 марта 2019

Полагаю, вы имели в виду «index» вместо «key» в вашем вопросе.Если это так, то я бы создал индекс:

create index ix1 on sample_table (car_type, color, car_brand, submit_dt);

Тогда следующий запрос будет мгновенным, поскольку он не будет читать кучи:

select max(submit_dt)
from sample_table
where CAR_TYPE = rCar_Type
  and COLOR = rColor
  and CAR_BRAND = rCar_Brand
1 голос
/ 07 марта 2019

Вы можете использовать row_number для решения этой проблемы.

Например

SELECT x.value 
FROM (
  SELECT VALUE, 
         ROW_NUMBER() OVER (PARTITION BY CAR_TYPE, CAR_COLOR, CAR_BRAND ORDER BY SUBMIT_DATE DESC) AS RN
  FROM table
) x
WHERE x.RN = 1
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...