В отличие от классического подхода, где BI-система жестко фиксирует связи, мы реализовали модель, которая сама определяет, какие таблицы и связи нужны под конкретный дэшлет, и формирует оптимальный запрос «на лету». Это наша новая Адаптивная модель данных Luxms BI (подробнее о модели, как она работает и какие преимущества дает читайте в этой статье). Это значит:
Без лишних JOIN’ов — только необходимые данные, ровно под запрос визуализации.
Еще быстрее — минимальная нагрузка на базу, меньше вычислений, быстрее результат, устойчивость при росте объема данных.
Self-service — пользователю не нужно знать, в какой таблице хранится нужное поле, система сама найдёт и подставит его.
Такой подход когда-то был одной из сильных сторон Qlik, который умел «понимать» связи между разрозненными таблицами. Мы реализовали тоже самое - динамические связи, умную генерацию запроса под контекст визуализации и гибкие фильтры. Причём, на текущий момент среди российских BI-платформ мы единственные, кто это сделал по-настоящему.
Я, Николай Павлов, инженер по обработке данных, и в статье мы разберём, как на практике построить такую модель на примере небольшого проекта: поднимем ClickHouse в Docker, создадим схему «снежинка» с тестовыми данными, соберём адаптивную модель и построим дэшборд с экономическими метриками интернет-магазина.
Сценарий выполнения кейса
Подготовка среды
Используем Luxms BI, в моем примере версии:
luxmsbi-web — 11.1.19
luxmsbi-pg — 11.1.3
luxmsbi-appserver-mono — 11.1.7
Разворачиваем ClickHouse в Docker на рабочем компьютере.
Подготовка данных
Внутри ClickHouse создаём схему базы данных по типу «снежинка» — таблицы, логически связанные между собой.
Загружаем тестовые данные (до 30 строк в каждую таблицу).
Объёмы данных в данном кейсе не важны — акцент на использовании адаптивной модели.
Построение адаптивной модели
В Luxms BI создаём адаптивную модель, добавляя все таблицы и указывая связи между ними.
Создание визуализаций
Разрабатываем дэшборды с визуализацией ключевых экономических метрик интернет-магазина.
Анализ результатов
Проводим оценку работы с адаптивной моделью и формулируем выводы по кейсу.
Инфраструктура в Clickhouse
Внутри clickhouse, создадим базу данных, таблицы и зальём туда данные. Это можно сделать через clickhouse клиента или через dbeaver.
Все таблицы используют движок MergeTree.
Модель описывает интернет-магазин: факты продаж (fact_sales) + набор измерений (dim_*):
Скрытый текст
--Создание базы данных
CREATE database adaptive_m;
-- 1. Корневая таблица дат
CREATE TABLE IF NOT EXISTS dim_date
(
date_key UInt32, -- ключ даты (YYYYMMDD)
date_value Date, -- сама дата
year UInt16, -- год (2024 и т.д.)
quarter UInt8, -- квартал 1-4
month UInt8, -- месяц 1-12
week UInt8, -- номер недели в году
day UInt8, -- день месяца 1-31
day_of_week UInt8, -- день недели 1=пн, 7=вс
is_weekend UInt8 -- признак выходного (1=да, 0=нет)
)
ENGINE = MergeTree()
ORDER BY date_key;
-- 2. Товары
CREATE TABLE IF NOT EXISTS dim_product
(
product_key UInt32, -- технический ключ товара
product_id UInt32, -- бизнес-ID товара
product_name String, -- название товара
category_key UInt32, -- ссылка на категорию (FK → dim_category)
brand_key UInt32, -- ссылка на бренд (FK → dim_brand)
unit_price Decimal(10,2) -- базовая цена товара
)
ENGINE = MergeTree()
ORDER BY product_key;
-- 3. Категории
CREATE TABLE IF NOT EXISTS dim_category
(
category_key UInt32, -- технический ключ категории
category_id UInt32, -- бизнес-ID категории
category_name String, -- название категории товара
parent_category_key UInt32 -- ссылка на родительскую категорию (0 если корень)
)
ENGINE = MergeTree()
ORDER BY category_key;
-- 4. Бренды
CREATE TABLE IF NOT EXISTS dim_brand
(
brand_key UInt32, -- технический ключ бренда
brand_id UInt32, -- бизнес-ID бренда
brand_name String, -- название бренда
country_key UInt32 -- страна регистрации бренда (FK → dim_country)
)
ENGINE = MergeTree()
ORDER BY brand_key;
-- 5. Страны
CREATE TABLE IF NOT EXISTS dim_country
(
country_key UInt32, -- технический ключ страны
country_code FixedString(2),-- двухбуквенный ISO-код
country_name String -- название страны
)
ENGINE = MergeTree()
ORDER BY country_key;
-- 6. Покупатели
CREATE TABLE IF NOT EXISTS dim_customer
(
customer_key UInt32, -- технический ключ клиента
customer_id UInt32, -- бизнес-ID клиента
full_name String, -- ФИО / полное имя
email String, -- электронная почта
city_key UInt32, -- город проживания (FK → dim_city)
registration_date Date -- дата регистрации в магазине
)
ENGINE = MergeTree()
ORDER BY customer_key;
-- 7. Города
CREATE TABLE IF NOT EXISTS dim_city
(
city_key UInt32, -- технический ключ города
city_id UInt32, -- бизнес-ID города
city_name String, -- название города
country_key UInt32 -- ссылка на dim_country
)
ENGINE = MergeTree()
ORDER BY city_key;
-- 8. Склады
CREATE TABLE IF NOT EXISTS dim_warehouse
(
warehouse_key UInt32, -- технический ключ склада
warehouse_id UInt32, -- бизнес-ID склада
warehouse_name String, -- название склада
city_key UInt32 -- город расположения (FK → dim_city)
)
ENGINE = MergeTree()
ORDER BY warehouse_key;
-- 9. Поставщики
CREATE TABLE IF NOT EXISTS dim_supplier
(
supplier_key UInt32, -- технический ключ поставщика
supplier_id UInt32, -- бизнес-ID поставщика
supplier_name String, -- название поставщика
city_key UInt32 -- город расположения (FK → dim_city)
)
ENGINE = MergeTree()
ORDER BY supplier_key;
-- 10. Факт продаж
CREATE TABLE IF NOT EXISTS fact_sales
(
sale_key UInt64, -- уникальный ключ продажи
date_key UInt32, -- день продажи (FK → dim_date)
product_key UInt32, -- товар (FK → dim_product)
customer_key UInt32, -- покупатель (FK → dim_customer)
warehouse_key UInt32, -- склад отгрузки (FK → dim_warehouse)
supplier_key UInt32, -- поставщик (FK → dim_supplier)
quantity UInt32, -- проданное количество
gross_amount Decimal(12,2),-- выручка до скидок
discount_amount Decimal(12,2),-- сумма скидки
net_amount Decimal(12,2) -- выручка после скидок
)
ENGINE = MergeTree()
ORDER BY (date_key, product_key, customer_key);
Заливка данных. Данных немного, поэтому приложены INSERT INTO команды:
Скрытый текст
--dim_date
INSERT INTO dim_date
(date_key, date_value, year, quarter, month, week, day, day_of_week, is_weekend)
VALUES
(20240101,'2024-01-01',2024,1,1,1,1,1,1),
(20240102,'2024-01-02',2024,1,1,1,2,2,0),
(20240103,'2024-01-03',2024,1,1,1,3,3,0),
(20240104,'2024-01-04',2024,1,1,1,4,4,0),
(20240105,'2024-01-05',2024,1,1,1,5,5,0),
(20240106,'2024-01-06',2024,1,1,1,6,6,1),
(20240107,'2024-01-07',2024,1,1,1,7,7,1),
(20240108,'2024-01-08',2024,1,1,2,8,1,0),
(20240109,'2024-01-09',2024,1,1,2,9,2,0),
(20240110,'2024-01-10',2024,1,1,2,10,3,0),
(20240111,'2024-01-11',2024,1,1,2,11,4,0),
(20240112,'2024-01-12',2024,1,1,2,12,5,0),
(20240113,'2024-01-13',2024,1,1,2,13,6,1),
(20240114,'2024-01-14',2024,1,1,2,14,7,1),
(20240115,'2024-01-15',2024,1,1,3,15,1,0),
(20240116,'2024-01-16',2024,1,1,3,16,2,0),
(20240117,'2024-01-17',2024,1,1,3,17,3,0),
(20240118,'2024-01-18',2024,1,1,3,18,4,0),
(20240119,'2024-01-19',2024,1,1,3,19,5,0),
(20240120,'2024-01-20',2024,1,1,3,20,6,1),
(20240121,'2024-01-21',2024,1,1,3,21,7,1),
(20240122,'2024-01-22',2024,1,1,4,22,1,0),
(20240123,'2024-01-23',2024,1,1,4,23,2,0),
(20240124,'2024-01-24',2024,1,1,4,24,3,0),
(20240125,'2024-01-25',2024,1,1,4,25,4,0),
(20240126,'2024-01-26',2024,1,1,4,26,5,0),
(20240127,'2024-01-27',2024,1,1,4,27,6,1),
(20240128,'2024-01-28',2024,1,1,4,28,7,1),
(20240129,'2024-01-29',2024,1,1,5,29,1,0),
(20240130,'2024-01-30',2024,1,1,5,30,2,0);
--dim_country
INSERT INTO dim_country
(country_key, country_code, country_name)
VALUES
(1,'US','United States'),
(2,'DE','Germany'),
(3,'CN','China'),
(4,'RU','Russia'),
(5,'FR','France'),
(6,'JP','Japan'),
(7,'IN','India'),
(8,'BR','Brazil'),
(9,'CA','Canada'),
(10,'MX','Mexico'),
(11,'IT','Italy'),
(12,'ES','Spain'),
(13,'KR','South Korea'),
(14,'NL','Netherlands'),
(15,'SE','Sweden'),
(16,'AU','Australia'),
(17,'CH','Switzerland'),
(18,'AT','Austria'),
(19,'BE','Belgium'),
(20,'DK','Denmark'),
(21,'NO','Norway'),
(22,'FI','Finland'),
(23,'PL','Poland'),
(24,'CZ','Czechia'),
(25,'TR','Turkey'),
(26,'ZA','South Africa'),
(27,'SG','Singapore'),
(28,'MY','Malaysia'),
(29,'TH','Thailand'),
(30,'VN','Vietnam');
--dim_city
INSERT INTO dim_city
(city_key, city_id, city_name, country_key)
VALUES
(1,101,'New York',1),
(2,102,'Los Angeles',1),
(3,103,'Chicago',1),
(4,104,'Berlin',2),
(5,105,'Munich',2),
(6,106,'Hamburg',2),
(7,107,'Shanghai',3),
(8,108,'Beijing',3),
(9,109,'Shenzhen',3),
(10,110,'Moscow',4),
(11,111,'St Petersburg',4),
(12,112,'Paris',5),
(13,113,'Lyon',5),
(14,114,'Tokyo',6),
(15,115,'Osaka',6),
(16,116,'Delhi',7),
(17,117,'Mumbai',7),
(18,118,'São Paulo',8),
(19,119,'Rio de Janeiro',8),
(20,120,'Toronto',9),
(21,121,'Vancouver',9),
(22,122,'Mexico City',10),
(23,123,'Milan',11),
(24,124,'Rome',11),
(25,125,'Madrid',12),
(26,126,'Barcelona',12),
(27,127,'Seoul',13),
(28,128,'Busan',13),
(29,129,'Amsterdam',14),
(30,130,'Stockholm',15);
--dim_brand
INSERT INTO dim_brand
(brand_key, brand_id, brand_name, country_key)
VALUES
(1,201,'Apple',1),
(2,202,'Samsung',13),
(3,203,'BMW',2),
(4,204,'Xiaomi',3),
(5,205,'Sony',6),
(6,206,'LG',13),
(7,207,'Huawei',3),
(8,208,'Dell',1),
(9,209,'HP',1),
(10,210,'Tesla',1),
(11,211,'Mercedes-Benz',2),
(12,212,'Audi',2),
(13,213,'Nike',1),
(14,214,'Adidas',2),
(15,215,'Puma',2),
(16,216,'Zara',12),
(17,217,'H&M',15),
(18,218,'Uniqlo',6),
(19,219,'Toyota',6),
(20,220,'Volkswagen',2),
(21,221,'Ford',1),
(22,222,'GM',1),
(23,223,'Hyundai',13),
(24,224,'Kia',13),
(25,225,'Nestlé',17),
(26,226,'Coca-Cola',1),
(27,227,'PepsiCo',1),
(28,228,'L`Oreal',5),
(29,229,'Colgate',1),
(30,230,'Heineken',14);
--dim_category
INSERT INTO dim_category
(category_key, category_id, category_name, parent_category_key)
VALUES
(1,301,'Electronics',0),
(2,302,'Smartphones',1),
(3,303,'Laptops',1),
(4,304,'Automotive',0),
(5,305,'Electric Cars',4),
(6,306,'Fashion',0),
(7,307,'Clothing',6),
(8,308,'Footwear',6),
(9,309,'Home Appliances',1),
(10,310,'TVs',1),
(11,311,'Audio',1),
(12,312,'Books',0),
(13,313,'Fiction',12),
(14,314,'Non-fiction',12),
(15,315,'Sports',0),
(16,316,'Fitness Equipment',15),
(17,317,'Beauty',0),
(18,318,'Skincare',17),
(19,319,'Makeup',17),
(20,320,'Toys',0),
(21,321,'Outdoor',15),
(22,322,'Camping',21),
(23,323,'Travel',0),
(24,324,'Luggage',23),
(25,325,'Food & Beverage',0),
(26,326,'Snacks',25),
(27,327,'Dairy',25),
(28,328,'Furniture',0),
(29,329,'Office Chairs',28),
(30,330,'Desks',28);
--dim_product
INSERT INTO dim_product
(product_key, product_id, product_name, category_key, brand_key, unit_price)
VALUES
(1,401,'iPhone 15',2,1,999.00),
(2,402,'Galaxy S24',2,2,899.00),
(3,403,'MacBook Air 13',3,1,1299.00),
(4,404,'Model 3 Standard',5,10,42990.00),
(5,405,'Redmi Note 13',2,4,399.00),
(6,406,'Sony WH-1000XM5',11,5,399.99),
(7,407,'LG OLED55C3',10,6,1499.00),
(8,408,'Dell XPS 15',3,8,1899.00),
(9,409,'HP Pavilion',3,9,799.00),
(10,410,'Tesla Model Y',5,10,52990.00),
(11,411,'Mercedes EQE',5,11,74900.00),
(12,412,'Audi Q8 e-tron',5,12,72900.00),
(13,413,'Nike Air Max',8,13,139.99),
(14,414,'Adidas Ultraboost',8,14,180.00),
(15,415,'Puma RS-X',8,15,110.00),
(16,416,'Zara T-shirt',7,16,19.99),
(17,417,'H&M Hoodie',7,17,39.99),
(18,418,'Uniqlo Jeans',7,18,49.90),
(19,419,'Toyota Camry',4,19,25900.00),
(20,420,'VW Golf',4,20,24000.00),
(21,421,'Ford Mustang',4,21,27000.00),
(22,422,'Hyundai Ioniq 5',5,23,45900.00),
(23,423,'Kia EV6',5,24,47900.00),
(24,424,'Nestlé KitKat',26,25,1.49),
(25,425,'Coca-Cola 1L',26,26,1.19),
(26,426,'Pepsi 1L',26,27,1.19),
(27,427,'Lipstick Matte',19,28,12.99),
(28,428,'Office Chair Ergo',29,8,249.00),
(29,429,'Standing Desk',30,8,399.00),
(30,430,'Camping Tent',22,15,199.00);
--dim_customer
INSERT INTO dim_customer
(customer_key, customer_id, full_name, email, city_key, registration_date)
VALUES
(1,501,'John Doe','john.doe@mail.com',1,'2023-01-10'),
(2,502,'Anna Müller','anna.mueller@mail.de',4,'2022-08-15'),
(3,503,'Li Wei','li.wei@mail.cn',7,'2024-03-20'),
(4,504,'Petr Ivanov','petr.ivanov@mail.ru',10,'2021-06-05'),
(5,505,'Maria Garcia','maria.garcia@mail.es',25,'2023-05-12'),
(6,506,'Jean Dupont','jean.dupont@mail.fr',12,'2022-11-30'),
(7,507,'Yuki Sato','yuki.sato@mail.jp',14,'2023-09-01'),
(8,508,'Priya Sharma','priya.sharma@mail.in',16,'2024-02-18'),
(9,509,'Carlos Silva','carlos.silva@mail.br',18,'2021-12-03'),
(10,510,'Emily Clark','emily.clark@mail.ca',20,'2023-07-22'),
(11,511,'Luis Hernandez','luis.hernandez@mail.mx',22,'2022-04-11'),
(12,512,'Laura Rossi','laura.rossi@mail.it',23,'2023-03-14'),
(13,513,'Erik Janssen','erik.janssen@mail.nl',29,'2024-01-05'),
(14,514,'Sven Andersson','sven.andersson@mail.se',30,'2021-10-09'),
(15,515,'Kenji Tanaka','kenji.tanaka@mail.jp',15,'2022-06-27'),
(16,516,'Olivia Smith','olivia.smith@mail.com',2,'2023-08-19'),
(17,517,'Raj Patel','raj.patel@mail.in',17,'2024-04-30'),
(18,518,'Sophie Laurent','sophie.laurent@mail.fr',13,'2022-12-11'),
(19,519,'Lucas Oliveira','lucas.oliveira@mail.br',19,'2023-01-25'),
(20,520,'Isabella Wong','isabella.wong@mail.sg',27,'2022-09-13'),
(21,521,'Noah Andersen','noah.andersen@mail.dk',20,'2023-06-08'),
(22,522,'Mia Müller','mia.mueller@mail.de',5,'2024-05-02'),
(23,523,'Leo Martin','leo.martin@mail.fr',13,'2021-05-30'),
(24,524,'Zoe Kim','zoe.kim@mail.kr',28,'2023-10-14'),
(25,525,'Ethan Brown','ethan.brown@mail.com',3,'2022-07-17'),
(26,526,'Chloe Dubois','chloe.dubois@mail.fr',12,'2023-11-06'),
(27,527,'William Jones','william.jones@mail.ca',21,'2024-03-09'),
(28,528,'Grace Lee','grace.lee@mail.my',28,'2022-02-28'),
(29,529,'Oliver Wilson','oliver.wilson@mail.au',16,'2023-04-22'),
(30,530,'Ava Johnson','ava.johnson@mail.com',2,'2021-11-11');
--dim_warehouse
INSERT INTO dim_warehouse
(warehouse_key, warehouse_id, warehouse_name, city_key)
VALUES
(1,601,'WH-New York',1),
(2,602,'WH-Los Angeles',2),
(3,603,'WH-Chicago',3),
(4,604,'WH-Berlin',4),
(5,605,'WH-Munich',5),
(6,606,'WH-Shanghai',7),
(7,607,'WH-Beijing',8),
(8,608,'WH-Shenzhen',9),
(9,609,'WH-Moscow',10),
(10,610,'WH-St Petersburg',11),
(11,611,'WH-Paris',12),
(12,612,'WH-Lyon',13),
(13,613,'WH-Tokyo',14),
(14,614,'WH-Osaka',15),
(15,615,'WH-Delhi',16),
(16,616,'WH-Mumbai',17),
(17,617,'WH-São Paulo',18),
(18,618,'WH-Rio de Janeiro',19),
(19,619,'WH-Toronto',20),
(20,620,'WH-Vancouver',21),
(21,621,'WH-Mexico City',22),
(22,622,'WH-Milan',23),
(23,623,'WH-Rome',24),
(24,624,'WH-Madrid',25),
(25,625,'WH-Barcelona',26),
(26,626,'WH-Seoul',27),
(27,627,'WH-Busan',28),
(28,628,'WH-Amsterdam',29),
(29,629,'WH-Stockholm',30),
(30,630,'WH-Copenhagen',20);
--dim_supplier
INSERT INTO dim_supplier
(supplier_key, supplier_id, supplier_name, city_key)
VALUES
(1,701,'TechParts USA',1),
(2,702,'EuroElectronics',4),
(3,703,'AsiaGear',7),
(4,704,'AutoSupply RU',10),
(5,705,'Fashion Wholesale FR',12),
(6,706,'Tokyo Components',14),
(7,707,'Mumbai Traders',17),
(8,708,'Brasil Auto Parts',18),
(9,709,'Canadian Goods',20),
(10,710,'Mexico Imports',22),
(11,711,'Milan Fashion House',23),
(12,712,'Madrid Distribution',25),
(13,713,'Seoul Electronics',27),
(14,714,'Amsterdam Supply',29),
(15,715,'Nordic Tools',30),
(16,716,'Shanghai Motors',9),
(17,717,'Beijing Chips',8),
(18,718,'Shenzhen Devices',9),
(19,719,'Berlin Logistics',4),
(20,720,'Munich Spare Parts',5),
(21,721,'Paris Sourcing',12),
(22,722,'London Exports',1),
(23,723,'Singapore Trade',27),
(24,724,'Kuala Lumpur Hub',28),
(25,725,'Bangkok Suppliers',29),
(26,726,'Johannesburg Parts',26),
(27,727,'Sydney Components',16),
(28,728,'Vienna Auto',18),
(29,729,'Zurich Electronics',17),
(30,730,'Warsaw Wholesale',23);
--fact_sales
INSERT INTO fact_sales
(sale_key, date_key, product_key, customer_key, warehouse_key, supplier_key, quantity, gross_amount, discount_amount, net_amount)
VALUES
(1,20240101,1,1,1,1,2,1998.00,100.00,1898.00),
(2,20240101,2,2,4,2,1,899.00,0.00,899.00),
(3,20240102,3,3,6,3,1,1299.00,99.00,1200.00),
(4,20240102,4,4,9,4,1,42990.00,2000.00,40990.00),
(5,20240103,5,5,11,5,3,1197.00,100.00,1097.00),
(6,20240103,6,6,13,6,1,399.99,20.00,379.99),
(7,20240104,7,7,15,7,1,1499.00,150.00,1349.00),
(8,20240104,8,8,16,8,1,1899.00,0.00,1899.00),
(9,20240105,9,9,17,9,2,1598.00,100.00,1498.00),
(10,20240105,10,10,19,10,1,52990.00,3000.00,49990.00),
(11,20240106,11,11,21,11,1,74900.00,5000.00,69900.00),
(12,20240106,12,12,23,12,1,72900.00,4000.00,68900.00),
(13,20240107,13,13,24,13,2,279.98,20.00,259.98),
(14,20240107,14,14,26,14,1,180.00,10.00,170.00),
(15,20240108,15,15,27,15,1,110.00,5.00,105.00),
(16,20240108,16,16,1,1,3,59.97,3.00,56.97),
(17,20240109,17,17,3,16,2,79.98,4.00,75.98),
(18,20240109,18,18,5,17,1,49.90,0.00,49.90),
(19,20240110,19,19,7,18,1,25900.00,2000.00,23900.00),
(20,20240110,20,20,8,19,1,24000.00,1500.00,22500.00),
(21,20240111,21,21,10,20,1,27000.00,1000.00,26000.00),
(22,20240111,22,22,12,21,1,45900.00,3000.00,42900.00),
(23,20240112,23,23,14,22,1,47900.00,2500.00,45400.00),
(24,20240112,24,24,18,23,5,7.45,0.45,7.00),
(25,20240113,25,25,20,24,10,11.90,0.90,11.00),
(26,20240113,26,26,22,25,8,9.52,0.52,9.00),
(27,20240114,27,27,25,26,3,38.97,2.97,36.00),
(28,20240114,28,28,28,27,1,249.00,25.00,224.00),
(29,20240115,29,29,30,28,1,399.00,40.00,359.00),
(30,20240115,30,30,2,29,1,199.00,10.00,189.00);
Создание адаптивной модели
Теперь, после создания инфраструктуры в Clickhouse, можно создать источник данных в Luxms BI. Если ваш сервер Clickhouse доступен только через SSL используйте параметр ssl=true.

