Когда дело доходит до нарезки и агрегации данных (по времени или чему-то еще), схема «звезда» (звезда «Кимбалл») является довольно простым, но мощным решением. Предположим, что для каждого клика мы храним время (с точностью до секунды), информацию о пользователе, идентификатор кнопки и местоположение пользователя. Чтобы включить легкую нарезку и нарезку кубиками, я начну с предварительно загруженных таблиц поиска для свойств объектов, которые редко изменяются - так называемые таблицы измерений в мире DW.
Таблица dimDate
имеет одну строку для каждого дня с количеством атрибутов (полей), которые описывают конкретный день. Таблица может быть предварительно загружена на несколько лет вперед и должна обновляться один раз в день, если она содержит поля типа DaysAgo, WeeksAgo, MonthsAgo, YearsAgo
; в противном случае это может быть «загрузить и забыть». dimDate
позволяет легко разрезать по дате атрибуты, такие как
WHERE [YEAR] = 2009 AND DayOfWeek = 'Sunday'
За десять лет данных в таблице всего ~ 3650 строк.
Таблица dimGeography
предварительно загружена с интересующими географическими регионами - количество строк зависит от "географического разрешения", требуемого в отчетах, оно позволяет нарезать данные как
WHERE Continent = 'South America'
После загрузки он редко изменяется.
Для каждой кнопки сайта есть одна строка в таблице dimButton, поэтому запрос может иметь
WHERE PageURL = 'http://…/somepage.php'
Таблица dimUser
имеет одну строку для каждого зарегистрированного пользователя, эта таблица должна быть загружена с новой информацией о пользователе, как только пользователь зарегистрируется, или, по крайней мере, новая информация о пользователе должна быть в таблице, прежде чем любая другая пользовательская транзакция будет записано в таблицах фактов.
Для записи нажатий кнопок я добавлю таблицу factClick
.
Таблица factClick
имеет одну строку для каждого нажатия кнопки определенного пользователя в определенный момент времени. Я использовал TimeStamp
(второе разрешение), ButtonKey
и UserKey
в составном первичном ключе, чтобы отфильтровывать клики быстрее, чем раз в секунду от конкретного пользователя. Обратите внимание на поле Hour
, оно содержит часовую часть TimeStamp
, целое число в диапазоне 0-23, чтобы можно было легко разрезать в час, например,
WHERE [HOUR] BETWEEN 7 AND 9
Итак, теперь мы должны рассмотреть:
- Как загрузить таблицу? Периодически - возможно, каждый час или каждые несколько минут - из блога с использованием инструмента ETL или решения с малой задержкой с использованием какого-либо процесса потоковой передачи событий.
- Как долго хранить информацию в таблице?
Независимо от того, хранит ли таблица информацию только один день или несколько лет - ее следует разбивать; ConcernedOfTunbridgeW объяснил разбиение в своем ответе, поэтому я пропущу его здесь.
Теперь несколько примеров нарезки и нарезки кубиками для разных атрибутов (включая день и час)
Чтобы упростить запросы, я добавлю представление, чтобы выровнять модель:
/* To simplify queries flatten the model */
CREATE VIEW vClicks
AS
SELECT *
FROM factClick AS f
JOIN dimDate AS d ON d.DateKey = f.DateKey
JOIN dimButton AS b ON b.ButtonKey = f.ButtonKey
JOIN dimUser AS u ON u.UserKey = f.UserKey
JOIN dimGeography AS g ON g.GeographyKey = f.GeographyKey
Пример запроса
/*
Count number of times specific users clicked any button
today between 7 and 9 AM (7:00 - 9:59)
*/
SELECT [Email]
,COUNT(*) AS [Counter]
FROM vClicks
WHERE [DaysAgo] = 0
AND [Hour] BETWEEN 7 AND 9
AND [Email] IN ('dude45@somemail.com', 'bob46@bobmail.com')
GROUP BY [Email]
ORDER BY [Email]
Предположим, меня интересуют данные для User = ALL
. dimUser
- это большая таблица, поэтому я сделаю представление без нее, чтобы ускорить запросы.
/*
Because dimUser can be large table it is good
to have a view without it, to speed-up queries
when user info is not required
*/
CREATE VIEW vClicksNoUsr
AS
SELECT *
FROM factClick AS f
JOIN dimDate AS d ON d.DateKey = f.DateKey
JOIN dimButton AS b ON b.ButtonKey = f.ButtonKey
JOIN dimGeography AS g ON g.GeographyKey = f.GeographyKey
Пример запроса
/*
Count number of times a button was clicked on a specific page
today and yesterday, for each hour.
*/
SELECT [FullDate]
,[Hour]
,COUNT(*) AS [Counter]
FROM vClicksNoUsr
WHERE [DaysAgo] IN ( 0, 1 )
AND PageURL = 'http://...MyPage'
GROUP BY [FullDate], [Hour]
ORDER BY [FullDate] DESC, [Hour] DESC
Предположим, что для агрегатов нам не нужно хранить конкретную информацию о пользователе, а интересуются только дата, час, кнопка и география. Каждая строка в таблице factClickAgg
имеет счетчик для каждого часа, когда была нажата определенная кнопка из определенной географической области.
Таблицу factClickAgg
можно загружать ежечасно или даже в конце каждого дня - в зависимости от требований к отчетности и аналитике. Например, допустим, что таблица загружается в конце каждого дня (после полуночи), я могу использовать что-то вроде:
/* At the end of each day (after midnight) aggregate data. */
INSERT INTO factClickAgg
SELECT DateKey
,[Hour]
,ButtonKey
,GeographyKey
,COUNT(*) AS [ClickCount]
FROM vClicksNoUsr
WHERE [DaysAgo] = 1
GROUP BY DateKey
,[Hour]
,ButtonKey
,GeographyKey
Чтобы упростить запросы, я создам представление для выравнивания модели:
/* To simplify queries for aggregated data */
CREATE VIEW vClicksAggregate
AS
SELECT *
FROM factClickAgg AS f
JOIN dimDate AS d ON d.DateKey = f.DateKey
JOIN dimButton AS b ON b.ButtonKey = f.ButtonKey
JOIN dimGeography AS g ON g.GeographyKey = f.GeographyKey
Теперь я могу запрашивать агрегированные данные, например, по дням:
/*
Number of times a specific buttons was clicked
in year 2009, by day
*/
SELECT FullDate
,SUM(ClickCount) AS [Counter]
FROM vClicksAggregate
WHERE ButtonName = 'MyBtn_1'
AND [Year] = 2009
GROUP BY FullDate
ORDER BY FullDate
Или с несколькими дополнительными опциями
/*
Number of times specific buttons were clicked
in year 2008, on Saturdays, between 9:00 and 11:59 AM
by users from Africa
*/
SELECT SUM(ClickCount) AS [Counter]
FROM vClicksAggregate
WHERE [Year] = 2008
AND [DayOfWeek] = 'Saturday'
AND [Hour] BETWEEN 9 AND 11
AND Continent = 'Africa'
AND ButtonName IN ( 'MyBtn_1', 'MyBtn_2', 'MyBtn_3' )