Агрегирование перформантов, где пункт - PullRequest
1 голос
/ 09 марта 2011

Я пытаюсь найти количество пользователей, которые были созданы за три месяца с момента создания последнего пользователя.Все сгруппированы по состоянию.

Вот запрос, который работает:

select count(u.id) as numberOfUsers,
s.state
from users u
join states s on u.state_id = s.id
where u.creationdate > (
select max(u2.creationdate)
from users u2
where u2.state_id = s.id
) - interval '3 months'
group by s.state

Однако, это займет 100 секунд.Может кто-нибудь получить мне более производительный?

Я бы хотел, чтобы это сработало:

select count(u.id) as numberOfUsers,
s.state, max(u2.creationdate) as lastCreated
from users u
join states s on u.state_id = s.id
where u.creationdate > lastCreated - interval '3 months'
group by s.state

Ответы [ 4 ]

3 голосов
/ 09 марта 2011

Это может работать лучше из-за выполнения только одного сканирования:

select count(*) as numberofusers,
       state
from ( select id, state_id, creationdate,
              max(creationdate) over (partition by state_id) - '3 months'::interval as cutoff
       from users
     ) x
     join states on states.id = x.state_id
where creationdate > cutoff
group by state

Тем не менее, оно будет прожевать много рабочей памяти при первоначальном агрегировании окон.

Хм, может быть что-тобольше похоже на:

with cutoffs as (
  select id, state,
         (select max(creationdate)
          from users
          where users.state_id = states.id) - '3 months'::interval as cutoff
  from states)
select count(*) as numberofusers, state
from users
     join cutoffs on users.state_id = cutoffs.id
where users.creationdate > cutoff
group by state

Это попытка заставить PostgreSQL правильно сканировать разделы, но это не совсем идеально.Это все еще делает полное сканирование таблицы, но по крайней мере только одно.Функция, возвращающая множество, которая перебирает выходные данные CTE и отправляет результат внешнего запроса внутри цикла, вероятно, будет работать лучше, поскольку она сможет использовать индекс creationdate для каждого состояния.

2 голосов
/ 09 марта 2011

Просто из интереса, как работает следующий запрос?Меня особенно интересует, как Postgresql обрабатывает самый внутренний запрос (таблица состояний + скалярный подзапрос).

Для этого должен составной индекс пользователей (state_id, creation_date).

select s2.id
      ,s2.state
      ,(select count(*) 
          from users u 
         where u.state_id     = s2.id
           and u.creationdate > s2.max_date) as numberOfUsers
  from (select s.id
              ,s.state
              ,(select max(u.creationdate) - interval '3 months'
                  from users u
                 where u.state_id = s.id) as max_date
         from states s
       ) s2;

edit это план, созданный для этого запроса с 100 000 пользовательских строк в 3 состояниях:

 Seq Scan on states s (actual time=4.033..13.949 rows=3 loops=1)
   Buffers: shared hit=1743
   SubPlan 3
     ->  Aggregate (actual time=4.636..4.636 rows=1 loops=3)
           Buffers: shared hit=1742
           InitPlan 2 (returns $2)
             ->  Result (actual time=0.028..0.028 rows=1 loops=3)
                   Buffers: shared hit=12
                   InitPlan 1 (returns $1)
                     ->  Limit (actual time=0.022..0.022 rows=1 loops=3)
                           Buffers: shared hit=12
                           ->  Index Scan Backward using users_state_id_creationdate_idx on users u (actual time=0.019..0.019 rows=1 loops=3)
                                 Index Cond: ((state_id = $0) AND (creationdate IS NOT NULL))
                                 Buffers: shared hit=12
           ->  Bitmap Heap Scan on users u (actual time=1.095..3.693 rows=8425 loops=3)
                 Recheck Cond: ((state_id = $0) AND (creationdate > $2))
                 Buffers: shared hit=1730
                 ->  Bitmap Index Scan on users_state_id_creationdate_idx (actual time=1.017..1.017 rows=8425 loops=3)
                       Index Cond: ((state_id = $0) AND (creationdate > $2))
                       Buffers: shared hit=107
 Total runtime: 14.017 ms
1 голос
/ 09 марта 2011

Это запрос, который я использовал для сокращения времени до 82 мс:

with cutoffs as (
  select max(u.creationdate) as cuttoff, s.id, s.state,
          from users u
  join states s on u.state_id = s.id
group by s.state, s.id)
select count(*) as numberofusers, state
from users
     join cutoffs on users.state_id = cutoffs.id
where users.creationdate > cutoff
group by state

Спасибо araqnid.

0 голосов
/ 09 марта 2011

Определили ли вы, какая часть запроса медленная?Можете ли вы добавить индексацию?Я не гуру Postgres, но подозреваю, что если пользователи не проиндексированы на users.creationdate, функция MAX () должна будет выполнить полное сканирование таблицы.Хммм, возможно, в любом случае это придется сделать ...

Тем не менее, здесь ничего не идет!

SELECT u.numUsers, s.state FROM 
(SELECT count(id) as numUsers, state_id 
 FROM users
 WHERE creationdate > (MAX(creationdate) - interval '3 Months'
 GROUP BY state_id) u 
left join states s on u.state_id = s.state_id 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...