Переходим в интерфейс создания кубов, выбираем в качестве источника только что созданный Clickhouse. Вытаскиваем все таблицы для «снежинки» на листик в клеточку. (см. Рис.2)

Следующим шагом создаём связи между таблицами. Связи должны быть логически правильными, например, странно было бы соединять id покупок и id города. Так что тут надо быть внимательным. (см. Рис.3)

Чтобы переключить интерфейс в режим работы с моделью, необходимо навести мышку на таблицу фактов, нажать правую кнопку мыши и выбрать Факты. (см. Рис.4)

Сейчас мы видим, что таблица фактов преобразилась в кубик. Это и есть показатель перехода к режиму адаптивной модели. (см. Рис.5)

Теперь (используем преимущества адаптивной модели) назовём все поля русскими названиями. Во-первых, перед рядовым пользователем не встанет вопроса, а из какой таблицы мне взять и вытащить поле для визуализации. Здесь одинаковые поля из двух разных таблиц как бы сольются в одно, внутренние алгоритмы отберут правильным образом нужное и вытащат его на дэш. Во-вторых, проще ориентироваться во всех атрибутах модели, если они названы понятным языком, что ускорит построение визуализаций на дэшборде. (см. Рис.6)

Как итог, получили модель, изображенную на рисунке 7.

