• JP

First steps with DBT - Data Build Tool


DBT has been used by a lot of companies on Data area and I believe that we can extract good insights in this post about it. That's going to be a practical post showing how DBT works it and hope you guys enjoy it.


What's DBT?


DBT means Data Build Tool and enables teams to transform data already loaded in their warehouse with simple select statements. DBT does the T in ELT processes, in the other words, he doesn't work to extract and load data but he's useful to transform it.



Step 1: Creating a DBT Project


Now, we're assume that DBT is already installed but if not, I recommend see this link.


After DBT installed you can create a new project using CLI or you can clone this project from the DBT Github repository.


Here for this post we're going to use CLI mode to create our project and also to complete the next steps. To create a new project, run the command below.


dbt init

After running this command, you need to type the project's name and which warehouse or database you're going to use like the image below.


For this post, we're going to use postgres adapter. It's very important that you have a postgres database already installed or you can up a postgres image using docker.


About adapters, DBT supports different of them and you can check here.


I created a table structure and also loaded it with data simulating data from a video platform called wetube and we're going to use them to understand how DBT works it. Follow the structure:



Step 2: Structure and more about DBT


After running dbt init command to create the project, a structure of folders and files below will be created.

I won't talk about the whole directories of project but I'd like to focus in two of them.


Sources


Sources are basically the data already loaded into your warehouse. In DBT process, sources have the same meaning of raw data.


There's no folders representing source data for this project but you need to know about this term because we're going to set up tables already created as sources for the next sections.


Seeds


Seeds is an interesting and useful mechanism to load static data into your warehouse through CSV files. If you want to load these data you need to create a CSV file on this directory and run the command below.

dbt seed

For each field on CSV file, DBT will infer their types and create a table into warehouse or database.


Models


DBT works with Model paradigm, the main idea is that you can create models through the transformation using SQL statements based on tables sources or existing models


Every SQL file located in your model folder will create a model into your warehouse when the command below runs.

dbt run

Remember that a model can be created through a source or another model and don't worry about this, I'll show you more details about it.



Step 3: Setting up database connection


After project already created, we need to set up our database's connection and here at this post, we're going to use postgres as database.


After initialize the project a bunch of files are created and one of them is called profiles.yml.


profiles.yml file is responsible to control the different profiles to the different database's connection like dev and production environment. If you've noticed, we can't see this file on the image above because this file is created outside of project to avoid sensitive credentials. You can find this file in ~/.dbt/ directory.




If you note, we have one profile named dbt_blog and a target called dev, by default the target refer to dev with the database's connection settings. Also, It's possible to create one or more profiles and targets, it enables working with different environments.


Another important detail is that dbt_blog profile should be specified on dbt_project.yml file as a default profile. For the next sections, we'll discuss what and how dbt_project.yml file works it.






Step 4: Creating dbt_project.yml file


Every DBT project has a dbt_project.yml file, you can set up informations like project name, directories, profiles and materialization type.


name: 'dbt_blog'
version: '1.0.0'
config-version: 2
profile: 'dbt_blog'

model-paths: ["models"]
analysis-paths: ["analyses"]
test-paths: ["tests"]
seed-paths: ["seeds"]
macro-paths: ["macros"]
snapshot-paths: ["snapshots"]

target-path: "target"  # directory which will store compiled SQL files
clean-targets:         # directories to be removed by `dbt clean`
  - "target"
  - "dbt_packages"

models:
  dbt_blog:
    # Config indicated by + and applies to all files under models/example/
    mart:
      +materialized: table

Note that profile field was set up as the same profile specified on profiles.yml file and another important detail is about materialized field. Here was set up as a "table" value but by default, is a "view".


Materialized fields allows you to create models as a table or view on each run. There are others type of materialization but we won't discuss here and I recommend see dbt docs.



Step 5: Creating our first model


Creating first files


Let's change a little and let's going to create a sub-folder on model directory called mart and inside this folder we're going to create our .SQL files and also another important file that we don't discuss yet called schema.yml.


Creating schema file


Schema files are used to map sources and to document models like model's name, columns and more.

Now you can create a file called schema.yml e fill up with these informations below.


version: 2

sources:
  - name: wetube
    tables:
      - name: account
      - name: city
      - name: state
      - name: channel
      - name: channel_subs
      - name: video
      - name: video_like
      - name: user_address

models:
  - name: number_of_subs_by_channel
    description: "Number of subscribers by channel"
    columns:
      - name: id_channel
        description: "Channel's ID"
        tests:
          - not_null
      - name: channel
        description: "Channel's Name"
        tests:
          - not_null
      - name: num_of_subs
        description: "Number of Subs"
        tests:
          - not_null

Sources: At sources field you can include tables from your warehouse or database that's going to be used on model creation.


models: At models field you can include the name's model, columns and their description


Creating a model


This part is where we can create SQL scripts that's going to result in our first model.


