{
    "version": "https:\/\/jsonfeed.org\/version\/1.1",
    "title": "Yuriy Gavrilov: posts tagged dbt",
    "_rss_description": "Welcome to my personal place for love, peace and happiness 🤖 Yuiry Gavrilov",
    "_rss_language": "en",
    "_itunes_email": "yvgavrilov@gmail.com",
    "_itunes_categories_xml": "",
    "_itunes_image": "https:\/\/gavrilov.info\/pictures\/userpic\/userpic-square@2x.jpg?1643451008",
    "_itunes_explicit": "no",
    "home_page_url": "https:\/\/gavrilov.info\/tags\/dbt\/",
    "feed_url": "https:\/\/gavrilov.info\/tags\/dbt\/json\/",
    "icon": "https:\/\/gavrilov.info\/pictures\/userpic\/userpic@2x.jpg?1643451008",
    "authors": [
        {
            "name": "Yuriy Gavrilov - B[u]g - for charity.gavrilov.eth",
            "url": "https:\/\/gavrilov.info\/",
            "avatar": "https:\/\/gavrilov.info\/pictures\/userpic\/userpic@2x.jpg?1643451008"
        }
    ],
    "items": [
        {
            "id": "292",
            "url": "https:\/\/gavrilov.info\/all\/dbt-otkryvaet-ishodny-kod-metricflow-upravlyaemye-metriki-dlya-a\/",
            "title": "dbt открывает исходный код MetricFlow: Управляемые метрики для AI и аналитики",
            "content_html": "<p>Компания dbt Labs объявила о важном изменении в своей стратегии: `MetricFlow`, ключевая технология, лежащая в основе `dbt Semantic Layer`, становится полностью открытой. Проект переводится под лицензию Apache 2.0, что позволяет любому использовать, изменять и встраивать его в свои продукты. Это стратегический шаг, направленный на создание единого отраслевого стандарта для определения бизнес-метрик, особенно в свете бурного развития AI-систем.<\/p>\n<p>Оригинал тут: <a href=\"https:\/\/www.getdbt.com\/blog\/open-source-metricflow-governed-metrics\">https:\/\/www.getdbt.com\/blog\/open-source-metricflow-governed-metrics<\/a><br \/>\nА гит тут: <a href=\"https:\/\/github.com\/dbt-labs\/metricflow\">https:\/\/github.com\/dbt-labs\/metricflow<\/a><\/p>\n<p>Еще кстати есть <a href=\"https:\/\/github.com\/memiiso\/opendbt\">https:\/\/github.com\/memiiso\/opendbt<\/a> ( Make dbt great again! :) Может они сольются с метриками, интересно.<\/p>\n<h3>Проблема: почему семантический слой стал критически важен<\/h3>\n<p>Концепция семантического слоя, который служит промежуточным слоем для определения бизнес-логики (метрик, измерений, связей), не нова. Она уже много лет используется в BI-системах для обеспечения согласованности отчетов. Однако с появлением больших языковых моделей (LLM) и инструментов в стиле “Chat with your data” проблема вышла на новый уровень.<\/p>\n<p>Когда AI-агент или LLM пытается ответить на вопрос, обращаясь напрямую к базе данных, он вынужден самостоятельно генерировать SQL-запрос. При этом модель “угадывает”, какие таблицы нужно соединить (`JOIN`), как правильно отфильтровать данные, какую использовать гранулярность по времени и какие оконные функции применить.<\/p>\n<p><b>Проблемы такого подхода:<\/b><\/p>\n<ol start=\"1\">\n<li><b>Несогласованность:<\/b> Две разные модели (или даже одна и та же, но с другим запросом) могут сгенерировать разный SQL для расчета, казалось бы, одной и той же метрики. Это приводит к разным цифрам в отчетах.<\/li>\n<li><b>Ошибки:<\/b> LLM может не знать о тонкостях бизнес-логики, например, о том, что при расчете выручки нужно учитывать возвраты или использовать специальный финансовый календарь.<\/li>\n<li><b>Потеря доверия:<\/b> Когда пользователи получают противоречивые или неверные данные, доверие ко всей системе аналитики быстро падает.<\/li>\n<\/ol>\n<blockquote>\n<p>Метрики не должны быть вероятностными, зависящими от “догадок” LLM при каждом вызове. <b>Они должны быть детерминированными.<\/b><\/p>\n<\/blockquote>\n<p>`MetricFlow` решает именно эту задачу.<\/p>\n<h3>Что такое MetricFlow и как он работает<\/h3>\n<p>`MetricFlow` — это движок, который преобразует семантические определения бизнес-понятий в готовый к выполнению и оптимизированный SQL-код. Аналитик один раз определяет метрику “Валовая маржа” на языке `MetricFlow`, и после этого любая система (BI-инструмент, AI-агент, Python-скрипт) может запросить эту метрику по имени, будучи уверенной, что получит корректный и одинаковый результат.<\/p>\n<h3>Ключевые изменения и их значение<\/h3>\n<ol start=\"1\">\n<li><b>Лицензия Apache 2.0:<\/b> Это одно из главных нововведений. Apache 2.0 — это разрешительная лицензия, которая позволяет другим компаниям свободно встраивать `MetricFlow` в свои коммерческие и открытые продукты. Это снимает барьеры для принятия технологии и способствует ее распространению как стандарта.<\/li>\n<li><b>Сотрудничество с Open Semantic Interchange (OSI):<\/b> dbt Labs будет развивать `MetricFlow` совместно с такими партнерами, как Snowflake и Salesforce, в рамках инициативы OSI. Цель — создать единый стандарт для семантической совместимости между разными платформами, чтобы метрики, определенные один раз, одинаково работали во всех инструментах.<\/li>\n<\/ol>\n<h3>Как MetricFlow обеспечивает надежность AI<\/h3>\n<p>`MetricFlow` предоставляет открытый стандарт для метаданных и расширяемый движок, который превращает намерение (“покажи валовую маржу”) в SQL-запрос для хранилища данных.<\/p>\n<p><b>Пример работы:<\/b><\/p>\n<p>Предположим, пользователь задает AI-агенту вопрос:<\/p>\n<blockquote>\n<p>“Покажи валовую маржу (%) по месяцам за прошлый квартал для Северной Америки (за вычетом скидок и возвратов, по финансовому календарю).”<\/p>\n<\/blockquote>\n<p>Без семантического слоя LLM пришлось бы конструировать сложный запрос с нуля. С `MetricFlow` процесс выглядит так:<\/p>\n<ol start=\"1\">\n<li>Агент распознает намерение и запрашивает у `MetricFlow` метрику `gross_margin_pct` с нужными измерениями (`region`, `fiscal_month`) и фильтрами.<\/li>\n<li>`MetricFlow`, на основе заранее созданных определений, строит план запроса:\n<ul>\n  <li>Находит нужные таблицы: `orders`, `discounts`, `returns`, `cogs` (себестоимость).<\/li>\n  <li>Применяет правильные `JOIN` между ними.<\/li>\n  <li>Применяет фильтр по региону (`North America`).<\/li>\n  <li>Группирует данные по месяцам финансового, а не календарного, года.<\/li>\n  <li>Рассчитывает числитель (выручка) и знаменатель (себестоимость) с учетом того, что популяция данных для них должна быть одинаковой.<\/li>\n  <li>Вычисляет итоговое соотношение.<\/li>\n<\/ul>\n<\/li>\n<li>`MetricFlow` компилирует этот план в оптимизированный SQL-запрос, специфичный для диалекта конкретного хранилища (Snowflake, BigQuery, Databricks и т.д.).<\/li>\n<li>Запрос выполняется в хранилище, и результат возвращается пользователю.<\/li>\n<\/ol>\n<p>При этом весь сгенерированный SQL доступен для проверки, что обеспечивает <b>прозрачность и объяснимость<\/b> вычислений.<\/p>\n<h3>Основные возможности движка:<\/h3>\n<ul>\n<li><b>Единое определение, выполнение где угодно:<\/b> Метрики и измерения определяются один раз, а `MetricFlow` компилирует их в SQL для разных диалектов.<\/li>\n<li><b>Оптимизация производительности:<\/b> Движок строит эффективные запросы, чтобы избежать лишних сканирований и снизить нагрузку на хранилище данных.<\/li>\n<li><b>Поддержка сложных вычислений:<\/b> `MetricFlow` из коробки обрабатывает сложные соединения, оконные функции, расчеты по когортам и полуаддитивные метрики (например, остатки на счетах, которые нельзя просто суммировать по времени).<\/li>\n<\/ul>\n<h3>`MetricFlow` vs. `dbt Semantic Layer`<\/h3>\n<p>Важно понимать различие между двумя компонентами:<\/p>\n<ul>\n<li><b>`MetricFlow`<\/b> — это движок с открытым исходным кодом для <b>определения и вычисления<\/b> метрик. Это “сердце” системы, которое выполняет всю сложную работу по генерации SQL.<\/li>\n<li><b>`dbt Semantic Layer`<\/b> — это коммерческий продукт dbt Labs, построенный *поверх* `MetricFlow`. Он добавляет функциональность корпоративного уровня:\n<ul>\n  <li>Управление доступом (`RBAC`).<\/li>\n  <li>Версионирование определений метрик.<\/li>\n  <li>Аудит и отслеживание происхождения данных (`lineage`).<\/li>\n  <li>Надежные API и коннекторы для интеграции с BI- и AI-инструментами.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>Таким образом, `MetricFlow` становится общедоступным строительным блоком, а `dbt Semantic Layer` — готовым решением для его безопасного и управляемого внедрения в компаниях.<\/p>\n<h3>Итог<\/h3>\n<ol start=\"1\">\n<li><b>dbt Labs сделала `MetricFlow` (движок для расчета метрик) полностью открытым под лицензией Apache 2.0.<\/b> Это позволяет всем желающим использовать его без ограничений.<\/li>\n<li><b>Главная цель — создать открытый стандарт для определения бизнес-метрик.<\/b> Это особенно актуально для AI-систем, которые часто ошибаются при самостоятельной генерации SQL.<\/li>\n<li><b>`MetricFlow` позволяет AI и BI-инструментам запрашивать данные по имени метрики (например, `revenue`), получая детерминированный и корректный SQL-запрос.<\/b> Это повышает надежность и согласованность данных.<\/li>\n<li><b>Этот шаг способствует совместимости инструментов (`interoperability`) и снижает зависимость от конкретного вендора (`vendor lock-in`).<\/b> Метрики, определенные один раз, будут работать одинаково в разных системах.<\/li>\n<li><b>Коммерческий продукт `dbt Semantic Layer` продолжит развиваться<\/b> как решение для управления жизненным циклом метрик в корпоративной среде (безопасность, контроль версий, аудит).<\/li>\n<\/ol>\n",
            "date_published": "2025-11-01T01:03:55+03:00",
            "date_modified": "2025-11-01T01:04:30+03:00",
            "tags": [
                "Data",
                "Data Engineer",
                "dbt",
                "ETL"
            ],
            "_date_published_rfc2822": "Sat, 01 Nov 2025 01:03:55 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "292",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": []
            }
        },
        {
            "id": "272",
            "url": "https:\/\/gavrilov.info\/all\/novaya-era-transformacii-dannyh-dbt-protiv-bruin-i-aac\/",
            "title": "Новая эра трансформации данных: dbt против Bruin и aaC",
            "content_html": "<p>В мире данных произошла тихая, но фундаментальная революция. На смену традиционному подходу <b>ETL (Extract, Transform, Load)<\/b>, где данные преобразовывались до загрузки в хранилище, пришла новая парадигма — <b>ELT (Extract, Load, Transform)<\/b>. Благодаря мощности современных облачных хранилищ (таких как Snowflake, BigQuery, Databricks, Starburst\\Trino) стало выгоднее сначала загружать сырые данные, а уже затем трансформировать их непосредственно в хранилище.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/demo.gif\" width=\"1200\" height=\"900\" alt=\"\" \/>\n<\/div>\n<p>Этот сдвиг породил потребность в инструментах, которые специализируются на последнем шаге — трансформации (T). Именно в этой нише dbt (data build tool) стал безоговорочным лидером, но на его поле появляются и новые сильные игроки, такие как Bruin. Давайте разберемся, что это за инструменты, какой подход они олицетворяют и в чем их ключевые различия.<\/p>\n<h4>Подход «Аналитика как код»<\/h4>\n<p>И dbt, и Bruin являются яркими представителями движения <b>“Analytics as Code”<\/b> (аналитика как код). Это не просто инструменты, а целая философия, которая переносит лучшие практики разработки программного обеспечения в мир аналитики данных.<\/p>\n<p><b>Основные принципы и идеи:<\/b><\/p>\n<ol start=\"1\">\n<li><b>Версионирование:<\/b> Все трансформации данных описываются в виде кода (в основном SQL), который хранится в системе контроля версий, такой как Git. Это позволяет отслеживать изменения, совместно работать и откатываться к предыдущим версиям.<\/li>\n<li><b>Модульность и переиспользование (DRY – Don’t Repeat Yourself):<\/b> Сложные трансформации разбиваются на небольшие, логически завершенные модели, которые могут ссылаться друг на друга. Это делает код чище, понятнее и позволяет повторно использовать уже написанную логику.<\/li>\n<li><b>Тестирование:<\/b> Код трансформаций должен быть протестирован. Инструменты позволяют автоматически проверять качество данных после преобразований: на уникальность ключей, отсутствие `NULL` значений, соответствие заданным условиям и т.д.<\/li>\n<li><b>Документация и прозрачность:<\/b> Процесс трансформации становится самодокументируемым. Инструменты могут автоматически генерировать документацию и строить графы зависимостей моделей (data lineage), показывая, как данные текут и преобразуются от источника к конечному виду. <a href=\"https:\/\/www.element61.be\/en\/resource\/when-use-dbt-or-delta-live-tables\">element61.be<\/a><\/li>\n<li><b>CI\/CD (Continuous Integration \/ Continuous Deployment):<\/b> Изменения в коде трансформаций могут автоматически тестироваться и разворачиваться в продуктивную среду, что значительно ускоряет циклы разработки.<\/li>\n<\/ol>\n<p><b>Решаемые проблемы:<\/b><\/p>\n<ul>\n<li><b>“Черные ящики” ETL:<\/b> Заменяют сложные, трудноподдерживаемые и непрозрачные ETL-процессы на понятный и документированный код.<\/li>\n<li><b>Рассинхронизация команд:<\/b> Стирают границы между инженерами данных и аналитиками, позволяя аналитикам, владеющим SQL, самостоятельно создавать надежные модели данных.<\/li>\n<li><b>Низкое качество данных:<\/b> Встроенные механизмы тестирования помогают обеспечить надежность и согласованность данных.<\/li>\n<\/ul>\n<p>---<\/p>\n<h4>dbt (data build tool): Золотой стандарт трансформации<\/h4>\n<p><b>dbt<\/b> — это инструмент с открытым исходным кодом, который позволяет аналитикам и инженерам трансформировать данные в их хранилищах с помощью простых SQL-запросов. Важно понимать, что dbt <b>не извлекает и не загружает данные<\/b>. Он специализируется исключительно на шаге <b>“T”<\/b> в ELT  <a href=\"https:\/\/vutr.substack.com\/p\/why-is-dbt-so-popular\">vutr.substack.com<\/a>. <a href=\"https:\/\/github.com\/dbt-labs\/dbt-core\">dbt git<\/a>.<\/p>\n<p>Он работает как компилятор и исполнитель: вы пишете модели данных в `.sql` файлах, используя шаблонизатор Jinja для добавления логики (циклы, условия, макросы). Затем dbt компилирует этот код в чистый SQL и выполняет его в вашем хранилище данных <a href=\"https:\/\/www.element61.be\/en\/resource\/when-use-dbt-or-delta-live-tables\">element61.be<\/a>.<\/p>\n<h5>Плюсы dbt<\/h5>\n<ul>\n<li><b>Огромное сообщество и экосистема:<\/b> dbt стал де-факто стандартом индустрии. Существует огромное количество статей, курсов, готовых пакетов (библиотек) и экспертов.<\/li>\n<li><b>Фокус на SQL:<\/b> Низкий порог входа для аналитиков, которые уже знают SQL. Это демократизирует процесс трансформации данных.<\/li>\n<li><b>Мощное тестирование и документирование:<\/b> Встроенные команды для тестирования данных и автоматической генерации проектной документации с графом зависимостей.<\/li>\n<li><b>Зрелость и надежность:<\/b> Инструмент проверен временем и используется тысячами компаний по всему миру.<\/li>\n<li><b>Гибкость:<\/b> Благодаря шаблонизатору Jinja можно создавать очень сложные и переиспользуемые макросы, адаптируя dbt под любые нужды.<\/li>\n<\/ul>\n<h5>Минусы dbt<\/h5>\n<ul>\n<li><b>Только трансформация:<\/b> dbt не занимается извлечением (E) и загрузкой (L). Для этого вам понадобятся отдельные инструменты (например, Fivetran, Airbyte), что усложняет стек технологий.<\/li>\n<li><b>Кривая обучения:<\/b> Хотя основы просты, освоение продвинутых возможностей Jinja, макросов и структуры проекта требует времени.<\/li>\n<li><b>Зависимость от Python-моделей:<\/b> Хотя недавно появилась поддержка моделей на Python, она все еще не так нативна и проста, как основной SQL-подход, и требует дополнительных настроек.<\/li>\n<\/ul>\n<p>---<\/p>\n<h4>Bruin Data: Универсальный боец<\/h4>\n<p><b>Bruin<\/b> — это более новый игрок на рынке, который позиционирует себя как инструмент для создания “end-to-end” пайплайнов данных. В отличие от dbt, он не ограничивается только трансформацией, а стремится охватить больше этапов работы с данными, включая их загрузку (ingestion) <a href=\"https:\/\/github.com\/bruin-data\/bruin\">https:\/\/github.com\/bruin-data\/bruin<\/a>.<\/p>\n<p>Bruin разделяет ту же философию “Analytics as Code”, но предлагает более интегрированный опыт, где SQL и Python являются равноправными гражданами.<\/p>\n<h5>Плюсы Bruin<\/h5>\n<ul>\n<li><b>Универсальность:<\/b> Один инструмент для определения всего пайплайна: от загрузки из источников до финальных витрин данных. Это может упростить стек технологий.<\/li>\n<li><b>Нативная поддержка SQL и Python:<\/b> Позволяет легко комбинировать задачи на разных языках в одном пайплайне без дополнительных настроек. Это идеально для задач, где чистый SQL громоздок (например, работа с API, машинное обучение).<\/li>\n<li><b>Простота конфигурации:<\/b> Зачастую требует меньше шаблонного кода (boilerplate) для определения ассетов и пайплайнов по сравнению с dbt.<\/li>\n<li><b>Встроенное качество данных:<\/b> Как и dbt, делает акцент на проверках качества на каждом шаге.<\/li>\n<\/ul>\n<h5>Минусы Bruin<\/h5>\n<ul>\n<li><b> Пока маленькое сообщество:<\/b> Как у нового инструмента, у Bruin гораздо меньше пользователей, готовых решений и обсуждений на форумах по сравнению с dbt. Найти помощь или готовый пакет для решения специфической задачи сложнее.<\/li>\n<li><b>Незрелость:<\/b> Инструмент моложе, а значит, наверное, потенциально менее стабилен и может иметь меньше интеграций по сравнению с проверенным dbt. Пока нет облачных функция за деньги. Я так думал, но все же есть <a href=\"https:\/\/getbruin.com.\">https:\/\/getbruin.com.<\/a><\/li>\n<li><b>“Мастер на все руки — эксперт ни в чем?”:<\/b> Стремление охватить все этапы (E, L, T) может означать, что в каждом отдельном компоненте Bruin может уступать лучшим в своем классе специализированным инструментам (например, Fivetran в загрузке, dbt в трансформации), но это конечно субъективно.<\/li>\n<\/ul>\n<h4>Сводное сравнение<\/h4>\n<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"e2-text-table\">\n<tr>\n<td style=\"text-align: center\">Характеристика<\/td>\n<td style=\"text-align: center\">dbt (data build tool)<\/td>\n<td style=\"text-align: center\">Bruin Data<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>Основная задача<\/b><\/td>\n<td style=\"text-align: center\">Трансформация (T в ELT)<\/td>\n<td style=\"text-align: center\">Весь пайплайн (E, L, T)<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>Ключевые языки<\/b><\/td>\n<td style=\"text-align: center\">SQL с шаблонизатором Jinja<\/td>\n<td style=\"text-align: center\">SQL и Python как равноправные<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>Экосистема<\/b><\/td>\n<td style=\"text-align: center\">Огромная, стандарт индустрии<\/td>\n<td style=\"text-align: center\">Маленькая, развивающаяся<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>Зрелость<\/b><\/td>\n<td style=\"text-align: center\">Высокая, проверен временем<\/td>\n<td style=\"text-align: center\">Низкая\/Средняя<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>Стек инструментов<\/b><\/td>\n<td style=\"text-align: center\">Требует отдельных E\/L инструментов<\/td>\n<td style=\"text-align: center\">Стремится быть самодостаточным<\/td>\n<\/tr>\n<\/table>\n<h4>Итого<\/h4>\n<p>Выбор между dbt и Bruin — это выбор между двумя стратегиями построения современного стека данных.<\/p>\n<p><b>Выбирайте dbt, если:<\/b><\/p>\n<ul>\n<li>Вы строите гибкий стек из лучших в своем классе инструментов (“best-of-breed”): один для загрузки, другой для хранения, третий для трансформации.<\/li>\n<li>Ваша команда в основном состоит из аналитиков, сильных в SQL.<\/li>\n<li>Для вас критически важны поддержка сообщества, стабильность и наличие готовых решений.<\/li>\n<li>Вы работаете в большой организации, где принятие отраслевых стандартов является преимуществом.<\/li>\n<li>Вы готовы переехать к ним в платное облако, когда нибудь. Большая часть функционала доступна там.<\/li>\n<\/ul>\n<p><b>Выбирайте Bruin, если:<\/b><\/p>\n<ul>\n<li>Вы предпочитаете единый, интегрированный инструмент для управления всеми пайплайнами, чтобы упростить архитектуру<\/li>\n<li>Вы любите open source и End-to-end дата framework: фор data ingestion + transformations + кволити. :)<\/li>\n<li>Ваши пайплайны требуют тесной связки SQL и Python для трансформаций (например, обогащение данных через вызовы API или модели ML).<\/li>\n<li>Вы начинаете новый проект или работаете в небольшой команде и цените скорость настройки и меньшее количество движущихся частей.<\/li>\n<li>Вы Go’шник :) –  Bruin написан на Go почти на 100%.<\/li>\n<\/ul>\n<p>И dbt, и Bruin — мощные инструменты, воплощающие современные подходы к инженерии данных. dbt предлагает проверенный, сфокусированный и невероятно мощный движок для трансформаций, ставший стандартом. Bruin же предлагает более универсальный и интегрированный подход, который может быть привлекателен для команд, стремящихся к простоте и нативной поддержке Python.<\/p>\n<h4>А что такое “Аналитика как код” (Analytics as Code, AaC)?<\/h4>\n<p><b>Аналитика как код<\/b> — это подход к управлению аналитическими процессами, при котором все компоненты аналитики — от моделей данных и метрик до отчетов и правил доступа — определяются в виде кода в человекочитаемых файлах. Эти файлы затем управляются так же, как исходный код любого другого программного обеспечения: с помощью систем контроля версий, автоматизированного тестирования и развертывания <a href=\"https:\/\/medium.com\/gooddata-developers\/analytics-as-code-managing-analytics-solutions-like-any-other-software-504372ba6a61\">medium.com<\/a>.<\/p>\n<p>Самая близкая и известная аналогия — это <b>Infrastructure as Code (IaC)<\/b>. Как IaC (например, с помощью Terraform) позволил инженерам описывать серверы, сети и базы данных в коде вместо ручной настройки через веб-интерфейсы, так и AaC позволяет описывать в коде всё, что связано с данными <a href=\"https:\/\/medium.com\/@terezacihelkova\/analytics-as-code-what-is-it-and-how-does-it-help-you-93e9a3c179ee\">medium.com<\/a>.<\/p>\n<blockquote>\n<p>Идея проста и убедительна: “настройте свои системы один раз, выразите это в виде кода, а затем поместите в систему контроля версий” <a href=\"https:\/\/www.holistics.io\/blog\/analytics-as-code\/\">holistics.io<\/a>.<\/p>\n<\/blockquote>\n<h4>Проблема: Как было раньше?<\/h4>\n<p>Чтобы понять ценность AaC, нужно посмотреть на проблемы, которые он решает. В традиционном подходе аналитика часто была разрозненной и хрупкой:<\/p>\n<ul>\n<li><b>Логика в “черных ящиках”:<\/b> Сложные преобразования данных были скрыты внутри GUI-интерфейсов старых ETL-инструментов или непосредственно в настройках BI-платформы (например, Tableau, Power BI). Никто, кроме автора, не мог легко понять, как рассчитывается та или иная метрика.<\/li>\n<li><b>Разрозненные SQL-скрипты:<\/b> Аналитики хранили важные SQL-запросы на своих локальных машинах, в общих папках или на wiki-страницах. Не было единой версии правды, код дублировался и быстро устаревал.<\/li>\n<li><b>Отсутствие контроля версий:<\/b> Невозможно было отследить, кто, когда и почему изменил логику расчета ключевого показателя. Откат к предыдущей работающей версии был настоящей головной болью.<\/li>\n<li><b>“Ручное” тестирование:<\/b> Проверка качества данных после изменений была ручным, подверженным ошибкам процессом. Часто о проблемах узнавали уже от бизнес-пользователей, которые видели неверные цифры в отчетах.<\/li>\n<li><b>Рассинхронизация:<\/b> Инженеры данных готовили сырые таблицы, а аналитики строили свою логику поверх них. Любые изменения с одной стороны могли сломать всю цепочку, не будучи замеченными вовремя.<\/li>\n<\/ul>\n<p>Этот хаос приводил к главному — <b>недоверию к данным<\/b>. Никто не мог быть уверен, что цифры в дашборде верны.<\/p>\n<h4>Ключевые принципы “Аналитики как код”<\/h4>\n<p>AaC решает эти проблемы, внедряя практики из мира разработки ПО.<\/p>\n<ol start=\"1\">\n<li><b>Декларативное определение:<\/b> Все аналитические артефакты описываются в файлах.\n<ul>\n  <li>Модели данных:** `SELECT * FROM ...` в `.sql` файлах.<\/li>\n  <li>Тесты:** `not_null`, `unique` в `.yml` файлах.<\/li>\n  <li>Документация:** Описания таблиц и полей в `.yml` файлах.<\/li>\n  <li>Метрики и дашборды:** Определения в `.yml` или специализированных файлах <a href=\"https:\/\/medium.com\/gooddata-developers\/analytics-as-code-managing-analytics-solutions-like-any-other-software-504372ba6a61\">medium.com<\/a>.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<ol start=\"2\">\n<li><b>Контроль версий (Git):<\/b> Весь код хранится в репозитории (например, на GitHub или GitLab).\n<ul>\n  <li>Прозрачность:** Каждое изменение — это `commit` с понятным описанием.<\/li>\n  <li>Совместная работа:** Аналитики работают в отдельных ветках, а изменения вносятся через `Pull Request` (или `Merge Request`), что позволяет проводить ревью кода (code review).<\/li>\n  <li>Восстанавливаемость:** Если что-то пошло не так, можно легко откатиться к предыдущей версии.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<ol start=\"3\">\n<li><b>Автоматизированное тестирование:<\/b> Тесты являются неотъемлемой частью кода. Они запускаются автоматически при каждом изменении, чтобы гарантировать, что данные по-прежнему соответствуют ожиданиям (например, `user_id` всегда уникален и не равен `NULL`).<\/li>\n<\/ol>\n<ol start=\"4\">\n<li><b>CI\/CD (Непрерывная интеграция и развертывание):<\/b> Процессы полностью автоматизированы.\n<ul>\n  <li>Когда аналитик вносит изменения в `Pull Request`, автоматически запускаются тесты.<\/li>\n  <li>После одобрения и слияния ветки изменения автоматически развертываются в продуктивной среде (например, dbt Cloud или Jenkins запускает команду `dbt run`).<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<ol start=\"5\">\n<li><b>Модульность и переиспользование (DRY – Don’t Repeat Yourself):<\/b> Сложные потоки данных разбиваются на небольшие, логичные и переиспользуемые модели. Одна модель может ссылаться на другую, создавая четкий граф зависимостей (lineage), который можно визуализировать.<\/li>\n<\/ol>\n<h4>Преимущества подхода AaC<\/h4>\n<p>Принятие этой философии дает компании ощутимые выгоды:<\/p>\n<ul>\n<li><b>Надежность и доверие:<\/b> Благодаря автоматическому тестированию и ревью кода значительно повышается качество данных, а вместе с ним и доверие бизнеса к аналитике.<\/li>\n<li><b>Скорость и гибкость:<\/b> Аналитики могут вносить изменения гораздо быстрее. Цикл от идеи до готового отчета сокращается с недель до дней или даже часов.<\/li>\n<li><b>Масштабируемость:<\/b> Кодовая база легко поддерживается и расширяется. Новые члены команды могут быстро разобраться в проекте благодаря документации и прозрачности.<\/li>\n<li><b>Прозрачность и обнаруживаемость:<\/b> Автоматически сгенерированная документация и графы зависимостей позволяют любому сотруднику понять, откуда берутся данные и как они рассчитываются.<\/li>\n<li><b>Демократизация:<\/b> AaC дает возможность аналитикам, владеющим SQL, самостоятельно создавать надежные и протестированные модели данных, не дожидаясь инженеров данных. Это стирает барьеры между командами.<\/li>\n<\/ul>\n<p>В конечном итоге, “Аналитика как код” — это культурный сдвиг, который превращает аналитику из ремесленного занятия в зрелую инженерную дисциплину, обеспечивая скорость, надежность и масштабируемость, необходимые современному бизнесу.<\/p>\n",
            "date_published": "2025-08-23T16:04:02+03:00",
            "date_modified": "2025-08-24T11:51:29+03:00",
            "tags": [
                "BI",
                "Data Engineer",
                "Data Quality",
                "dbt",
                "ETL"
            ],
            "image": "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-08-23-v-15.37.26.png",
            "_date_published_rfc2822": "Sat, 23 Aug 2025 16:04:02 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "272",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": [
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-08-23-v-15.37.26.png",
                    "https:\/\/gavrilov.info\/pictures\/demo.gif"
                ]
            }
        },
        {
            "id": "267",
            "url": "https:\/\/gavrilov.info\/all\/opisanie-paterna-slowly-changing-dimensions-scd\/",
            "title": "Описание патерна Slowly Changing Dimensions (SCD)",
            "content_html": "<p><b>Slowly Changing Dimensions (SCD)<\/b>, или <b>Медленно меняющиеся измерения<\/b>, — это концепция и набор методов из области хранилищ данных (Data Warehousing), которые используются для управления изменениями в атрибутах измерений с течением времени. Измерения — это справочные таблицы, которые описывают бизнес-сущности, такие как клиенты, продукты, сотрудники, географические регионы.<\/p>\n<p>Атрибуты этих сущностей (например, адрес клиента или категория продукта) меняются, но обычно не очень часто — отсюда и название “медленно меняющиеся”. Основная задача SCD — решить, как хранить эти изменения, чтобы обеспечить точность исторических отчетов <a href=\"https:\/\/www.datacamp.com\/tutorial\/mastering-slowly-changing-dimensions-scd\">www.datacamp.com<\/a>.<\/p>\n<p>Например, если вы просто перезапишете адрес клиента, вы потеряете информацию о том, где он жил раньше. Это может исказить анализ продаж по регионам за прошлые периоды. Патерны SCD предлагают различные стратегии для решения этой проблемы.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/opisanie-paterna-slowly-changing-dimensions-scd.png\" width=\"1060\" height=\"720\" alt=\"\" \/>\n<\/div>\n<h4>Основные типы SCD<\/h4>\n<p>Существует несколько типов SCD, но самыми распространенными и фундаментальными являются Типы 1, 2 и 3.<\/p>\n<p>---<\/p>\n<h5>Тип 1: Перезапись атрибута (Overwrite)<\/h5>\n<p>Это самый простой подход. При изменении атрибута старое значение просто перезаписывается новым.<\/p>\n<ul>\n<li>Как работает:** Находится существующая запись в таблице измерения и значение в нужном столбце обновляется.<\/li>\n<li>Когда использовать:** Когда нет необходимости хранить историю изменений. Например, для исправления опечатки в имени клиента.<\/li>\n<li>Преимущества:** Простота реализации, не требует увеличения объема хранилища.<\/li>\n<li>Недостатки:<b> **История изменений полностью теряется.<\/b> Анализ, основанный на исторических значениях атрибута, становится невозможным.<\/li>\n<\/ul>\n<p><b>Пример:<\/b><br \/>\nУ нас есть клиент Анна Петрова, которая живет в Москве.<\/p>\n<p>*Таблица `DimCustomer` до изменений:*<\/p>\n<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"e2-text-table\">\n<tr>\n<td style=\"text-align: center\">CustomerKey<\/td>\n<td style=\"text-align: center\">FullName<\/td>\n<td style=\"text-align: center\">City<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">:---<\/td>\n<td style=\"text-align: center\">:---<\/td>\n<td style=\"text-align: center\">:---<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">101<\/td>\n<td style=\"text-align: center\">Анна Петрова<\/td>\n<td style=\"text-align: center\">Москва<\/td>\n<\/tr>\n<\/table>\n<p>Анна переезжает в Санкт-Петербург. При использовании <b>SCD Тип 1<\/b> таблица будет обновлена:<\/p>\n<p>*Таблица `DimCustomer` после изменений:*<\/p>\n<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"e2-text-table\">\n<tr>\n<td style=\"text-align: center\">CustomerKey<\/td>\n<td style=\"text-align: center\">FullName<\/td>\n<td style=\"text-align: center\">City<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">:---<\/td>\n<td style=\"text-align: center\">:---<\/td>\n<td style=\"text-align: center\">:---<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">101<\/td>\n<td style=\"text-align: center\">Анна Петрова<\/td>\n<td style=\"text-align: right\">Санкт-Петербург<\/td>\n<\/tr>\n<\/table>\n<p>Теперь невозможно узнать, что раньше Анна жила в Москве.<\/p>\n<p>---<\/p>\n<h5>Тип 2: Добавление новой строки (Add New Row)<\/h5>\n<p>Это самый распространенный и мощный тип SCD, так как он позволяет сохранять полную историю изменений.<\/p>\n<ul>\n<li>Как работает:** Вместо перезаписи существующей записи, создается новая запись для той же сущности (например, того же клиента). Старая запись помечается как неактуальная (истекшая), а новая — как актуальная. Для этого в таблицу измерения обычно добавляют несколько служебных столбцов <a href=\"https:\/\/learn.microsoft.com\/en-us\/fabric\/data-factory\/slowly-changing-dimension-type-two\">learn.microsoft.com<\/a>:\n<ul>\n  <li>`StartDate` \/ `EffectiveDate` — дата, с которой запись стала актуальной.<\/li>\n  <li>`EndDate` — дата, когда запись перестала быть актуальной.<\/li>\n  <li>`IsCurrent` \/ `CurrentFlag` — флаг (например, ‘Yes’\/’No’ или 1\/0), показывающий, является ли эта запись текущей.<\/li>\n<\/ul>\n<\/li>\n<li>Когда использовать:** Когда сохранение истории критически важно для анализа. Это стандартный выбор для большинства атрибутов в хранилищах данных.<\/li>\n<li>Преимущества:** Сохраняется полная, точная история. Позволяет проводить корректный point-in-time анализ (анализ на определенный момент времени).<\/li>\n<li>Недостатки:** Увеличивается объем таблицы, так как для одного клиента может быть несколько записей. Запросы могут стать сложнее (нужно фильтровать по флагу `IsCurrent` или по диапазону дат) <a href=\"https:\/\/hevodata.com\/learn\/slowly-changing-dimensions\/\">hevodata.com<\/a>.<\/li>\n<\/ul>\n<p><b>Пример:<\/b><br \/>\nСнова используем пример с Анной Петровой.<\/p>\n<p>*Таблица `DimCustomer` до изменений:*<\/p>\n<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"e2-text-table\">\n<tr>\n<td style=\"text-align: center\">SurrogateKey<\/td>\n<td style=\"text-align: center\">CustomerID<\/td>\n<td style=\"text-align: center\">FullName<\/td>\n<td style=\"text-align: center\">City<\/td>\n<td style=\"text-align: center\">StartDate<\/td>\n<td style=\"text-align: center\">EndDate<\/td>\n<td style=\"text-align: center\">IsCurrent<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">:---<\/td>\n<td style=\"text-align: center\">:---<\/td>\n<td style=\"text-align: center\">:---<\/td>\n<td style=\"text-align: center\">:---<\/td>\n<td style=\"text-align: center\">:---<\/td>\n<td style=\"text-align: center\">:---<\/td>\n<td style=\"text-align: center\">:---<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">1<\/td>\n<td style=\"text-align: center\">101<\/td>\n<td style=\"text-align: right\">Анна Петрова<\/td>\n<td style=\"text-align: center\">Москва<\/td>\n<td style=\"text-align: center\">2020-01-15<\/td>\n<td style=\"text-align: center\">NULL<\/td>\n<td style=\"text-align: center\">Yes<\/td>\n<\/tr>\n<\/table>\n<p>Анна переезжает 16 августа 2024 года. При использовании <b>SCD Тип 2<\/b> таблица изменится так:<\/p>\n<p>*Таблица `DimCustomer` после изменений:*<\/p>\n<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"e2-text-table\">\n<tr>\n<td style=\"text-align: center\">SurrogateKey<\/td>\n<td style=\"text-align: center\">CustomerID<\/td>\n<td style=\"text-align: center\">FullName<\/td>\n<td style=\"text-align: center\">City<\/td>\n<td style=\"text-align: center\">StartDate<\/td>\n<td style=\"text-align: center\">EndDate<\/td>\n<td style=\"text-align: center\">IsCurrent<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">:---<\/td>\n<td style=\"text-align: center\">:---<\/td>\n<td style=\"text-align: center\">:---<\/td>\n<td style=\"text-align: center\">:---<\/td>\n<td style=\"text-align: center\">:---<\/td>\n<td style=\"text-align: center\">:---<\/td>\n<td style=\"text-align: center\">:---<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">1<\/td>\n<td style=\"text-align: center\">101<\/td>\n<td style=\"text-align: right\">Анна Петрова<\/td>\n<td style=\"text-align: center\">Москва<\/td>\n<td style=\"text-align: center\">2020-01-15<\/td>\n<td style=\"text-align: center\">2024-08-15<\/td>\n<td style=\"text-align: center\">No<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>2<\/b><\/td>\n<td style=\"text-align: center\">101<\/td>\n<td style=\"text-align: right\">Анна Петрова<\/td>\n<td style=\"text-align: right\">Санкт-Петербург<\/td>\n<td style=\"text-align: center\"><b>2024-08-16<\/b><\/td>\n<td style=\"text-align: center\">NULL<\/td>\n<td style=\"text-align: center\"><b>Yes<\/b><\/td>\n<\/tr>\n<\/table>\n<p>Теперь мы сохранили всю историю перемещений Анны.<\/p>\n<p>---<\/p>\n<h5>Тип 3: Добавление нового атрибута (Add New Attribute)<\/h5>\n<p>Этот тип сохраняет ограниченную историю, добавляя в таблицу отдельный столбец для предыдущего значения атрибута.<\/p>\n<ul>\n<li>Как работает:** Создается новый столбец, например, `PreviousCity`. Когда атрибут `City` меняется, его старое значение копируется в `PreviousCity`, а новое записывается в `City`.<\/li>\n<li>Когда использовать:** Когда важно отслеживать только предыдущее состояние для сравнения, а более глубокая история не нужна.<\/li>\n<li>Преимущества:** Простота реализации, не увеличивает количество строк, легко запрашивать текущее и предыдущее значения.<\/li>\n<li>Недостатки:** Сохраняет историю только на один шаг назад. Не масштабируется, если нужно хранить более двух-трех последних значений.<\/li>\n<\/ul>\n<p><b>Пример:<\/b><br \/>\nАнна переезжает из Москвы в Санкт-Петербург.<\/p>\n<p>*Таблица `DimCustomer` до изменений:*<\/p>\n<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"e2-text-table\">\n<tr>\n<td style=\"text-align: center\">CustomerKey<\/td>\n<td style=\"text-align: center\">FullName<\/td>\n<td style=\"text-align: center\">CurrentCity<\/td>\n<td style=\"text-align: center\">PreviousCity<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">:---<\/td>\n<td style=\"text-align: center\">:---<\/td>\n<td style=\"text-align: center\">:---<\/td>\n<td style=\"text-align: center\">:---<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">101<\/td>\n<td style=\"text-align: center\">Анна Петрова<\/td>\n<td style=\"text-align: center\">Москва<\/td>\n<td style=\"text-align: center\">NULL<\/td>\n<\/tr>\n<\/table>\n<p>*Таблица `DimCustomer` после изменений:*<\/p>\n<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"e2-text-table\">\n<tr>\n<td style=\"text-align: center\">CustomerKey<\/td>\n<td style=\"text-align: center\">FullName<\/td>\n<td style=\"text-align: center\">CurrentCity<\/td>\n<td style=\"text-align: center\">PreviousCity<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">:---<\/td>\n<td style=\"text-align: center\">:---<\/td>\n<td style=\"text-align: center\">:---<\/td>\n<td style=\"text-align: center\">:---<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">101<\/td>\n<td style=\"text-align: center\">Анна Петрова<\/td>\n<td style=\"text-align: center\"><b>Санкт-Петербург<\/b><\/td>\n<td style=\"text-align: center\"><b>Москва<\/b><\/td>\n<\/tr>\n<\/table>\n<p>Если Анна переедет снова, значение “Москва” будет потеряно.<\/p>\n<h4>Другие типы SCD<\/h4>\n<p>Существуют и более сложные гибридные типы:<\/p>\n<ul>\n<li>Тип 4 (History Table):** Основная таблица измерения хранит только текущие данные (как Тип 1), а вся история изменений выносится в отдельную таблицу. Это полезно, когда изменения происходят часто в очень больших таблицах измерений <a href=\"https:\/\/medium.com\/@zivilevalutytesilveira\/slowly-changing-dimensions-in-data-warehousing-9479699bf40f\">medium.com<\/a>.<\/li>\n<li>Тип 6 (Hybrid):** Комбинирует подходы Типов 1, 2 и 3. Например, в таблице хранятся поля для полной истории (SCD2) и одновременно поле для текущего значения (SCD1 для быстрого доступа) и предыдущего значения (SCD3 для сравнения).<\/li>\n<\/ul>\n<h4>Тип 4: Добавление исторической таблицы (History Table \/ Audit Table)<\/h4>\n<p><b>Идея:<\/b> Разделить текущие данные и исторические данные в разные таблицы для оптимизации производительности.<\/p>\n<ul>\n<li>Как работает:** Создаются две таблицы:\n<ol start=\"1\">\n  <li><b>Таблица измерения (Dimension Table):<\/b> Хранит *только* текущие, самые последние данные. Эта таблица по своей сути работает как <b>SCD Тип 1<\/b> (данные просто перезаписываются). Она маленькая, быстрая и идеально подходит для большинства запросов, где история не нужна.<\/li>\n  <li><b>Историческая таблица (History Table):<\/b> Хранит всю историю изменений. Каждый раз, когда в основной таблице происходит изменение, старая версия строки (до обновления) добавляется в историческую таблицу. Эта таблица часто содержит служебные поля, как в SCD Тип 2 (`StartDate`, `EndDate`, `Version`), для отслеживания временного периода.<\/li>\n<\/ol>\n<\/li>\n<li>Когда использовать:** Когда у вас есть очень большая таблица измерений (например, десятки миллионов клиентов), и большинство аналитических запросов относится только к текущим данным. Разделение таблиц позволяет сделать эти частые запросы очень быстрыми, не жертвуя при этом возможностью проводить глубокий исторический анализ при необходимости.<\/li>\n<li>Преимущества:**\n<ul>\n  <li>Высокая производительность для запросов к текущим данным.<\/li>\n  <li>Логическое разделение данных: актуальные и исторические.<\/li>\n<\/ul>\n<\/li>\n<li>Недостатки:**\n<ul>\n  <li>Усложнение ETL\/ELT процесса, так как нужно управлять двумя таблицами.<\/li>\n  <li>Анализ, требующий одновременного доступа к историческим и текущим данным, усложняется, так как требует `JOIN` или `UNION` между двумя таблицами.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p><b>Пример:<\/b><br \/>\nКлиент Анна Петрова переезжает из Москвы в Санкт-Петербург.<\/p>\n<p>*Таблицы <b>до<\/b> изменений:*<\/p>\n<p>`DimCustomer` (основная таблица)<\/p>\n<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"e2-text-table\">\n<tr>\n<td style=\"text-align: center\">CustomerID<\/td>\n<td style=\"text-align: center\">FullName<\/td>\n<td style=\"text-align: center\">City<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">:---<\/td>\n<td style=\"text-align: center\">:---<\/td>\n<td style=\"text-align: center\">:---<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">101<\/td>\n<td style=\"text-align: right\">Анна Петрова<\/td>\n<td style=\"text-align: center\">Москва<\/td>\n<\/tr>\n<\/table>\n<p>`HistoryCustomer` (историческая таблица) – *пустая*<\/p>\n<p>*Процесс изменения:*<\/p>\n<ol start=\"1\">\n<li><b>Перед<\/b> обновлением основной таблицы, текущая строка (Анна в Москве) копируется в `HistoryCustomer`.<\/li>\n<li>Затем основная таблица `DimCustomer` обновляется новым значением.<\/li>\n<\/ol>\n<p>*Таблицы <b>после<\/b> изменений:*<\/p>\n<p>`DimCustomer` (всегда хранит только актуальные данные)<\/p>\n<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"e2-text-table\">\n<tr>\n<td style=\"text-align: center\">CustomerID<\/td>\n<td style=\"text-align: center\">FullName<\/td>\n<td style=\"text-align: center\">City<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">:---<\/td>\n<td style=\"text-align: center\">:---<\/td>\n<td style=\"text-align: center\">:---<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">101<\/td>\n<td style=\"text-align: right\">Анна Петрова<\/td>\n<td style=\"text-align: center\"><b>Санкт-Петербург<\/b><\/td>\n<\/tr>\n<\/table>\n<p>`HistoryCustomer` (накапливает историю)<\/p>\n<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"e2-text-table\">\n<tr>\n<td style=\"text-align: center\">HistoryID<\/td>\n<td style=\"text-align: right\">CustomerID<\/td>\n<td style=\"text-align: center\">FullName<\/td>\n<td style=\"text-align: center\">City<\/td>\n<td style=\"text-align: center\">StartDate<\/td>\n<td style=\"text-align: center\">EndDate<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">:---<\/td>\n<td style=\"text-align: center\">:---<\/td>\n<td style=\"text-align: left\">:---<\/td>\n<td style=\"text-align: left\">:---<\/td>\n<td style=\"text-align: left\">:---<\/td>\n<td style=\"text-align: left\">:---<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">1<\/td>\n<td style=\"text-align: center\">101<\/td>\n<td>Анна Петрова<\/td>\n<td style=\"text-align: center\"><b>Москва<\/b><\/td>\n<td style=\"text-align: center\">2020-01-15<\/td>\n<td style=\"text-align: center\">2024-08-15<\/td>\n<\/tr>\n<\/table>\n<h4>Тип 5: Гибридный подход (Mini-Dimension + Type 1 Outrigger)<\/h4>\n<p><b>Идея:<\/b> Вынести часто меняющиеся атрибуты из большой таблицы измерений в отдельную “мини-таблицу”, чтобы избежать “раздувания” основной таблицы.<\/p>\n<ul>\n<li>Как работает:**\n<ol start=\"1\">\n  <li>Из основной таблицы измерения (например, `DimCustomer`) выделяется группа атрибутов, которые часто меняются вместе (например, “Тарифный план”, “Статус подписки”).<\/li>\n  <li>Создается отдельная таблица — “мини-измерение” (например, `DimSubscriptionProfile`) — только для этих атрибутов. Эта мини-таблица управляется по <b>SCD Тип 2<\/b> (добавление новой строки для каждого уникального набора значений).<\/li>\n  <li>В основной таблице `DimCustomer` эти атрибуты удаляются, и вместо них добавляется один внешний ключ (например, `SubscriptionProfileKey`), который ссылается на мини-измерение.<\/li>\n  <li>Этот ключ в основной таблице `DimCustomer` обновляется по принципу <b>SCD Тип 1<\/b> (просто перезаписывается), указывая на *актуальную* запись в мини-измерении.<\/li>\n<\/ol>\n<\/li>\n<li>Когда использовать:** В очень больших (широких и\/или с большим количеством строк) таблицах измерений, где лишь небольшая группа атрибутов меняется относительно часто. Это позволяет отслеживать историю этих атрибутов, не создавая новую многомиллионную запись в основной таблице при каждом изменении.<\/li>\n<li>Преимущества:**\n<ul>\n  <li>Экономия места и контроль над ростом основной таблицы измерения.<\/li>\n  <li>Позволяет вести детальную историю для подгруппы атрибутов.<\/li>\n<\/ul>\n<\/li>\n<li>Недостатки:**\n<ul>\n  <li>Более сложная модель данных, требующая дополнительных `JOIN`.<\/li>\n  <li>Может быть сложнее для понимания конечными пользователями.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p><b>Пример:<\/b><br \/>\nКлиент Иван меняет свой тарифный план.<\/p>\n<p>*Таблицы <b>до<\/b> изменений:*<\/p>\n<p>`DimCustomer`<\/p>\n<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"e2-text-table\">\n<tr>\n<td style=\"text-align: right\">CustomerKey<\/td>\n<td style=\"text-align: center\">FullName<\/td>\n<td style=\"text-align: center\">SubscriptionProfileKey<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">:---<\/td>\n<td style=\"text-align: left\">:---<\/td>\n<td style=\"text-align: center\">:---<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">202<\/td>\n<td style=\"text-align: center\">Иван Иванов<\/td>\n<td style=\"text-align: center\">55<\/td>\n<\/tr>\n<\/table>\n<p>`DimSubscriptionProfile` (мини-измерение, управляется по SCD2)<\/p>\n<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"e2-text-table\">\n<tr>\n<td style=\"text-align: center\">ProfileKey<\/td>\n<td style=\"text-align: center\">Plan<\/td>\n<td style=\"text-align: center\">Status<\/td>\n<td style=\"text-align: center\">IsCurrent<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">:---<\/td>\n<td style=\"text-align: left\">:---<\/td>\n<td style=\"text-align: left\">:---<\/td>\n<td style=\"text-align: left\">:---<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>55<\/b><\/td>\n<td style=\"text-align: center\">Basic<\/td>\n<td style=\"text-align: center\">Active<\/td>\n<td style=\"text-align: center\">Yes<\/td>\n<\/tr>\n<\/table>\n<p>*Процесс изменения:* Иван переходит на план “Premium”.<\/p>\n<ol start=\"1\">\n<li>В `DimSubscriptionProfile` добавляется новая строка для “Premium”, а старая помечается как неактуальная.<\/li>\n<li>В `DimCustomer` у Ивана обновляется ключ `SubscriptionProfileKey`.<\/li>\n<\/ol>\n<p>*Таблицы <b>после<\/b> изменений:*<\/p>\n<p>`DimCustomer` (здесь изменился только ключ)<\/p>\n<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"e2-text-table\">\n<tr>\n<td style=\"text-align: right\">CustomerKey<\/td>\n<td style=\"text-align: center\">FullName<\/td>\n<td style=\"text-align: center\">SubscriptionProfileKey<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">:---<\/td>\n<td style=\"text-align: center\">:---<\/td>\n<td style=\"text-align: center\">:---<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">202<\/td>\n<td style=\"text-align: center\">Иван Иванов<\/td>\n<td style=\"text-align: center\"><b>56<\/b><\/td>\n<\/tr>\n<\/table>\n<p>`DimSubscriptionProfile` (здесь хранится вся история)<\/p>\n<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"e2-text-table\">\n<tr>\n<td style=\"text-align: center\">ProfileKey<\/td>\n<td style=\"text-align: center\">Plan<\/td>\n<td style=\"text-align: center\">Status<\/td>\n<td style=\"text-align: center\">IsCurrent<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">:---<\/td>\n<td style=\"text-align: left\">:---<\/td>\n<td style=\"text-align: left\">:---<\/td>\n<td style=\"text-align: left\">:---<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">55<\/td>\n<td style=\"text-align: center\">Basic<\/td>\n<td style=\"text-align: center\">Active<\/td>\n<td style=\"text-align: center\">No<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>56<\/b><\/td>\n<td style=\"text-align: center\"><b>Premium<\/b><\/td>\n<td style=\"text-align: center\"><b>Active<\/b><\/td>\n<td style=\"text-align: center\"><b>Yes<\/b><\/td>\n<\/tr>\n<\/table>\n<h4>Тип 6: Гибридный (Комбинация Типа 1, 2 и 3)<\/h4>\n<p><b>Идея:<\/b> Обеспечить максимальную гибкость для анализа, объединив сильные стороны трех основных типов в одной таблице.<\/p>\n<ul>\n<li>Как работает:<b> Этот тип строится на основе **SCD Тип 2<\/b> (добавление новой строки для истории), но с добавлением атрибутов из <b>SCD Тип 1<\/b> (перезапись) для упрощения некоторых запросов.\n<ul>\n  <li>Основная структура — это SCD Тип 2: есть строки для каждой исторической версии с полями `StartDate`, `EndDate` и `IsCurrent`. Поле атрибута (например, `City`) хранит значение, актуальное на тот исторический период.<\/li>\n  <li>Дополнительно в таблицу добавляется столбец `CurrentCity`. Этот столбец для *всех* записей одного клиента (и исторических, и текущей) всегда хранит <b>актуальное на данный момент<\/b> значение (поведение SCD Тип 1).<\/li>\n<\/ul>\n<\/li>\n<li>Когда использовать:** Когда аналитикам часто нужно отвечать на два типа вопросов:\n<ol start=\"1\">\n  <li>“Каким был город клиента на момент продажи?” (Используется историческое поле `City`).<\/li>\n  <li>“Каковы продажи всем клиентам, которые *сейчас* живут в Москве, за всю историю?” (Используется поле `CurrentCity` для фильтрации).<\/li>\n<\/ol>\n<\/li>\n<li>Преимущества:**\n<ul>\n  <li>Невероятная гибкость анализа без сложных `JOIN` или подзапросов для определения текущего состояния.<\/li>\n<\/ul>\n<\/li>\n<li>Недостатки:**\n<ul>\n  <li>Усложнение ETL\/ELT. При изменении адреса нужно не только создать новую строку и закрыть старую, но и обновить поле `CurrentCity` во <b>всех<\/b> предыдущих строках для этого клиента. Это может быть ресурсозатратно.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p><b>Пример:<\/b><br \/>\nСнова Анна, переезжающая из Москвы в Санкт-Петербург.<\/p>\n<p>*Таблица `DimCustomer` <b>до<\/b> изменений:*<\/p>\n<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"e2-text-table\">\n<tr>\n<td style=\"text-align: center\">SurrogateKey<\/td>\n<td style=\"text-align: center\">CustomerID<\/td>\n<td style=\"text-align: center\">City<\/td>\n<td style=\"text-align: center\">CurrentCity<\/td>\n<td style=\"text-align: center\">StartDate<\/td>\n<td style=\"text-align: center\">EndDate<\/td>\n<td style=\"text-align: center\">IsCurrent<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">:---<\/td>\n<td style=\"text-align: left\">:---<\/td>\n<td style=\"text-align: left\">:---<\/td>\n<td style=\"text-align: left\">:---<\/td>\n<td style=\"text-align: left\">:---<\/td>\n<td style=\"text-align: left\">:---<\/td>\n<td style=\"text-align: left\">:---<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">1<\/td>\n<td style=\"text-align: center\">101<\/td>\n<td style=\"text-align: center\">Москва<\/td>\n<td style=\"text-align: center\">Москва<\/td>\n<td style=\"text-align: center\">2020-01-15<\/td>\n<td style=\"text-align: center\">NULL<\/td>\n<td style=\"text-align: center\">Yes<\/td>\n<\/tr>\n<\/table>\n<p>*Процесс изменения:*<\/p>\n<ol start=\"1\">\n<li>Старая строка “закрывается” (обновляется `EndDate`, `IsCurrent` = ‘No’).<\/li>\n<li>Создается новая актуальная строка.<\/li>\n<li>Во <b>всех строках<\/b> для CustomerID=101 поле `CurrentCity` обновляется до “Санкт-Петербург”.<\/li>\n<\/ol>\n<p>*Таблица `DimCustomer` <b>после<\/b> изменений:*<\/p>\n<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"e2-text-table\">\n<tr>\n<td style=\"text-align: center\">SurrogateKey<\/td>\n<td style=\"text-align: center\">CustomerID<\/td>\n<td style=\"text-align: center\">City<\/td>\n<td style=\"text-align: center\">CurrentCity<\/td>\n<td style=\"text-align: center\">StartDate<\/td>\n<td style=\"text-align: center\">EndDate<\/td>\n<td style=\"text-align: center\">IsCurrent<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">:---<\/td>\n<td style=\"text-align: left\">:---<\/td>\n<td style=\"text-align: left\">:---<\/td>\n<td style=\"text-align: left\">:---<\/td>\n<td style=\"text-align: left\">:---<\/td>\n<td style=\"text-align: left\">:---<\/td>\n<td style=\"text-align: left\">:---<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">1<\/td>\n<td style=\"text-align: center\">101<\/td>\n<td style=\"text-align: center\"><b>Москва<\/b><\/td>\n<td style=\"text-align: center\"><b>Санкт-Петербург<\/b><\/td>\n<td style=\"text-align: center\">2020-01-15<\/td>\n<td style=\"text-align: center\">2024-08-15<\/td>\n<td style=\"text-align: center\">No<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">2<\/td>\n<td style=\"text-align: center\">101<\/td>\n<td style=\"text-align: center\"><b>Санкт-Петербург<\/b><\/td>\n<td style=\"text-align: center\"><b>Санкт-Петербург<\/b><\/td>\n<td style=\"text-align: center\">2024-08-16<\/td>\n<td style=\"text-align: center\">NULL<\/td>\n<td style=\"text-align: center\">Yes<\/td>\n<\/tr>\n<\/table>\n<p>Теперь можно легко отфильтровать по `City` для исторического анализа или по `CurrentCity` для анализа в разрезе текущего состояния.<\/p>\n<h4>Ссылки для дальнейшего изучения<\/h4>\n<ul>\n<li>Microsoft Fabric:** Slowly changing dimension type 2 <a href=\"https:\/\/learn.microsoft.com\/en-us\/fabric\/data-factory\/slowly-changing-dimension-type-two\">https:\/\/learn.microsoft.com\/en-us\/fabric\/data-factory\/slowly-changing-dimension-type-two<\/a> — Хорошее описание и пример реализации SCD Тип 2.<\/li>\n<li>DataCamp:** Mastering Slowly Changing Dimensions (SCD) <a href=\"https:\/\/www.datacamp.com\/tutorial\/mastering-slowly-changing-dimensions-scd\">https:\/\/www.datacamp.com\/tutorial\/mastering-slowly-changing-dimensions-scd<\/a> — Комплексный учебник по основным типам SCD.<\/li>\n<li>HevoData:** Slowly Changing Dimensions(SCD): Types with Examples <a href=\"https:\/\/hevodata.com\/learn\/slowly-changing-dimensions\/\">https:\/\/hevodata.com\/learn\/slowly-changing-dimensions\/<\/a> — Детальное объяснение всех основных типов с примерами.<\/li>\n<li>ThoughtSpot:** Slowly Changing Dimensions (SCD): 4 Types & How to ...<a href=\"https:\/\/www.thoughtspot.com\/data-trends\/data-modeling\/slowly-changing-dimensions-in-data-warehouse\">https:\/\/www.thoughtspot.com\/data-trends\/data-modeling\/slowly-changing-dimensions-in-data-warehouse<\/a> — Еще один ресурс с обзором и сравнением типов SCD.<\/li>\n<\/ul>\n<p>Идея: Концептуальная архитектура: SCD на стеке Lakehouse + Data Mesh + dbt<\/p>\n<p>Основная идея заключается в создании надежных, версионируемых и децентрализованных “продуктов данных”, одним из которых является таблица измерений с полной историей (SCD). (Автоматическая)<\/p>\n<p>Вот как компоненты взаимодействуют друг с другом:<\/p>\n<ol start=\"1\">\n<li><b>Lakehouse (Основа):<\/b> Это наша физическая среда. Мы используем открытое озеро данных (например, S3, ADLS) для хранения, а поверх него — табличный формат <b>Apache Iceberg<\/b>. Iceberg предоставляет нам ACID-транзакции, эволюцию схемы и, что самое важное для SCD, атомарные и эффективные операции `MERGE` (`UPDATE`\/`INSERT`\/`DELETE`) на уровне строк прямо в озере данных.<\/li>\n<\/ol>\n<ol start=\"2\">\n<li><b>Data Mesh (Философия организации):<\/b> Вместо централизованной команды данных, мы принимаем философию Data Mesh. A “Команда домена Клиенты” несет полную ответственность за все данные, связанные с клиентами. Их задача — предоставить остальной компании высококачественный продукт данных под названием `dim_customers`. Этот продукт должен включать полную историю изменений (SCD Type 2).<\/li>\n<\/ol>\n<ol start=\"3\">\n<li><b>ETL\/ELT (Процесс):<\/b> Это конвейер, по которому данные текут от источника к потребителю.\n<ul>\n  <li>Extract & Load:<b> Исходные данные (например, изменения в базе данных клиентов) захватываются с помощью CDC (Change Data Capture) инструментов типа Debezium и попадают в **Kafka<\/b>. Оттуда они загружаются (Load) в “бронзовый” слой нашего Lakehouse (в сыром виде, в таблицы Iceberg).<\/li>\n  <li>Transform:<b> Здесь в игру вступает **dbt<\/b>. Команда домена использует `dbt` для преобразования сырых данных из бронзового слоя в готовую к использованию модель в “серебряном” слое — нашу таблицу `dim_customers`.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<ol start=\"4\">\n<li><b>dbt (Инструмент автоматизации SCD):<\/b> `dbt` является сердцем автоматизации. Он не просто выполняет SQL-скрипты. У него есть встроенный функционал для реализации SCD Type 2, который называется <b>`Snapshots`<\/b>.<\/li>\n<\/ol>\n<p>---<\/p>\n<h4>Сценарий 1: Автоматическое формирование SCD с помощью `dbt snapshots`<\/h4>\n<p>Это наиболее распространенный, надежный и идиоматический способ реализации идеи.<\/p>\n<p><b>Как это работает:<\/b><\/p>\n<ol start=\"1\">\n<li><b>Источник:<\/b> У нас есть “бронзовая” таблица `bronze_customers`, которая содержит текущее состояние всех клиентов. Эта таблица обновляется периодически (например, раз в час) новыми данными из Kafka.<\/li>\n<li><b>dbt Snapshot:<\/b> В проекте `dbt` команда домена создает файл “снэпшота” (`snapshot\/customers_snapshot.sql`). Внутри него описывается, как `dbt` должен отслеживать изменения.<\/li>\n<\/ol>\n<pre class=\"e2-text-code\"><code class=\"\">{% snapshot customers_snapshot %}\n\n    {{\n        config(\n          target_schema='silver',\n          unique_key='customer_id',\n          strategy='check',\n          check_cols=['address', 'email', 'phone_number'],\n          updated_at='last_modified_at',\n        )\n    }}\n\n    select * from {{ source('bronze', 'customers') }}\n\n    {% endsnapshot %}<\/code><\/pre><ol start=\"3\">\n<li><b>Автоматизация:<\/b> Оркестратор (например, Airflow) запускает команду `dbt snapshot` по расписанию.<\/li>\n<li><b>Что делает dbt “под капотом”:<\/b>\n<ul>\n  <li>Он сравнивает записи из исходной таблицы (`bronze_customers`) с текущими записями в целевой таблице (`silver.customers_snapshot`).<\/li>\n  <li>Используя `unique_key` (`customer_id`), он находит совпадающие записи.<\/li>\n  <li>С помощью стратегии `check` он проверяет, изменилось ли значение в любом из столбцов, перечисленных в `check_cols`.<\/li>\n  <li>Если изменение обнаружено:\n<ul>\n    <li>Он обновляет старую запись в целевой таблице, проставляя ей дату окончания актуальности (`dbt_valid_to`).<\/li>\n    <li>Он вставляет новую строку с обновленными данными и датой начала актуальности (`dbt_valid_from`).<\/li>\n  <\/ul>\n<\/li>\n  <li>`dbt` генерирует одну атомарную операцию `MERGE` для таблицы Iceberg, которая эффективно выполняет все эти обновления и вставки за одну транзакцию.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<p><b>Результат:<\/b> В `silver.customers_snapshot` мы получаем идеальную таблицу SCD Type 2, которая обновляется автоматически и надежно, без написания сложной логики `MERGE` вручную.<\/p>\n",
            "date_published": "2025-08-16T23:24:59+03:00",
            "date_modified": "2025-08-16T23:28:48+03:00",
            "tags": [
                "Data Engineer",
                "Data Governance",
                "dbt"
            ],
            "image": "https:\/\/gavrilov.info\/pictures\/opisanie-paterna-slowly-changing-dimensions-scd.png",
            "_date_published_rfc2822": "Sat, 16 Aug 2025 23:24:59 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "267",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": [
                    "https:\/\/gavrilov.info\/pictures\/opisanie-paterna-slowly-changing-dimensions-scd.png"
                ]
            }
        }
    ],
    "_e2_version": 4171,
    "_e2_ua_string": "Aegea 11.4 (v4171e)"
}