По аналогии с кубами, происходит автоматическое определение типов данных и агрегационных функций для всех полей. На рисунке 8 показан скриншот процесса анализа, на данном этапе проанализировано 3 из 44 поля. Естественно здесь можно самому выбрать внутренний тип, если что-то пошло не так, а также назначить набор агрегационных функций. (см. Рис.9)


Нажимаем кнопку сохранить и, как водится, самое сложное =) : называем понятным образом, придумываем латинское наименование модели и вперёд творить дэшборд! (см. Рис.10)

Построение дэшборда
Я пропустил блок со скриншотами, как создать свой первый атлас, т.к. многие уже в курсе, как это делается. Если же нет и остаются вопросы, милости просим на наше обучение на курсе «Основы визуализации Luxms BI», или присоединяйтесь к нашему комьюнити пользователей в телеграм.
Перейдём к примеру одного дэшлета «Радар» с названием «Выручка по клиентам». Мы выбираем визуализацию Радар и в качестве данных нашу модель с непримечательным названием «Модель Интернет-магазин: факты продаж (fact_sales) + набор измерений (dim_*).» (см. Рис.11)

Теперь пора выбрать, а что мы хотим видеть в качестве метрики на визуале, пусть будут Суммы выручек после скидки, до скидок и скидки в разрезе ФИО/ полное имя . (см. Рис.12 и 13)


