Using dbt to Transform OpenSky Flight Data

작성자

카테고리:

← 피드로
DEV Community · Charity Jelimo · 2026-08-07 개발(SW)

Building an OpenSky Flight Data Pipeline with dbt Core

Recently, I built an OpenSky Flight Data Pipeline that ingests aircraft state vectors into PostgreSQL and transforms them into analytics-ready datasets using dbt Core. Along the way, I discovered that dbt isn’t just about writing SQL, it brings software engineering practices like modularity, testing, documentation, and dependency management to analytics.

In this article, I’ll walk through how I used dbt, the concepts I learned, and how I organized my project.

The Problem

The OpenSky Network provides aircraft state vectors containing information such as:

  • ICAO24 identifier
  • Callsign
  • Origin country
  • Latitude and longitude
  • Speed
  • Altitude
  • Heading
  • Vertical rate
  • Flight status

Although the data is rich, it isn’t immediately suitable for analytics. It contains inconsistent formatting, duplicate records, missing values, and measurements that need conversion.

Rather than writing one large SQL script, I wanted to build a modular transformation pipeline that was easy to maintain, test, and extend.

What is dbt?

dbt (Data Build Tool) is an open-source transformation framework that transforms data already stored in your data warehouse or database.

Unlike ingestion tools, dbt does not move data. Instead, it focuses on the Transform stage of the ELT workflow.

With dbt, you write SQL models while it handles the engineering around them, including:

  • Running models in the correct order
  • Managing dependencies
  • Testing data quality
  • Generating documentation
  • Building lineage graphs
  • Integrating with Git

Think of it as applying software engineering principles to SQL.

Why Not Just Use PostgreSQL?

You can absolutely build transformation tables manually using SQL.

The challenge comes when your project starts growing.

Questions quickly arise:

  • Which table should run first?
  • How do you avoid duplicating transformation logic?
  • How do you document your models?
  • How do you validate data quality?
  • How do you understand model dependencies?

dbt solves these problems by introducing modular models, dependency management, testing, and documentation.

Project Architecture

My transformation pipeline follows a layered architecture.

bronze.all_state_vectors
            │
            ▼
stg_state_vectors
            │
            ▼
state_vectors
      ├───────────────┐
      │               │
      ▼               ▼
performance      location
      │               │
      ▼               ▼
status          latest
        │
        ▼
snapshot_summary
        │
        ▼
country_statistics

Enter fullscreen mode Exit fullscreen mode

Every downstream model builds upon another model using ref(), allowing dbt to automatically determine the execution order.

Building the Silver Layer

1. stg_state_vectors

The staging model is responsible for cleaning the raw OpenSky data.

It performs tasks such as:

  • Trimming text fields
  • Removing duplicate rows
  • Validating latitude and longitude values
  • Standardizing country names
  • Casting columns to appropriate data types

This model becomes the foundation for every downstream transformation.

2. state_vectors

After cleaning the raw data, I created an enriched model containing derived metrics such as:

  • Speed in km/h
  • Speed in knots
  • Altitude in feet
  • Flight status
  • Aircraft movement classification
  • Compass heading
  • Snapshot date
  • Snapshot hour

Rather than recalculating these metrics repeatedly across multiple queries, they’re computed once and reused everywhere else.

Purpose-Built Models

Instead of creating one massive analytics table, I broke the transformations into smaller, focused models.

Model Purpose silver_aircraft_performance Speed, altitude, climb rate, heading silver_aircraft_location Latitude, longitude, timestamps silver_aircraft_status Operational state and movement silver_aircraft_latest Latest snapshot for each aircraft silver_snapshot_summary Fleet-level metrics country_statistics Country-level aggregations airborne_flights Aircraft currently airborne airport_surface_activity Aircraft currently on the ground

Keeping each model focused makes them easier to understand, test, and maintain.

One of My Favourite Features: ref()

Instead of referencing tables directly like this:

SELECT *
FROM state_vectors;

Enter fullscreen mode Exit fullscreen mode

dbt encourages referencing models using ref().

SELECT *
FROM {{ ref('state_vectors') }}

Enter fullscreen mode Exit fullscreen mode

Using ref() has several advantages:

  • Automatically creates model dependencies
  • Executes models in the correct order
  • Makes refactoring easier
  • Builds lineage documentation automatically

As projects grow, this becomes incredibly valuable.

Working with Sources

The raw OpenSky table is defined as a source.

sources:
  - name: bronze
    schema: bronze

    tables:
      - name: all_state_vectors

Enter fullscreen mode Exit fullscreen mode

Instead of hardcoding table names, models reference the source using:

SELECT *
FROM {{ source('bronze', 'all_state_vectors') }}

Enter fullscreen mode Exit fullscreen mode

Using sources improves documentation, lineage, and maintainability.

Testing

One of the features I appreciated most was dbt’s built-in testing.

For example:

columns:
  - name: icao24
    tests:
      - not_null

  - name: flight_status
    tests:
      - accepted_values:
          values:
            - Airborne
            - Ground

Enter fullscreen mode Exit fullscreen mode

Running:

dbt test

Enter fullscreen mode Exit fullscreen mode

automatically validates these rules and reports any failures.

Having tests alongside transformation logic makes it much easier to catch issues before downstream models are affected.

Documentation

Generating documentation is incredibly simple.

dbt docs generate
dbt docs serve

Enter fullscreen mode Exit fullscreen mode

dbt creates an interactive documentation website containing:

  • Models
  • Sources
  • Column descriptions
  • Tests
  • Lineage graph

One of my favourite moments during this project was watching the lineage graph grow as I added more models.

It provides a clear visual representation of how every transformation connects together.

Commands I Used

Throughout the project, these were the commands I used most frequently.

dbt debug
dbt compile
dbt run
dbt test
dbt docs generate
dbt docs serve

Enter fullscreen mode Exit fullscreen mode

Each command serves a different purpose, from validating configuration to building documentation.

Lessons Learned

This project completely changed how I think about SQL transformations.

Instead of writing one massive SQL query, I built a collection of small, reusable models that are easier to understand, maintain, and test.

My biggest takeaways were:

  • Build small, reusable models.
  • Keep transformations modular.
  • Document everything.
  • Add tests early.
  • Let dbt manage dependencies instead of doing it manually.
  • Treat analytics projects like software engineering projects.

Repository

The complete project is available on GitHub:

https://github.com/jelimo-charity/opensky-flight-data-pipeline/tree/main/dbt

Feel free to explore the models, documentation, and project structure.

If you’re already comfortable writing SQL and want to build more maintainable analytics pipelines, I highly recommend learning dbt.

Beyond simplifying transformations, dbt encourages practices that make data projects easier to scale, collaborate on, and maintain over time. It bridges the gap between traditional SQL development and modern data engineering by making transformations modular, testable, and well documented.

For me, this project wasn’t just about transforming flight data—it was about learning a better way to build analytics pipelines.

원문에서 계속 ↗

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다