Подсчет разных пользователей за n дней - PullRequest
0 голосов
/ 28 ноября 2018

Моя таблица состоит из двух полей: CalDay поле отметки времени с временем, установленным на 00:00:00 и UserID.Вместе они образуют составной ключ, но важно иметь в виду, что у нас есть много строк для каждого данного календарного дня и не существует фиксированного количества строк для данного дня.

На основании этого набора данных мне понадобитсячтобы подсчитать, сколько разных пользователей существует за определенный промежуток времени, скажем, 30d.

Использование postgres 9.3 Я не могу использовать COUNT(Distinct UserID) OVER ... и не могу обойти проблему, используя DENSE_RANK() OVER (... RANGE BETWEEN), потому что RANGE принимает толькоUNBOUNDED.

Итак, я пошел по старинке и попробовал скалярный подзапрос:

SELECT
  xx.*
 ,(
       SELECT COUNT(DISTINCT UserID) 
       FROM data_table AS yy
       WHERE yy.CalDay BETWEEN xx.CalDay - interval '30 days' AND xx.u_ts
  ) as rolling_count
FROM data_table AS xx
ORDER BY yy.CalDay

Теоретически это должно сработать, верно?Я еще не уверен, потому что я начал запрос около 20 минут назад, и он все еще выполняется.В этом и заключается проблема: набор данных все еще относительно мал (25000 строк), но со временем будет расти.Мне нужно что-то, что масштабируется и работает лучше.

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

1 Ответ

0 голосов
/ 28 ноября 2018

Это должно работать.Не могу прокомментировать скорость, но она должна быть намного меньше текущей.Надеюсь, у вас есть индексы в обоих этих полях.

SELECT t1.calday, COUNT(DISTINCT t1.userid) AS daily, COUNT(DISTINCT t2.userid) AS last_30_days
FROM data_table t1
JOIN data_table t2
    ON t2.calday BETWEEN t1.calday - '30 days'::INTERVAL AND t1.calday
GROUP BY t1.calday

ОБНОВЛЕНИЕ

Протестировано с большим количеством данных.Выше работает, но медленно.Намного быстрее сделать это следующим образом:

SELECT t1.*, COUNT(DISTINCT t2.userid) AS last_30_days
FROM (
    SELECT calday, COUNT(DISTINCT userid) AS daily
    FROM data_table
    GROUP BY calday
) t1
JOIN data_table t2
    ON t2.calday BETWEEN t1.calday - '30 days'::INTERVAL AND t1.calday
GROUP BY 1, 2

Таким образом, вместо создания массивной таблицы для всех комбинаций JOIN, а затем для группировки / агрегирования, он сначала получает «ежедневные» данные, а затем присоединяется к 30-дневнымна что.Сохраняет объединение намного меньше и быстро возвращается (чуть менее 1 секунды для 45000 строк в исходной таблице в моей системе).

...