Вступ
dbt є потужним фреймворком, який включає в себе дві популярні мови: SQL + Python.
За допомогою dbt можна створювати різні «шари» даних або виділити dbt тільки під один шар, наприклад dm.
За допомогою зрозумілого і всім відомого SQL інтерфейсу можна створювати консистентні моделі, якими зможуть користуватися і розуміти всі учасники data-команди. Ну, також, на мою думку, плюс dbt – можливість дати аналітикам самостійно створювати dm шар зі своєю логікою, що звільняє ресурс data-команди. Але варто мати на увазі, що потрібен хороший процес доставки коду: Code Review, патерни, лінтери, принципи та інше.
Варто також згадати, що сервіс може працювати як окремий інстанс через докер, так і пітон пакетом. Всі подальші приклади я буду проводити через пітон пакет.
Ініціалізація проекту
На початку піднімемо потрібні нам сервіси, використовуючи команду: docker-compose up -d
Після цього давайте налаштуємо локальне Python-середовище для подальшої роботи:
python3.12 -m venv venv && \
source venv/bin/activate && \
pip install --upgrade pip && \
pip install poetry && \
poetry lock && \
poetry install
Після того, як ми встановили всі залежності, ми можемо перевірити версію dbt командою: dbt --version.
Наступним етапом при роботі з dbt є ініціалізація проекту, тому виконаємо команду: dbt init pg_analytics --profiles-dir . і вкажемо всі необхідні параметри, які він буде запитувати під час створення проекту. Я назвав наш проект: pg_analytics.
Важливо: При ініціалізації проекту ви можете не вказувати параметр --profiles-dir.
Якщо не вказати --profiles-dir, то при ініціалізації проекту у нас буде створено глобальний profiles.yml, який буде зберігатися в ~/.dbt/.
Але для демонстрації ми не будемо його використовувати, а скористаємося локальним profiles.yml всередині самого проекту. Тому ми виконаємо команду dbt init pg_analytics --profiles-dir . щоб створити profiles.yml всередині нашого проекту.
Важливо: рекомендується створювати profiles.yml поза проектом, щоб усі параметри для підключення тримати поза проектом, а також profiles.yml міститиме інформацію по всіх проектах, якщо у вас їх більше одного.
Після ініціалізації ми отримаємо наступний profiles.yml:
pg_analytics:
outputs:
dev:
dbname: dbt
host: localhost
pass: postgres
port: 5432
schema: dbt
threads: 4
type: postgres
user: postgres
target: dev
Перевірити з’єднання можна командою dbt debug (попередньо зайшовши в проект pg_analytics).
Також варто звернути увагу, що коли ми створили проект, то він містить стандартний набір каталогів:
- analyses
- logs
- macros
- models
- seeds
- snapshots
- target
- tests
Більш детально про логіку та роботу директорій ви можете дізнатися з документації. Нижче ми розглянемо частину з них на прикладах.
І файлів:
- dbt_project.yml – документація.
- profiles.yml – документація.
Стандартні приклади dbt
Всі налаштування завершені, тому ми можемо запустити наш проект. Спочатку у нас в папці models зберігаються моделі, які будуть створені спочатку. На них перевіримо коректність роботи dbt.
Для цього виконаємо команду: dbt run і побачимо логи нашої команди:
08:01:36 Running with dbt=1.8.8
08:01:36 Registered adapter: postgres=1.8.2
08:01:37 Unable to do partial parsing because saved manifest not found. Starting full parse.
08:01:37 Found 2 models, 4 data tests, 423 macros
08:01:37
08:01:37 Concurrency: 4 threads (target='dev')
08:01:37
08:01:37 1 of 2 START sql table model dbt.my_first_dbt_model ............................ [RUN]
08:01:37 1 of 2 OK created sql table model dbt.my_first_dbt_model ....................... [SELECT 2 in 0.07s]
08:01:37 2 of 2 START sql view model dbt.my_second_dbt_model ............................ [RUN]
08:01:37 2 of 2 OK created sql view model dbt.my_second_dbt_model ....................... [CREATE VIEW in 0.06s]
08:01:37
08:01:37 Finished running 1 table model, 1 view model in 0 hours 0 minutes and 0.27 seconds (0.27s).
08:01:37
08:01:37 Completed successfully
08:01:37
08:01:37 Done. PASS=2 WARN=0 ERROR=0 SKIP=0 TOTAL=2
І якщо зараз підключитися до нашого PostgreSQL, то ми зможемо побачити, що у нас створено схему dbt, а в ній — таблицю my_first_dbt_model і представлення my_second_dbt_model.
Також ми можемо «скомпілювати» наші моделі і відразу провести тестування.
А зараз давайте виконаємо команду dbt build і ми побачимо, що у нас частина процесів не пройшла, тому що один з тестів провалений.
Ми бачимо, що у нас провалений етап not_null_my_first_dbt_model_id. Щоб це виправити, приберемо коментар у моделі where id is not null і якщо заново виконати build проекту, то у нас пройдуть всі тести і всі етапи.
Практика
Все, ми переконалися, що наш dbt працює, тому давайте створимо нашу першу модель.
Але на початку нам необхідно запустити файл create_and_fill_users.py з папки py_files.
Після створення і заповнення таблиці ми можемо над нею працювати за допомогою dbt-моделей.
Для цього нам потрібно створити нову папку dm, яка буде містити dm-таблиці, потім створимо fct_registred_by_date.sql, в якому напишемо простий запит:
SELECT
created_at::date AS registred_date,
count(*)
FROM
users AS u
GROUP BY
1
Важливо: Як ви назвете файл, так і буде називатися ваша таблиця, тому вибирайте правильно.
Варто зазначити, що основним dbt-проектом є dbt_project.yml і в ньому вказується вся інформація по проекту: налаштування компіляції, матеріалізація таблиць, які папки включати тощо.
І оскільки ми додали нову директорію dm, то її необхідно додати в проект, щоб моделі всередині директорії запускалися.
Для цього необхідно в dbt_project.yml додати нашу директорію і вказати, як ми хочемо бачити наші моделі в БД. Я зроблю таблиці з рівнем матеріалізації table:
...
dm:
+materialized: table
Додатково ми можемо прописати метадані для нашої моделі, щоб у подальшому було зручніше орієнтуватися в ній.
Для цього створимо файл schema.yaml:
models:
- name: fct_registred_by_date
description: "Кількість реєстрацій за день"
columns:
- name: registred_date
description: "Дата реєстрації"
- name: count
description: "Кількість реєстрацій"
Важливо: Якщо ви неправильно вкажете ім’я стовпця, наприклад замість name вкажіть namefoo, то у вас не відобразиться жодних винятків.
Тепер якщо виконати команду dbt run, то отримаємо таке повідомлення:
...
OK created sql table model dbt.fct_registred_by_date ...[SELECT 365 in 0.37s]
...
І тепер наша таблиця fct_registred_by_date готова до використання.
Варто звернути увагу, що таблиця заповнила значення за поточний момент і якщо в таблиці users з’являться нові записи, то ми в нашій вітрині цього не побачимо. Тому все це потрібно оркеструвати.
Оркестрування доступне в dbt Cloud або сторонніми рішеннями. Одним із популярних рішень є об’єднання dbt + Airflow.
Тепер давайте подивимося, як буде працювати модель, якщо в таблиці users з’являться нові записи.
Для цього виконайте файл add_new_month.py і у нас з’являться записи за січень 2025-го.
І якщо після цього знову виконати команду dbt run, то отримаємо таке повідомлення:
...
OK created sql table model dbt.fct_registred_by_date ... [SELECT 396 in 0.08s]
...
Важливо: він перезаписує всю таблицю, надалі ми заповнимо відсутні дані.
Для того, щоб виключити повне перезаписування вітрини в dbt існує формат матеріалізації incremental. Давайте ним скористаємося і створимо для цього новий файл fct_registred_by_date_incremental.sql:
{{
config(
materialized='incremental',
unique_key='registred_date'
)
}}
SELECT
created_at::date AS registred_date,
count(*)
FROM
users AS u
{% if is_incremental() %}
WHERE created_at >= (SELECT coalesce(max(registred_date), '1900-01-01') FROM {{ this }})
{% endif %}
GROUP BY
1
На що тут варто звернути увагу:
- Раніше в dbt_project.yml ми вказали, що всі моделі в директорії dm будуть створюватися як table. Але в даному випадку ми перевизначаємо основний config на incremental.
- Для incremental є обов’язковим зазначенням unique_key, за яким він якраз і буде виконувати вставки.
Тепер, якщо ми повторимо дії над таблицею users, як раніше, то отримаємо коректний результат. Наша таблиця після кожного виконання команди dbt run буде інкрементально заповнюватися.
Більше подробиць і тонкощів інкрементальної матеріалізації описано в документації.
Source
Для коректного звернення до джерел давайте їх пропишемо в нашій папці models, щоб ми могли на них посилатися через Jinja-шаблони, а не прописуючи їх самостійно.
Для цього необхідно створити файл source.yml і в ньому прописати конфігурацію для джерел:
version: 2
sources:
- name: public
database: dbt
schema: public
tables:
- name: users
А тепер спробуємо скористатися цим джерелом, щоб скопіювати таблицю users до своєї схеми dbt.
Для цього створимо файл dbt_source_users.sql у новій директорії as_is всередині models і додамо такий код:
SELECT * FROM {{ source("public", "users") }}
Цей код на перший погляд може здатися незрозумілим, але зараз ми його скомпілюємо і отримаємо звичний нам SQL.
Давайте виконаємо команду: dbt compile і якщо у нас все буде добре, то в папці target ми зможемо знайти наш скомпільований файл, який виглядає наступним чином:
SELECT * FROM "dbt"."public"."users"
Якщо ми візьмемо цей запит і виконаємо його в нашій БД, то отримаємо коректний результат. Тепер ми можемо запустити цю модель в БД, щоб вона виконала закладену логіку.
Важливо: компілятор викличе виняток і вкаже на помилку, якщо ваш запит складений некоректно.
Виконаємо команду: dbt run і побачимо, що у нас створилося представлення (view), а це тому, що ми не вказали, як матеріалізувати наші моделі з папки as_is, давайте вкажемо в dbt_project.yml, за якою логікою повинна працювати наша модель:
as_is:
+materialized: table
І якщо ще раз виконати команду dbt run, то ми побачимо, що у нас створилася таблиця, а не представлення (в логах буде вказано SELECT *** а не CREATE VIEW).
Надалі всі наші джерела, які будуть використані під час проекту, будемо вказувати всередині source.yml
Snapshots
Для аналітики досить важливим моментом є «подорож у часі». Що це означає? Це означає, що аналітик бажає подивитися, який у нас був стан за користувачами/продажами/іншим на той чи інший момент.
Тому наше завдання як дата-інженерів організувати таку можливість, і її можна реалізувати за допомогою SCD2.
При ініціалізації проекту у нас створилася папка snapshots, і ми в ній будемо створювати нашу таблицю SCD2.
На початку нам потрібно створити файл users_scd2.sql:
{% snapshot users_timestamp %}
{{
config(
target_database='dbt',
target_schema='snapshots',
strategy='timestamp',
unique_key='id',
updated_at='updated_at',
)
}}
SELECT * FROM {{ source('public', 'users') }}
{% endsnapshot %}
Тут ми також використовуємо Jinja-шаблони:
- Вказуємо, що це таблиця типу snapshot
- Вказуємо всі необхідні атрибути для роботи з таблицею.
- Вказуємо запит на витяг даних з таблиці. Тут також використовуємо наш source, який ми визначили раніше.
Після створення цього файлу ми можемо виконати команду: dbt snapshot і побачимо в лозі SELECT *** – це означає, що таблиця створена і перенесла всі записи з таблиці users.
Давайте тепер змінимо якийсь запис, щоб побачити роботу SCD2.
Оскільки в полі у нас зберігається uuid і він щоразу різний, ви можете вибрати будь-якого «користувача» для зміни.
UPDATE users SET (first_name, updated_at) = ('dummy', now())
WHERE id = '86433147-fafe-4d74-b67c-59c3a283974c'
Ми змінили ім’я користувача і щоб створити новий запис у таблиці SCD, необхідно знову виконати команду: dbt snapshot. Після виконання ми побачимо, що у нас додався новий рядок у таблицю:
| id | created_at | updated_at | first_name | dbt_scd_id | dbt_updated_at | dbt_valid_from | dbt_valid_to |
| 86433147-fafe-4d74-b67c-59c3a283974c | 2024-08-16 17:31:30.670 +0000 | 2024-02-29 14:53:29.191 +0000 | Аліна | 0cea94932fa3921c59e771f5c0b53bb4 | 2024-02-29 14:53:29.191 +0000 | 2024-02-29 14:53:29.191 +0000 | 2024-10-26 03:07:44.519 +0000 |
| 86433147-fafe-4d74-b67c-59c3a283974c | 2024-08-16 17:31:30.670 +0000 | 2024-10-26 03:07:44.519 +0000 | dummy | cf61eb52bac629a0af70cd2c46e63f8a | 2024-10-26 03:07:44.519 +0000 | 2024-10-26 03:07:44.519 +0000 |
Тепер наша таблиця users буде версіонуватися після кожного запуску команди dbt snapshot.
Більш детально про можливості snapshots описано в офіційній документації.
Функція ref
Ref допомагає нам вибудувати data lineage, для того щоб він коректно відображався на графіку і також ми могли правильно запускати/перезапускати наші моделі.
Важливим моментом тут є те, що ми повинні «затягнути» до себе в проект якусь модель, щоб нею надалі користуватися.
Основна відмінність від source тут у тому, що source – це просто посилання на об’єкт у БД, а ref – це посилання на об’єкт всередині dbt-проекту.
Давайте розглянемо це на прикладі і для цього створимо файл count_users_by_city.sql в папці dm з наступним кодом:
SELECT
city,
count(*)
FROM
{{ ref('dbt_source_users') }}
GROUP BY
1
У цьому прикладі ми використовуємо раніше створену таблицю dbt_source_users з користувачами. Якщо ми захочемо звернутися спочатку до нашої таблиці, вказавши {{ ref(“users”) }}, то отримаємо виняток:
Compilation Error
Model 'model.pg_analytics.count_users_by_city' (models/dm/count_users_by_city.sql) depends on a node named 'users' which was not found
Важливо: Під час роботи з ref ми повинні використовувати тільки dbt-моделі.
Більш детально про ref функцію описано в документації.
WEB UI
Ми зараз з вами побудували кілька моделей і тепер ми можемо подивитися взаємозв’язки між ними.
Для цього нам спочатку потрібно виконати команду: dbt docs generate, яка вибудує залежності між моделями і створить документацію по нашому проекту.
Для того, щоб запустити наш веб-сервер, необхідно виконати команду: dbt docs serve. Після цього сторінка відкриється автоматично, а якщо не відкрилася, то буде доступна за адресою: http://localhost:8080/#!/overview
Після запуску нашого сервера ми можемо вивчити список взаємозв’язків у проекті. Що ми маємо зараз:
Тут варто звернути увагу на висячу ноду fct_registred_by_date, тому що вона у нас була створена таким запитом:
SELECT
created_at::date AS registred_date,
count(*)
FROM
users AS u
GROUP BY
1
І як ми бачимо в даному запиті, у нас не використовується ні source, ні ref. Тому в разі дебагування або перевірки залежностей потрібно буде самостійно вивчати dbt-моделі.
При роботі з графом залежностей часто говорять про запуск моделі та її залежностей. У dbt це реалізовано досить гнучко. Про що описано в документації.
Але з основного я б зазначив, що ми можемо регулювати, що запускати при запуску моделі. Наприклад:
- Запустити саму модель: dbt run -m name_of_model
- Запустити саму модель і залежності ДО: dbt run -m +name_of_model
- Запустити саму модель і залежності ПІСЛЯ: dbt run -m name_of_model+
- Запустити саму модель і залежності ДО і ПІСЛЯ: dbt run -m +name_of_model+
Теги
Крім поділу за папками, ми також можемо додати поділ за тегами. Для цього необхідно всередині головного конфіг-файлу dbt_project.yml задати теги:
models:
pg_analytics:
example:
+materialized: view
dm:
+materialized: table
tags: "marts"
as_is:
+materialized: table
І тепер завдяки такому налаштуванню ми можемо запускати моделі за тегами такою командою: dbt run -m tag:marts
Більш детально про роботу тегів описано в документації.
Тести
Тести – це невід’ємна частина в дата-інженерії та аналітиці. Наше завдання – зробити так, щоб даним могли довіряти.
І один із способів забезпечити довіру до даних – це покрити їх тестами і перевірками.
Почнемо зі стандартних тестів, які надає dbt «з коробки»:
- unique: перевіряє, що значення повинні бути унікальними, без дублікатів.
- not_null: перевіряє, що в стовпці немає значень NULL.
- accepted_values: перевіряє, що стовпець містить потрібні значення. Наприклад: для стовпця orders повинні бути значення: “placed”, “shipped”, “completed”, ‘returned‘
- relationships: Перевіряє, що зовнішній ключ цілісний і послідовний, і що у нас у вітрині або іншій моделі не з’явилося «нових» ключів.
Всі тести вказуються для колонок, наприклад так можна вказати тести для нашої вітрини fct_registred_by_date:
version: 2
models:
- name: fct_registred_by_date
description: "Кількість реєстрацій за день"
columns:
- name: registred_date
description: "Дата реєстрації"
data_tests:
- unique
- not_null
- name: count
description: "Кількість реєстрацій"
Всі тести вказуються в schema.yml для потрібної нам моделі і потрібної колонки
Але крім стандартних тестів ми можемо самостійно писати тести для наших моделей. Основна логіка dbt тестів – запит нічого не повинен повертати. Якщо тест повернув хоча б один рядок, то тест вважається проваленим.
Давайте зробимо свій not_null для колонки count таблиці fct_registred_by_date.
Для цього створимо файл not_null_count_fct_registred_by_date.sql в папці tests з наступним кодом:
SELECT
*
FROM
{{ ref('fct_registred_by_date') }}
WHERE
"count" IS NULL
І якщо ми знову запустимо наші тести командою dbt test, то зможемо побачити, що наш тест запустився і пройшов успішно.
...
test not_null_count_fct_registred_by_date ... [RUN]
...
not_null_count_fct_registred_by_date ... [PASS in 0.06s]
...
Макроси
Макроси – це створення певного шаблону, який ми зможемо використовувати в нашому коді за допомогою Jinja.
Давайте створимо наш перший макрос. Для цього створимо файл cast_to_date.sql у папці macros:
{% macro cast_to_date(column_name) %}
{{ column_name }}::date
{% endmacro %}
А тепер давайте ще раз створимо нашу таблицю fct_registred_by_date, але вже з використанням макросу:
SELECT
{{ cast_to_date('created_at') }} AS registred_date,
count(*)
FROM
users AS u
GROUP BY
1
І якщо зараз ми виконаємо команду: dbt compile, то отримаємо такий скомпільований файл (форматування залишив, як зробив dbt):
SELECT
created_at::date
AS registred_date,
count(*)
FROM
users AS u
GROUP BY
1
Важливо: Не варто створювати надто складні макроси, оскільки їх досить важко дебажити, а також варто враховувати, що макрос буде використовуватися в різних частинах проєктів і при подальших його змінах можна порушити частину бізнес-логіки.
Більш детально про роботу та можливості макросів описано в документації.
Пакети
Для dbt існує пакетний менеджер, як і для Python. З усіма пакетами ви можете ознайомитися на офіційному dbt-hub.
Але ми зараз розглянемо найпопулярніший пакет для dbt — dbt_utils.
Цей пакет надає: нові тести для наших моделей, макроси для створення моделей і різні корисні інструменти для більш комфортної роботи з dbt.
Для того, щоб встановити dbt_utils, необхідно в корені нашого проекту створити файл packages.yml і вказати наступні параметри:
packages:
- package: dbt-labs/dbt_utils
version: 1.3.0
І потім необхідно виконати команду: dbt deps
Тепер ми маємо можливість використовувати функції dbt_utils, давайте розглянемо одну з них – dbt_utils.star.
Для цього прикладу ми продублюємо таблицю fct_registred_by_date. Давайте створимо файл dbt_utils_source_fct_registred_by_date.sql і напишемо такий код:
SELECT
{{ dbt_utils.star(ref("fct_registred_by_date")) }}
FROM
{{ ref("fct_registred_by_date") }}
І якщо зараз виконати команду: dbt compile, то отримаємо такий код:
SELECT
"registred_date",
"count"
FROM
"dbt"."dbt"."fct_registred_by_date"
Він зробив нам перелік всіх стовпців з таблиці, а не використав стандартну команду: *.
Якщо ми хочемо отримувати не всі стовпці, то можна їх виключити наступним чином:
SELECT
{{ dbt_utils.star(ref("fct_registred_by_date"), except=["count"]) }}
FROM
{{ ref("fct_registred_by_date") }}
І після компіляції отримаємо такий код:
SELECT
"registred_date"
FROM
"dbt"."dbt"."fct_registred_by_date"
Зрозуміло, що я наводжу простий приклад, на практиці таблиці можуть мати більше однієї колонки. У даному випадку я хотів продемонструвати можливості dbt_utils, яких безліч.
Інші коннекти
На даний момент dbt має безліч коннектів до різних систем: OLAP БД, OLTP БД, Data Lake.
Більш детально про підключення дата платформ в dbt описано в документації.
Давайте спробуємо створити підключення до DuckDB.
Оскільки dbt не дозволяє тримати кілька різних типів підключень, нам необхідно буде створити новий dbt-проект і для цього виконаємо команду: dbt init duckdb_analytics --profiles-dir .
Після ініціалізації проекту необхідно всередині нього створити profiles.yml:
duckdb_analytics:
outputs:
dev:
type: duckdb
path: dev.duckdb
threads: 4
extensions:
- httpfs
- parquet
- postgres
target: dev
Я також створив папку duckdb_example в models, тому нам необхідно вказати, як матеріалізувати моделі з неї.
...
моделі:
duckdb_analytics:
duckdb_example:
+materialized: view
Після цієї підготовки ми готові створити нашу першу модель з використанням duckdb, для цього створимо файл users_from_pg_extension.sql:
SELECT
*
FROM
postgres_scan(
'host=localhost port=5432 dbname=dbt user=postgres password=postgres',
'public',
'users'
)
Важливо: цей приклад є навчальним. Тому не варто відкрито використовувати параметри для підключення. Їх краще ховати в змінні оточення (environment variables) або проставляти під час CI/CD-процесу. Детальніше про те, як приховати параметри для підключення, описано в документації.
Також варто зазначити, що я в прикладі використовую старий спосіб підключення до PostgreSQL, оскільки новий через ATTACH в dbt не працює. Детальніше про роботу з PostgreSQL через DuckDB описано в документації. І ще можна під себе налаштувати коннект, із зазначенням конкретних плагінів та їх налаштувань, детальніше описано в Configuring dbt-duckdb Plugins.
Оскільки ми створили нашу першу модель, ми можемо робити ref на неї, тому давайте повторимо завдання, яке ми робили в PostgreSQL — порахуємо кількість реєстрацій за кожен день.
Для цього створимо fct_registred_by_date.sql:
SELECT
created_at::date AS registred_date,
count(id) AS count
FROM
{{ ref("users_from_pg_extension") }}
GROUP BY
1
І якщо виконати команду dbt run, то у нас створиться ще одне представлення в DuckDB. І ця інформація також відобразиться в графі:

Давайте тепер попрацюємо з S3 через DuckDB.
Додамо новий сервіс до нашого початкового docker-compose.yaml:
minio:
image: minio/minio:RELEASE.2024-10-13T13-34-11Z
restart: always
command: server /data --console-address ":9001"
volumes:
- ./data:/data
environment:
- MINIO_ROOT_USER=minioadmin
- MINIO_ROOT_PASSWORD=minioadmin
- MINIO_DOMAIN=minio
ports:
- "9000:9000" # MinIO S3 API
- "9001:9001" # MinIO Console
Важливо: створення бакета, генерацію ключів я пропущу, оскільки вона описана у статті вище.
На самому початку нашої роботи S3 + DuckDB необхідно створити параметри для підключення. Тому ми трохи змінимо profiles.yml:
duckdb_analytics:
outputs:
dev:
type: duckdb
path: dev.duckdb
threads: 4
extensions:
- httpfs
- parquet
- postgres
settings:
s3_access_key_id: "GYZDiyIV4wnNoNTzp2Q5"
s3_secret_access_key: "zHOERNJFxcBYXXsa4XUXXSSy0apl8OED7BVp1A3Q"
s3_endpoint: "localhost:9000"
s3_use_ssl: "FALSE"
s3_url_style: "path"
target: dev
Тепер під час роботи DuckDB ми будемо використовувати налаштування, які ми прописали.
Давайте спочатку запишемо користувачів в S3, для цього необхідно виконати скрипт create_and_fill_users_s3.py.
Після цього у нас з’явиться .parquet з користувачами в нашому бакеті prod.
Тепер ми можемо створити першу модель, яка буде читати файл з S3, створимо файл users_from_s3_extension.sql:
SELECT * FROM read_parquet('s3://prod/ods/users.parquet')
Якщо виконати команду dbt run, то в нашій БД буде створено представлення.
Тепер давайте виконаємо наш приклад з підрахунком реєстрацій і для цього створимо модель fct_registred_by_date_from_s3.sql:
{{ config(materialized=“table”) }}
SELECT
created_at::date AS registred_date,
count(id) AS count
FROM
{{ ref(«users_from_s3_extension») }}
GROUP BY
1
Важливо: Я змінив основний конфіг матеріалізації на таблицю, щоб перевірити коректність роботи моделі.
Варто також зазначити, що коли ми створюємо таблиці/представлення за допомогою dbt, ті налаштування, які ми вказали в profiles.yml, створюються на сесію.
Це означає, що якщо ми підключимося до нашої фізичної dev.duckdb і спробуємо виконати запит до S3, у нас не буде працювати з’єднання, тому що ми створили нову сесію і в ній потрібно заново ініціалізувати підключення до S3.
І тепер після створення всіх потрібних нам моделей ми можемо виконати dbt run і підключитися до DuckDB, щоб побачити результат.
А також всі наші моделі відображаються в графі залежностей:

