PostgreSQL: подсчет количества строк для запроса «по минутам» - PullRequest
34 голосов
/ 19 ноября 2011

Мне нужно каждую минуту запрашивать общее количество строк до этой минуты.

Лучшее, чего я мог достичь до сих пор, не сработало.Он возвращает количество в минуту, а не общее количество до каждой минуты:

SELECT COUNT(id) AS count
     , EXTRACT(hour from "when") AS hour
     , EXTRACT(minute from "when") AS minute
  FROM mytable
 GROUP BY hour, minute

1 Ответ

82 голосов
/ 19 ноября 2011

Возвращать только минуты с активностью

Кратчайший

SELECT DISTINCT
       date_trunc('minute', "when") AS minute
     , count(*) OVER (ORDER BY date_trunc('minute', "when")) AS running_ct
FROM   mytable
ORDER  BY 1;
  • Использовать date_trunc(), он возвращает именно то, что вам нужно.

  • Не включайте id в запрос, поскольку вы хотите GROUP BY минутные срезы.

  • count() обычно используется какобычный агрегатная функция .Добавление предложения OVER делает его оконной функцией .Пропустите PARTITION BY в определении окна - вы хотите, чтобы работало по всем строкам.По умолчанию это считается от первой строки до последней точки текущей строки, как определено ORDER BY. Я цитирую руководство :

    Параметр кадрирования по умолчанию - RANGE UNBOUNDED PRECEDING, что совпадает с RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROWORDER BY, это устанавливает фрейм, чтобы все строки от раздела запускались до последнего ORDER BY текущего пира текущей строки.

    И это точно чтовам нужно.

  • Используйте count(*) вместо count(id).Это лучше соответствует вашему вопросу («количество строк»).Обычно это немного быстрее , чем count(id).И хотя мы можем предположить, что id равно NOT NULL, оно не было указано в вопросе, поэтому count(id) равно неправильно , строго говоря, потому что значения NULL не учитываются при count(id).

  • Вы не можете GROUP BY минутные кусочки на одном уровне запроса.Агрегатные функции применяются до оконных функций, оконная функция count(*) будет таким образом видеть только 1 строку в минуту.
    Однако вы можете SELECT DISTINCT, потому что DISTINCT применяется после оконных функций.

  • ORDER BY 1 - это просто сокращение для ORDER BY date_trunc('minute', "when").
    1 - это позиционная ссылка на 1-е выражение в SELECT список.

  • Используйте to_char(), если вам нужно отформатировать результат.Как:

SELECT DISTINCT
       to_char(date_trunc('minute', "when"), 'DD.MM.YYYY HH24:MI') AS minute
     , count(*) OVER (ORDER BY date_trunc('minute', "when")) AS running_ct
FROM   mytable
ORDER  BY date_trunc('minute', "when");

Самый быстрый

SELECT minute, sum(minute_ct) OVER (ORDER BY minute) AS running_ct
FROM  (
   SELECT date_trunc('minute', "when") AS minute
        , count(*) AS minute_ct
   FROM   tbl
   GROUP  BY 1
   ) sub
ORDER  BY 1;

Как и выше, но:

  • Я использую подзапрос для агрегирования и подсчета строк в минуту.Таким образом, мы получаем 1 строку в минуту без DISTINCT во внешнем SELECT.

  • Теперь используем sum() в качестве агрегатной функции окна, чтобы сложить значения из подзапроса.

Я обнаружил, что это происходит значительно быстрее с большим количеством строк в минуту.

Включает минуты без активности

Кратчайший

@GabiMe спросил в комментарии как получить строку eone для каждые minute во временном интервале, включая те, где не произошло ни одного события (нет строки в базовой таблице):

SELECT DISTINCT
       minute, count(c.minute) OVER (ORDER BY minute) AS running_ct
FROM  (
   SELECT generate_series(date_trunc('minute', min("when"))
                        ,                      max("when")
                        , interval '1 min')
   FROM   tbl
   ) m(minute)
LEFT   JOIN (SELECT date_trunc('minute', "when") FROM tbl) c(minute) USING (minute)
ORDER  BY 1;
  • Создать строку для каждой минуты во временном интервале между первым и последним событием с помощью generate_series() - здесь непосредственно на основе агрегированных значений из подзапроса.

  • LEFT JOIN для всех временных меток, усеченных до минуты и отсчета.NULL значения (там, где нет строк) не добавляются к счетчику.

Самый быстрый

С CTE:

WITH cte AS (
   SELECT date_trunc('minute', "when") AS minute, count(*) AS minute_ct
   FROM   tbl
   GROUP  BY 1
   ) 
SELECT m.minute
     , COALESCE(sum(cte.minute_ct) OVER (ORDER BY m.minute), 0) AS running_ct
FROM  (
   SELECT generate_series(min(minute), max(minute), interval '1 min')
   FROM   cte
   ) m(minute)
LEFT   JOIN cte USING (minute)
ORDER  BY 1;
  • Опять же, агрегировать и подсчитывать количество строк в минуту на первом шаге, он исключает необходимость в более поздних DISTINCT.

  • Отличается от count(), sum() можетвозврат NULL.По умолчанию 0 с COALESCE.

Со многими строками и индексом для "when" эта версия с подзапросом была самой быстрой среди пары вариантовЯ тестировал с Postgres 9.1 - 9.4:

SELECT m.minute
     , COALESCE(sum(c.minute_ct) OVER (ORDER BY m.minute), 0) AS running_ct
FROM  (
   SELECT generate_series(date_trunc('minute', min("when"))
                        ,                      max("when")
                        , interval '1 min')
   FROM   tbl
   ) m(minute)
LEFT   JOIN (
   SELECT date_trunc('minute', "when") AS minute
        , count(*) AS minute_ct
   FROM   tbl
   GROUP  BY 1
   ) c USING (minute)
ORDER  BY 1;
...