Таким образом, строим быстренько один за другим дэшлеты и наслаждаемся результатом на Рис.14.

Выводы
Теперь о главных преимуществах, по началу кажется, что их немного, но они ощутимы и в дальнейшем выстреливают в положительном ключе. Поясню.
Пользователю стало легче при работе с источником вашей визуализации: теперь не надо делать 10 кубов для каждых из 10 дэшлетов или делать один тяжёлый запрос и из него тащить поля для 10 дэшлетов. Достаточно создать одну модель из 10 таблиц, оформить её понятным для людей образом и использовать во всех визуализациях. При этом внутренние алгоритмы адаптивной модели создадут запрос на основе только необходимых таблиц и внутренних связей под потребности вашей визуализации.
Пользователю не нужно задумываться, из какой таблицы взять одно или другое поле, которые логически об одном и том же. А это несомненно сокращает время построения дэшбордов.
Для построения адаптивной модели достаточно иметь таблицу фактов и справочники для неё. Адаптивная модель поддерживает схемы “Звезда” и “снежинка”.
А для грамотного создания этих таблиц в базе, конечно, нужны хорошие дата-инженеры =)
Успехов вам и приятного использования.
Если интересно, как другие решают задачи с данными или есть вопросы по Luxms BI - заходите в наше комьюнити в телеграме, там общаемся, делимся экспертизой и помогаем друг другу =)