For the first model, we're going to create a SQL statement to represent a model that we can see the numbers of subscribers by channel. Let's create a file called number_of_subs_by_channel.sql and fill up with these scripts below.


with source_channel as (
    select * from
        {{ source('wetube', 'channel') }}
),

source_channel_subs as (
    select * from
        {{ source('wetube','channel_subs') }}
),

number_of_subs_by_channel as (
    select
        source_channel.id_channel,
        source_channel.name,
        count(source_channel_subs.id_subscriber) num_subs
    from source_channel_subs
       inner join source_channel using (id_channel)
    group by 1, 2
)

select * from number_of_subs_by_channel

Understanding model creation


  • Note that we have multiple scripts separated by common table expression (CTE) that becomes useful to understand the code.


  • DBT enables using Jinja template {{ }} bringing a better flexibility to our code.


  • The usage of keyword source inside Jinja template means that we're referring source tables. To refer a model you need to use ref keyword.


  • The last SELECT statement based on source tables generates the model that will be created as table in the database.


Running our first model


Run the command below to create our first model

dbt run

Output:


Creating another model


Imagine that we need to create a model containing account information and it's channels. Let's get back to schema.yml file to describe this new model.


- name: account_information
  description: "Model containing account information and it's channels"
  columns:
    - name: id_account
      description: "Account ID"
      tests:
        - not_null
    - name: first_name
      description: "First name of user's account"
      tests:
        - not_null
    - name: last_name
      description: "Last name of user's account"
      tests:
        - not_null
    - name: email
      description: "Account's email"
      tests:
        - not_null
    - name: city_name
      description: "city's name"
      tests:
        - not_null
    - name: state_name
      description: "state's name"
      tests:
        - not_null
    - name: id_channel
      description: "channel's Id"
      tests:
        - not_null
    - name: channel_name
      description: "channel's name"
      tests:
        - not_null
    - name: channel_creation
      description: "Date of creation name"
      tests:
        - not_null

Now, let's create a new SQL file and name it as account_information.sql and put scripts below:

with source_channel as (
    select * from
        {{ source('wetube', 'channel') }}
),

source_city as (
    select * from
        {{ source('wetube','city') }}
),

source_state as (
    select * from
        {{ source('wetube','state') }}
),

source_user_address as (
    select * from
        {{ source('wetube','user_address') }}
),

source_account as (
    select * from
        {{ source('wetube','account') }}
),

account_info as (
    select
        account.id_user as id_account,
        account.first_name,
        account.last_name,
        account.email,
        city.name as city_name,
        state.name as state_name,
        channel.id_channel,
        channel.name as channel,
        channel.creation_date as channel_creation
    FROM source_account account
       inner join source_channel channel on (channel.id_account = account.id_user)
       inner join source_user_address user_address using (id_user)
       inner join source_state state using (id_state)
       inner join source_city city using (id_city)
)

select * from account_info

Creating our last model


For our last model, we going to create a model about how many likes has a video. Let's change again the schema.yml to describe and to document our future and last model.


- name: total_likes_by_video
  description: "Model containing total of likes by video"
  columns:
    - name: id_channel
      description: "Channel's Id"
      tests:
        - not_null
    - name: channel
      description: "Channel's name"
      tests:
        - not_null
    - name: id_video
      description: "Video's Id"
      tests:
        - not_null
    - name: title
      description: "Video's Title"
      tests:
        - not_null
    - name: total_likes
      description: "Total of likes"
      tests:
        - not_null

Name it a file called total_likes_by_video.sql and put the code below:


with source_video as (
    select * from
        {{ source('wetube','video') }}
),

source_video_like as (
    select * from
        {{ source('wetube','video_like') }}
),

source_account_info as (
    select * from
        {{ ref('account_information') }}
),

source_total_like_by_video as (

    select source_account_info.id_channel, source_account_info.channel,
        source_video.id_video, source_video.title, count(*) as total_likes
    FROM source_video_like
        inner join source_video using (id_video)
        inner join source_account_info using (id_channel)
    GROUP BY source_account_info.id_channel,
             source_account_info.channel,
             source_video.id_video,
             source_video.title
    ORDER BY  total_likes DESC
)

select * from source_total_like_by_video

Running DBT again


After creation of our files, let's run them again to create the models

dbt run

Output



The models were created in the database and you can run select statements directly in your database to check it.



Model: account_information



Model: number_of_subs_by_channel



Model: total_likes_by_video





Step 6: DBT Docs


Documentation


After generated our models, now we're going to generate docs based on these models. DBT generates a complete documentation about models and sources and their columns and also you can see through a web page.


Generating docs

dbt docs generate

Running docs on webserver


After docs generated you can run command below to start a webserver on port 8080 and see the documentation locally.

dbt docs serve





Lineage


Another detail about documentation is that you can see through of a Lineage the models and it's dependencies.





Github code


You can checkout this code through our Github page.


Cool? I hope you guys enjoyed it!

Posts recentes

Ver tudo