topK, groupArray и uniqUpTo: паттерны product analytics
Три агрегатные функции для типичных задач product analytics: topK — какие элементы встречаются чаще всего, groupArray — собрать события в массив, uniqUpTo — проверить, превышает ли число уникальных значений порог. Каждая оптимизирована для своего сценария.
Три функции: обзор
topK: приближённые топ-N значения
-- Топ-10 наиболее посещаемых страниц
SELECT topK(10)(page_url) AS top_pages
FROM page_views
WHERE event_date = today();
-- Возвращает Array(String): ['/', '/products', '/checkout', ...]
-- Более полный пример: топ-10 страниц по дням
SELECT
event_date,
topK(10)(page_url) AS top_pages
FROM page_views
GROUP BY event_date
ORDER BY event_date DESC
LIMIT 7;
topK использует алгоритм Space-Saving: память O(N) независимо от кардинальности данных. Это принципиально отличает его от GROUP BY column ORDER BY count() LIMIT N который требует памяти пропорционально числу уникальных значений.
-- Взвешенный вариант: топ-10 страниц по суммарному времени на странице
SELECT topKWeighted(10)(page_url, time_on_page) AS top_by_engagement
FROM page_views;
topK vs GROUP BY + ORDER BY + LIMIT
| Подход | Точность | Память | Когда использовать |
|---|---|---|---|
topK(N) | Приближённая | O(N) | Аналитические дашборды, реальное время |
GROUP BY ... LIMIT N | Точная | O(кардинальности) | Точные отчёты, небольшие таблицы |
groupArray: сбор значений в массив
-- Последние 5 событий каждого пользователя
SELECT
user_id,
groupArray(5)(event_type) AS last_5_events
FROM events
GROUP BY user_id;
-- Возвращает первые 5 событий по порядку обработки
groupArray(N) собирает первые N значений по порядку обработки, который не гарантирован в ClickHouse без явного ORDER BY. Для “последних N событий по времени” используйте подзапрос с ORDER BY ts DESC перед groupArray или функцию groupArrayLast(N).
-- groupArrayLast(N): последние N значений по порядку
SELECT
user_id,
groupArrayLast(5)(event_type) AS last_5_events
FROM events
GROUP BY user_id;
-- Сэмплирование: случайные 100 event_id для проверки
SELECT groupArray(100)(event_id) AS sample
FROM events
WHERE event_date = today()
LIMIT 1;
groupArray без ограничения N собирает все значения в массив — будьте осторожны на больших данных: массив может вырасти до размеров таблицы.
-- Без ограничения -- все значения в один массив
SELECT groupArray(user_id) AS all_users FROM small_table;
-- Использовать только для небольших групп
uniqUpTo: проверка порогов уникальности
-- Проверка: сколько уникальных посетителей у каждого SKU?
-- Если <= 100, возвращает точное число; если > 100, возвращает 101
SELECT
sku_id,
uniqUpTo(100)(user_id) AS visitor_count
FROM product_views
GROUP BY sku_id;
| sku_id | visitor_count |
|---|---|
| A001 | 47 |
| A002 | 101 |
| A003 | 23 |
SKU A002 имеет более 100 уникальных посетителей (точное число неизвестно, возвращается N+1=101).
-- Использование в условии: найти SKU с "достаточной" аудиторией
SELECT sku_id
FROM product_views
GROUP BY sku_id
HAVING uniqUpTo(100)(user_id) >= 50;
-- Все SKU, у которых точно есть >= 50 уникальных посетителей
uniqUpTo(N) значительно дешевле uniq() или uniqExact() когда нужна только проверка порога. Память O(N) vs O(кардинальности).
Ссылка: Module 08 урок 09 содержит полное семейство uniq-функций (uniq, uniqCombined, uniqExact, uniqTheta). uniqUpTo — специализированный вариант для проверки порогов.
Сводная таблица
| Функция | Точность | Память | Возвращаемый тип | Типичное применение |
|---|---|---|---|---|
topK(N) | Приближённая | O(N) | Array(T) | Топ страниц, топ ошибок |
topKWeighted(N) | Приближённая | O(N) | Array(T) | Топ с весовым коэффициентом |
groupArray(N) | Точная | O(N * elem_size) | Array(T) | Последние N событий, сэмплирование |
groupArrayLast(N) | Точная | O(N * elem_size) | Array(T) | Последние N по порядку |
uniqUpTo(N) | Точная до N, N+1 выше | O(N) | UInt64 | Проверки пороговых значений |
Ключевые выводы
topK(N)(column)— приближённый топ-N через алгоритм Space-Saving. ПамятьO(N), не зависит от кардинальности. Для точного результата —GROUP BY ... ORDER BY count() LIMIT N.groupArray(N)(column)— первые N значений в массив.groupArrayLast(N)— последние N. Без N — все значения (опасно на больших данных).uniqUpTo(N)(column)— точное число уникальных если <= N, иначе N+1. Дешевлеuniq()когда нужна только проверка порога.- Паттерн uniqUpTo в HAVING:
HAVING uniqUpTo(100)(col) < 101— это фильтр “меньше 100 уникальных значений”. - Все три функции оптимизированы по памяти:
O(N)vsO(кардинальности)для наивных аналогов.