PG: Про один запрос и оконные функции

PG: Про один запрос и оконные функции

SanSYS

По заметке https://habrahabr.ru/post/268983/

Вполне себе мешает, вот запрос:

EXPLAIN ANALYSE
SELECT ID, lasthistoryupdate, expectedenddate
FROM tasks 
where expectedenddate is not null
ORDER BY lasthistoryupdate desc
limit 10 -- Обрати внимание


Его план:

Для ускорения - индекс

drop index if EXISTS idx_tasks_lasthistoryupdate; 

create index idx_tasks_lasthistoryupdate 
  on tasks (lasthistoryupdate desc) 
where expectedenddate is not null; 

ANALYSE tasks


Новый план запроса - отличный

Теперь пробую добавить оконную функцию row_number по другому полю:

EXPLAIN ANALYSE
SELECT ID, lasthistoryupdate, expectedenddate
, row_number() OVER (ORDER BY expectedenddate DESC) AS rating
where expectedenddate is not null
ORDER BY lasthistoryupdate desc
limit 10


План выполнения:

<sarcasm> один order ничуть не мешает другому </sarcasm>


Ок, допускаю, что нужен индекс на внешний ордер

drop index if EXISTS idx_tasks_expectedenddate; 

create index idx_tasks_expectedenddate 
  on tasks (expectedenddate desc) 
  where expectedenddate is not null; 

ANALYSE tasks


Хрен там

Давай только на внутренний оставлю, пути плана же неисповедимы

А нет, как и ожидалось

На оба поля по отдельности не поможет, и даже составной:

drop index if EXISTS idx_tasks_dates; 

create index idx_tasks_dates 
  on tasks (expectedenddate desc, lasthistoryupdate desc) 
    where expectedenddate is not null; 

ANALYSE tasks


Ну и, конечно, фактический результат:

Как оптимизировать этот вариант в одном запросе - без понятия

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

Вот пример "оптимизации":

EXPLAIN ANALYSE
with t as (
  select ID, lasthistoryupdate, expectedenddate
  FROM tasks 
  where expectedenddate is not null
  ORDER BY lasthistoryupdate DESC
  limit 10
)
select t.*, r.rating from t
join (
    select id, row_number() OVER (ORDER BY expectedenddate DESC) AS rating 
    from tasks 
    where expectedenddate is not null
  ) r
    on t.id = r.id


...

Как видишь - так себе, просто меньше данных перебирается, за счёт индекса, это полезно, если подобного запроса не избежать

Но даже это ничего не даёт, если нужно сделать что-то с данными, например - заменив row_number() на sum(viewed), получается: