В этом уроке мы познакомимся с понятиями баз и витрин данных компании и их различиями. Витрины — важная тема для аналитика данных, так как он работает именно с ними.
Сегодня мы научимся создавать витрины и работать с ними. Эти навыки помогут построить аналитические отчеты и выявить причины проблем в работе сервисов компании. Например, построить отчет о выручке магазинов сети за период времени и найти самые прибыльные магазины.
Базы данных систем
Представим, что мы ведем заметки о своей жизни и работе в разных местах. Для учета доходов и расходов мы используем Google-таблицы или специальные приложения. Трекинг рабочих задач производим в Trello. А в Notion записываем мысли о событиях из жизни и ведем дневник. Каждый из этих инструментов удобен для определенных задач.
Это примеры баз данных, которые мы используем в личных целях. У компаний тоже есть разные базы для разных подсистем. Например, у бухгалтерии банка своя база данных, у отдела продаж своя, у отдела кредитного скоринга тоже своя.
Обычно базы данных подсистем компании хранятся в нормализованном виде. Покажем, как это выглядит на примере.
Допустим, мы отслеживаем ежемесячный поток клиентов в нескольких магазинах сети электроники. Клиенты пользуются именными скидочными картами, и по ним мы отслеживаем каждого по отдельности. Тогда таблица с данными клиентов и купленных ими товарами будет выглядеть так:
Purchases
date | full_name | shop_address | product_name | price | discount | discounted_price |
---|---|---|---|---|---|---|
22.12.2022 | Попов Константин Федорович | Ленинградский проспект 12, Москва | Стиральная машина Electrolux | 28499 | 0.1 | 25649 |
22.12.2022 | Гречкина Анастасия Антоновна | ул Нансена 83, Ростов-на-Дону | Беспроводные наушники JBL с микрофоном | 4999 | 0.05 | 4749 |
24.12.2022 | Алексеев Алексей Михайлович | Лиговский проспект 30, Санкт-Петербург | Игровая мышь Acer | 1099 | 0.07 | 1022 |
... | ... | ... | ... | ... | ... | ... |
В этой таблице мы видим информацию о транзакциях покупателей в магазине электроники. Здесь есть информация о покупателях, магазинах и товарах.
Теперь представим, что один из магазинов переехал на другой адрес. Чтобы внести изменения в эту таблицу, мы выделим все строки, которые содержат старый адрес, и заменим на новый.
Если в таблице тысячи или миллионы строк, тогда придется изменять большое количество. Но это замедляет работу сервиса, и даже может положить его.
Чтобы не столкнуться с этой проблемой, базы данных таких систем нормализуют — разносят разные объекты по разным таблицам. В таком случае предстоит изменять минимальное количество строк.
Так выглядят нормализованные таблицы:
Shops
id | shop_address |
---|---|
274 | Ленинградский проспект 12, Москва |
528 | ул Нансена 83, Ростов-на-Дону |
182 | Лиговский проспект 30, Санкт-Петербург |
... | ... |
Customers
id | full_name |
---|---|
24681982 | Попов Константин Федорович |
47560098 | Гречкина Анастасия Антоновна |
98738112 | Алексеев Алексей Михайлович |
... | ... |
Products
id | product_name | price |
---|---|---|
10928473 | Стиральная машина Electrolux | 28499.99 |
20483739 | Беспроводные наушники JBL с микрофоном | 4999.0 |
73648393 | Игровая мышь Acer | 1099.0 |
... | ... | ... |
Discounts
id | discount |
---|---|
776534 | 0.1 |
765432 | 0.05 |
692837 | 0.07 |
... | ... |
CustomersDiscounts
id | customer_id | discount_id |
---|---|---|
3367488 | 24681982 | 776534 |
3337482 | 47560098 | 765432 |
2876409 | 98738112 | 692837 |
... | ... | ... |
Purchases
id | date | customer_id | product_id | shop_id |
---|---|---|---|---|
1888726379 | 22.12.2022 | 24681982 | 10928473 | 274 |
1888726380 | 22.12.2022 | 47560098 | 20483739 | 528 |
1888726381 | 24.12.2022 | 98738112 | 73648393 | 182 |
... | ... | ... | ... | ... |
Датасет доступен на DB Fiddle по этой ссылке.
Здесь представлены следующие таблицы:
- Shops — информация о магазинах
- Customers — информация о клиентах
- Products — информация о товарах
- Discounts — информация о скидках
- CustomersDiscounts — таблица, которая связывает клиентов и их персональные скидки
- Purchases — таблица покупок
У каждой из этих таблиц есть поле ID — это уникальный ключ. Также одна таблица содержит в себе информацию только об одной сущности, например: Shops
— о магазинах, Customers
— о клиентах.
Каждый объект, например, магазин, клиент, представлены одной строкой без дублирования. При этом таблица Purchases
может содержать одинаковые ID клиентов, товаров или магазинов, так как клиенты покупают товары в разных магазинах в разное время или одни и те же товары.
Если заменить адрес магазина, мы затронем одну строчку в таблице с магазинами. Остальные таблицы останутся без изменений.
CustomersDiscounts
— это отдельный вид таблиц. Они содержат в себе только свой ID и ID других таблиц, что позволяет связать эти таблицы. Покупателей и их индивидуальные скидки разносят по разным таблицам.
Например, скидка у отдельного покупателя может увеличиться, когда он за все время купил в магазине много товаров. Мы хотим изменять как можно меньшее количество строк, поэтому меняем значение скидки в CustomersDiscounts
, а таблица Customers
остается без изменений.
Такие базы данных называются OLTP или Online Transaction Processing. Online — потому, что базы данных обновляются в реальном времени. Транзакции идут большим потоком, поэтому, чтобы достичь минимального времени отклика, важно вносить изменения в малое количество строк.
Витрины данных
Аналитикам данных сложно работать с большим количеством таблиц, так как каждая из них неинформативная. Аналитики интересуются не ID товаров или магазинов, им нужно знать названия товаров, адреса магазинов, цены с учетом скидки и так далее. Поэтому существуют витрины данных, где собрана вся важная информация.
Пример витрины данных — это таблица Purchases
. Посмотрим на нее еще раз:
Purchases
date | full_name | shop_address | product_name | price | discount | discounted_price |
---|---|---|---|---|---|---|
22.12.2022 | Попов Константин Федорович | Ленинградский проспект 12, Москва | Стиральная машина Electrolux | 25649.991 | 0.1 | 25649 |
22.12.2022 | Гречкина Анастасия Антоновна | ул Нансена 83, Ростов-на-Дону | Беспроводные наушники JBL с микрофоном | 4999 | 0.05 | 4749.05 |
24.12.2022 | Алексеев Алексей Михайлович | Лиговский проспект 30, Санкт-Петербург | Игровая мышь Acer | 1099 | 0.07 | 1022.07 |
... | ... | ... | ... | ... | ... | ... |
Здесь находится только важная информация: имена клиентов, наименование купленного товара, адрес покупки, и нет ID. Аналитики работают только с витринами, потому что они удобны для анализа и построения отчетов.
Напомним, что обычно в компаниях базы данных для каждого отдела разные. Аналитики обычно не анализируют все данные сразу, поэтому витрины — это срез во всех данных компании. Их еще называют Data Mart.
Представим общую схему потока данных между базами разных отделов и витринами:
Здесь видно, что каждый отдел компании содержит свою базу данных, а таблицы в базах хранятся в нормализованном виде. Витрины данных — это срезы в хранилище данных для разных аналитических задач. Одна витрина представляется не более чем одной базой.
Витрины данных могут быть базами данных или таблицами в Google Sheets, это не меняет их сути.
Теперь разберемся, как работать с витриной. Для этого создадим ее.
Создаем витрину
Откроем базу данных Purchases
. Мы увидим в ней таблицы, которые были в примерах урока:
- Customers
- CustomersDiscounts
- Discounts
- Products
- Purchases
- Shops
Посмотрим на таблицу Purchases:
purchases.purchases
id | date | customer_id | product_id | shop_id |
1888726379 | 22.12.2022 | 24681982 | 10928473 | 274 |
1888726380 | 22.12.2022 | 47560098 | 20483739 | 528 |
1888726381 | 24.12.2022 | 98738112 | 73648393 | 182 |
1888726382 | 25.12.2022 | 98738112 | 22657781 | 182 |
1888726383 | 25.12.2022 | 12000976 | 66379812 | 274 |
1888726384 | 26.12.2022 | 63428871 | 17728904 | 280 |
1888726385 | 26.12.2022 | 62765413 | 33801167 | 182 |
1888726386 | 26.12.2022 | 99871622 | 26659810 | 274 |
1888726387 | 27.12.2022 | 32009654 | 81176602 | 182 |
1888726388 | 27.12.2022 | 89774126 | 43287163 | 280 |
1888726389 | 27.12.2022 | 10998776 | 99842651 | 274 |
1888726390 | 28.12.2022 | 72254113 | 16673098 | 274 |
1888726391 | 28.12.2022 | 25536754 | 77649207 | 528 |
В этой таблице 13 строк, каждая строка — отдельная покупка. Нам нужно получить таблицу, где вся информация представлена понятным
образом, а не через id
. Выпишем все поля, которые хотим получить в витрине:
date
— дата покупкиfull_name
— ФИО покупателяshop_address
— адрес магазинаproduct_name
— наименование товараprice
— исходная цена товара в рубляхdiscount
— скидка в долях от единицыdiscounted_price
— цена товара после индивидуальной скидки покупателю
Таблица Purchases
будет основной связующей с другими таблицами. Друг с другом связаны только таблицы customers
и discounts
через
промежуточную таблицу customersdiscounts
.
Чтобы создать витрину, нужно объединить нормализованные таблицы джойнами. В нашем случае в таблицах нет NULL-значений, поэтому мы можем использовать любой вид джойнов, например, INNER JOIN
. INNER JOIN
— это разновидность джойна, которая связывает таблицы по ненулевым ключам. Если ключ какой-то строки имеет значение NULL, эта строка не попадает в итоговую таблицу.
Если в ключах есть NULL-значения, а мы все равно хотим включить эти строки в итоговую таблицу, мы воспользуемся LEFT JOIN
, RIGHT JOIN
или FULL OUTER JOIN
. В таком случае мы выберем джойн исходя из того, включать ли нулевые ключи только левой таблицы, правой или обеих.
Теперь нужно понять, откуда взять нужные нам поля. Например, date
— это дата покупки, поэтому возьмем ее из таблицы Purchases
. А full_name
есть в таблице customers. Попробуем составить селект, чтобы получить два этих поля:
SELECT
date,
full_name
FROM purchases
INNER JOIN customers
ON purchases.customer_id = customers.id;
В customers id
покупателя называется id
, так как эта таблица про покупателей, и id
покупателя в ней — это уникальный ключ. А таблица Purchases
о покупках. Покупатели в ней могут повторяться, поэтому поле называется customer_id
.
shop_address
— это адрес магазина, то есть поле address
в таблице Shops
. Добавим джойн с этой таблицей:
SELECT
date,
full_name,
address AS shop_address
FROM purchases
INNER JOIN customers
ON purchases.customer_id = customers.id
INNER JOIN shops
ON purchases.shop_id = shops.id;
product_name
и price
мы возьмем из таблицы Products:
SELECT
date,
full_name,
address AS shop_address,
product_name,
price
FROM purchases
INNER JOIN customers
ON purchases.customer_id = customers.id
INNER JOIN shops
ON purchases.shop_id = shops.id
INNER JOIN products
ON purchases.product_id = products.id;
Остались поля discount
и discounted_price
. Мы помним, что таблицы Discount
и Customers
связаны через CustomersDiscounts
. Поэтому мы объединим скидки с покупателями через дополнительную таблицу. В итоге посчитаем discounted_price
по следующей формуле:
discounted_price = price - price * discount = price * (1 - discount)
Итоговый запрос будет выглядеть так:
SELECT
date,
full_name,
address AS shop_address,
product_name,
price,
discount,
((1 - discount) * price) AS discounted_price
FROM purchases
INNER JOIN customers
ON purchases.customer_id = customers.id
INNER JOIN shops
ON purchases.shop_id = shops.id
INNER JOIN products
ON purchases.product_id = products.id
INNER JOIN customersdiscounts
ON customers.id = customersdiscounts.customer_id
INNER JOIN discounts
ON customersdiscounts.discount_id = discounts.id;
data_mart
date | full_name | shop_address | product_name | price | discount | discounted_price |
---|---|---|---|---|---|---|
22.12.2022 | Попов Константин Федорович | Ленинградский проспект 12, Москва | Стиральная машина Electrolux | 28499 | 0.1 | 25649 |
22.12.2022 | Гречкина Анастасия Антоновна | ул Нансена 83, Ростов-на-Дону | Беспроводные наушники JBL с микрофоном | 4999 | 0.05 | 4749 |
... | ... | ... | ... | ... | ... | ... |
Ссылка на таблицы
Работаем с витриной
Допустим, нам нужно посчитать суммарную выручку по каждому из магазинов. Для этого используем агрегирующую функцию SUM
с группировкой по магазинам. Функция SUM
позволяет посчитать сумму всех значений покупок в каждой из групп. Группы формируются с помощью GROUP BY
, и наши группы — это каждый отдельный магазин.
Для начала создадим новую таблицу purchasesDataMart
и запишем в нее нашу витрину. Мы создаем новую таблицу, чтобы в дальнейшем можно было обращаться только к ней с другими запросами.
CREATE TABLE purchases_data_mart AS
SELECT
date,
full_name,
address AS shop_address,
product_name,
price,
discount,
((1 - discount) * price) AS discounted_price
FROM purchases
INNER JOIN customers
ON purchases.customer_id = customers.id
INNER JOIN shops
ON purchases.shop_id = shops.id
INNER JOIN products
ON purchases.product_id = products.id
INNER JOIN customersdiscounts
ON customers.id = customersdiscounts.customer_id
INNER JOIN discounts
ON customersdiscounts.discount_id = discounts.id;
В запросе выше мы объединили все таблицы в одну и записали нашу витрину в новую таблицу.
Теперь мы можем составить запрос к таблице витрины:
SELECT
shop_address,
SUM(price) AS total_earnings
FROM purchases_data_mart
GROUP BY shop_address;
Итоговая таблица выглядит так:
purchases.purchasesDataMart
shop_address | total_earnings |
Ленинградский проспект 12, Москва | 143095.99 |
Лиговский проспект 30, Санкт-Петербург | 56187.0 |
Ломоносовский проспект 23, Москва | 9640.0 |
ул Нансена 83, Ростов-на-Дону | 11898.0 |
Ссылка на таблицу
В этой таблице мы видим суммарную выручку по каждому из магазинов в колонке total_earnings
.
Мы создали витрину данных и записали ее в таблицу purchasesDataMart
, а также создали свой первый отчет по витрине.
Выводы
В этом уроке мы рассмотрели концепцию OLTP и витрин данных. Витрины данных — это срезы во всех данных компании, которые интересны аналитику данных. Витрины содержат в себе более информативные таблицы, чем базы OLTP, которые нормализованы и связаны друг с другом с помощью ключей id.
Мы научились создавать витрину. Чтобы превратить таблицы в базе в витрину, нужно объединить информативные поля из разных таблиц по ключам. Главный ключ в каждой из таблиц — это уникальное поле. Обычно он называется id.
Когда у нас уже есть витрина, мы можем строить по ней отчеты и выявлять закономерности.
Витрины данных очень важны в аналитике данных, и вы будете с ними постоянно сталкиваться в дальнейшем.
Самостоятельная работа
Для работы с этой самостоятельной вам понадобится датасет по ссылке
Компания сети магазинов электроники решила отслеживать результативность своих продавцов-консультантов. Продавцы получают оклад и процент от продаж, который зависит от результатов сотрудника в прошлом месяце. С каждой продажи сотруднику начисляется бонус в размере индивидуального бонусного процента от проданного товара с учетом скидки.
В базе данных теперь хранятся новые таблицы:
- Employees — сотрудники
- Bonuses — процент в долях единицы бонуса сотрудника
- EmployeesBonuses — таблица-связка между Employees и Bonuses
Также в таблице Purchases появилось новое поле — employee_id
, которое показывает сотрудника, продавшего товар.
Вам нужно создать витрину данных, которая показывала бы, сколько бонусов получил каждый сотрудник за каждую продажу. Постройте отчет: посчитайте суммарный бонус в рублях, который получил каждый из сотрудников. И найдите сотрудника, который получил самый большой бонус.
Пример решения можно посмотреть по ссылке.
Дополнительные материалы
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.