Також цей пакет дозволяє писати в S3. Для того щоб зробити запис в S3, необхідно звернутися до документації – Writing to external files. Запис у зовнішні таблиці має специфічний синтаксис, про який варто знати.
Давайте запишемо результат нашого звичного запиту в S3 і для цього створимо файл fct_registred_by_date_from_s3_to_s3.sql:
{{ config(materialized='external', location='s3://prod/dm/fct_registred_date.gzip.parquet') }}
SELECT
created_at::date AS registred_date,
count(id) AS count
FROM
{{ ref("users_from_s3_extension") }}
GROUP BY
1
І тепер, крім того, що ми зробили запис в S3, це ще й коректно відображається в нашому графіку:

Важливо: я показав тільки приклад використання dbt + DuckDB, але таких інтеграцій може бути безліч: ClickHouse, MySQL та інші коннектори.
Висновок
Як я сказав на початку – dbt є потужним інструментом.
Я постарався показати основні моменти, але в dbt є безліч тонкощів і особливостей роботи, з якими ви зможете ознайомитися на практиці.
dbt також є досить популярним інструментом, ви його можете знайти у багатьох компаніях. Тому знання dbt вам точно знадобиться.
На що я б звернув увагу:
- Необхідно організувати зберігання моделей так, щоб було зручно всім учасникам data-команди, але й було зрозуміло, як відбувається поділ моделей (за шарами, за логікою тощо).
- Не варто уникати тегів при роботі з моделями.
- Заповнюйте метадані за моделями відразу.
- Визначте власника метаданих, щоб не було звалища за моделями і колонками.
- Покривайте моделі тестами, і не тільки стандартними, але й з модуля dbt_utils.
- Обережніше з макросами.
- Намагайтеся використовувати ref і source, щоб вибудувати коректний data lineage і мати можливість провантажити весь ланцюжок.
- У dbt хороша документація, не варто нею нехтувати.
- Якщо ви не можете знайти відповідь на своє питання, то ви завжди можете звернутися до офіційного Slack dbt.
🚀Долучайтесь до нашої спільноти Telegram:
🚀Долучайтесь до нашої спільноти FaceBook:
🚀Долучайтесь до нашої спільноти Twiter X:
