Как получить самую последнюю запись с использованием PARTITION BY быстрым способом в SQL? - PullRequest
0 голосов
/ 14 сентября 2018

В нашей нормализованной базе данных Oracle 12.2, используемой для получения информации для сводных панелей и отчетов, мы обнаружили, что пользователям часто приходится сталкиваться с необходимостью узнать данные о самой последней записи для некоторого раздела.В некоторых случаях мы можем отфильтровать данные в меньшем подмножестве, тогда как в других желателен весь набор данных.В большинстве случаев пользователи хотят знать последние результаты для нескольких разделов одновременно.Типичный шаблон для этого в oracle выглядит следующим образом:

select * from (
    select my_table.*,
           row_number() over (partition by fk1, fk2, ... order by my_date desc) rn
    from my_table
    [where fk1 = 1234]
) where rn = 1

Первоначально мы хотели абстрагировать это в представление для удобства, чтобы люди могли просто написать запрос к представлению.Мы попробовали что-то вроде этого:

create view my_table_latest as (
    select * from (
        select my_table.*,
               row_number() over (partition by fk1, fk2, ... order by my_date desc) rn
        from my_table
    ) where rn = 1
)

select * from my_table_latest where fk1 = 1234

К сожалению, это было две проблемы.Во-первых, кажется, что аналитическая функция внутри представления всегда вычисляет всю таблицу перед применением каких-либо фильтров.Следовательно, полная таблица сканировалась независимо от используемых фильтров и индексов.Во-вторых, при использовании таблиц с миллионами записей запросы выполнялись дольше, чем хотелось бы.

Учитывая, что мы хотим, чтобы наши данные оставались относительно свежими (в течение 10 минут), что было бы лучшим способомполучить последние записи для некоторого бизнес-ключа в качественной форме?Метод получения данных должен быть скрыт в представлении, чтобы его можно было легко использовать с помощью приложения передней панели.

Вот две идеи, которые у нас были:

  • МатериализованоПредставление - используйте материализованное представление, чтобы пересчитывать результаты каждые 10 минут.Учитывая, что запрос занимает несколько минут, мы обеспокоены тем, что это может не сработать.Кроме того, основываясь на том, что мы обнаружили, имея аналитический запрос в представлении, мы подозреваем, что использование более эффективной стратегии обновления не сработает из-за использования аналитической функции.
  • Отслеживание по мере продвижения - Учитывая, что у нас есть доступ к коду, который записывает данные, а данные всегда обрабатываются самыми старыми -> самыми новыми, мы можем легко отслеживать последнюю запись и сохранять ее в другой таблице.Затем может быть создано представление, которое использует эту информацию и присоединяется к исходной таблице, чтобы получить остальные детали записи.Таблица «Пользовательский индекс» будет выглядеть примерно так (fk1, fk2, my_table.pk, date).К сожалению, это потребует изменения кода.

Ответы [ 3 ]

0 голосов
/ 14 сентября 2018

Немного мозгового штурма:

  1. Создайте SQL TYPE, представляющий тип вашей строки;для получения более подробной информации прочитайте документы Oracle
create type my_table_t as( /* same fields as my_table */ );
Создайте функцию PIPELINED, которая получает все необходимые параметры и возвращает нужный вам тип строки.Прочтите документацию Oracle о конвейерных табличных функциях для получения более подробной информации.В его наиболее общей форме вы получите varchar2, содержащий предоставленный пользователем фильтр SQL, но, поскольку он может быть уязвим для атак с использованием инъекций, я предлагаю использовать другие альтернативы, такие как принятие (fk1, ..., fkn) в качестве параметров,Давайте назовем эту функцию query_my_table.Внутри этого запроса вы динамически генерируете именно тот SQL, который вам нужен, открывая REF CURSOR и PIPE в каждой строке.Поскольку вы генерируете конкретный SQL для каждого случая, вы можете выполнить именно тот запрос, который вам нужен, и вам не нужно полагаться на поведение при просмотре.
create or replace function query_my_table(fk1 number, ..., fkn number) return my_table_t pipelined is
    query varchar2;
begin
   query := /* Create a string with the exact SQL you need */
   /* open ref cursor for query using fk1, ..., fkn */
   loop
       /* fetch & exit when not_found */
       /* load data into instance of my_table_t */
       pipe row(my_table_t_instance);
    end loop;
    /* close ref cursor */
    return;
end issue

Затем вы можете SELECT, выдав:

select * from table(query_my_table(fk1, ..., fkn));

Это просто еще одно применение тех же функций, которые используются dbms_xplan.display.Основная проблема, с которой я могу столкнуться при таком подходе, заключается в том, что он не очень хорошо компонуется: поскольку у Oracle нет статистики о бите table(...), если вы начнете объединять его с другими таблицами, оптимизатор не сможет оптимизироватьстолько.Но если это своего рода «последний запрос», он должен работать нормально.

0 голосов
/ 14 сентября 2018

Вы можете использовать следующий запрос и реализовать его в виде представлений:

select *
  from my_table
 where ROWID IN (SELECT first_value(ROWID) over (PARTITION BY fk1, fk2, ...
                                                     ORDER BY my_date DESC)
                   FROM my_table)

Индекс для fk1, fk2, ..., my_date может помочь ускорить запрос.

0 голосов
/ 14 сентября 2018

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

select t.*
from my_table t
where t.my_date = (select max(t2.my_date)
                   from my_table t2
                   where t2.fk1 = t.fk1 and t2.fk2 = t.fk2 and . . .
                  );

Oracle может оказаться проще оптимизировать это при использовании представления с фильтрацией во внешнем запросе.Для производительности вам нужен индекс на (fk1, fk2, . . ., my_date).

. Предполагается, что дата не повторяется для данной комбинации клавиш.

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