Free · Full Book Phase 1 of the CLMITS Data Engineering Bootcamp See the live 12-week bootcamp →
DATA ENGINEERING BOOTCAMP — THE BOOK

Phase 1: Foundations
Python, Git & SQL

This is your companion book for Weeks 1–4 — written for someone switching careers into data, not someone who already codes. Every tool is explained before you're asked to use it, every command is shown in full, and every example uses the same running case study so the ideas connect instead of floating in isolation.

4
Weeks covered
6
Tool spotlights
1
Running case study
3
Portfolio projects
00
BEFORE YOU START

How This Book Works

Three ideas that make everything after this page easier to follow.

Read → Run → Break → Explain

You are not expected to memorize syntax. You are expected to type every example yourself, run it, and then break it on purpose to see what the error looks like. That last step matters more than people think — a data engineer's real job is reading error messages calmly and figuring out what they mean.

1. Code Along

Don't just read the code blocks — retype them in your own editor. Typing builds muscle memory that reading never will.

2. Break It on Purpose

Misspell a column name. Remove a comma. Read the error. This is how you stop being afraid of red text.

3. Explain It Back

After each lesson, say out loud (or write one sentence) what the code does. If you can't, re-read the lesson.

Meet BrightCart: Your Case Study

Every code example, every SQL query, and every mini-project in this book — across all three phases — uses the same small dataset: a fictional online store called BrightCart. Real data engineering work is never a random toy example per lesson; you follow one system end to end. This book does the same thing on purpose.

BrightCart sells electronics, home goods, apparel, and books. Its data has exactly the shape you'll meet in a real job: customers who sign up, products with prices, orders that get placed, and order line items — plus some orders that are still pending, cancelled, or refunded, because real businesses aren't tidy.

customers🔑 customer_idfirst_namelast_nameemailcitycountryorders🔑 order_idcustomer_idorder_datestatuspayment_methodorder_items🔑 order_item_idorder_idproduct_idquantityunit_priceproducts🔑 product_idproduct_namecategoryunit_pricecost_price1 → many1 → manymany → 1

The BrightCart schema — one customer places many orders, one order can contain many order_items, and every order_item points at one product.

TableGrain (one row =)Used to teach
customersone person who signed upfiltering, joins, dimension tables
productsone item BrightCart sellsjoins, categories, pricing logic
ordersone checkout eventdates, status, aggregation
order_itemsone product inside one orderjoins, SUM/quantity math, fact tables

Why a fake store and not a "real" public dataset?

Public datasets are messy in ways that distract from the lesson at hand. BrightCart is small enough to hold in your head (16 customers, 14 products, 45 orders) but shaped exactly like production data, including a deliberately messy version you'll clean in Phase 2. You always know what the "right" answer should look like, which is how you learn to spot when your own query is wrong.

Download the Case Study Files

These files are provided alongside this book. Save them into one project folder — you'll load the .sql files into PostgreSQL in Chapter 5, and read the .csv files with Python starting in Chapter 2.

Files in this bundle

  • 01_schema.sql
  • 02_seed_clean_data.sql
  • customers.csv
  • products.csv
  • orders.csv
  • order_items.csv
  • order_totals_clean.csv
  • dirty_orders.csv (Phase 2)

Keep one project folder

Create a folder called de-bootcamp and put a data/ subfolder inside it for these files. Chapter 1 walks you through creating exactly this structure with Git tracking it from day one.
01
WEEK 1 · THE WORKBENCH

Setting Up Your Developer Environment

Four tools you'll open every single day of this bootcamp. Install them once, understand them properly, and everything after this chapter goes faster.

Why Start With Tools, Not Code?

A carpenter doesn't start a project by learning to swing a hammer mid-job — they set up the workbench first. Data engineers do the same. Before you write a line of Python or SQL, you need a place to write it (an editor), a way to run it (an interpreter), a way to talk to your computer directly (a terminal), and a way to save your progress so nothing is ever lost (version control).

Python 3

The language interpreter — turns the code you write into instructions your computer executes.

VS Code

The IDE (editor) — where you write, run, and debug every script and query in this book.

The Terminal

The command line — how you navigate folders and run programs without clicking icons.

Git & GitHub

Version control — saves checkpoints of your work and gives you a public portfolio.

Tool Spotlight: Python

15 min

Tool Spotlight

Python 3

The programming language you'll use for almost everything in this bootcamp.

What it is

Python is an interpreter: a program that reads your .py file line by line and executes it immediately. There's no separate "compile" step, which is why it's a popular first language.

Why data engineers use it

Nearly every data tool you'll touch later — pandas, Airflow, PySpark — is a Python library. Learning Python fundamentals now is what makes all of Phase 2 and 3 possible.

How to get it running

  1. 1
    Go to python.org/downloads and download the latest Python 3 installer for your OS.
  2. 2
    Windows: during install, tick "Add python.exe to PATH" before clicking Install — this single checkbox prevents 90% of beginner setup issues.
  3. 3
    Mac: the installer works out of the box; Terminal.app already knows where Python lives once installed.
  4. 4
    Restart your terminal, then verify the install (see "Try it" below).

Try it

terminal
$ python --version
Python 3.12.4

$ python3 --version   # Mac/Linux sometimes need python3, not python
Python 3.12.4
Common snag: If python --version says "command not found," the PATH checkbox above was likely missed. Re-run the installer and choose "Modify" to fix it — no need to uninstall first.

Tool Spotlight: VS Code

10 min

Tool Spotlight

Visual Studio Code (VS Code)

A free code editor with the exact features a data engineer needs — nothing more, nothing less.

What it is

VS Code is a text editor built for writing code: it colors your syntax, catches typos before you run anything, and has a built-in terminal panel so you never have to alt-tab away from your code.

Why data engineers use it

You'll live inside VS Code for all three phases — writing Python scripts, SQL files, Airflow DAGs, and Dockerfiles all in one window, with Git built directly into the sidebar.

How to get it running

  1. 1
    Download it from code.visualstudio.com and run the installer (default options are fine).
  2. 2
    Open VS Code, click the Extensions icon in the left sidebar (four squares icon).
  3. 3
    Install the Python extension (by Microsoft) — enables run/debug and autocomplete.
  4. 4
    Install SQLTools (optional but recommended) — lets you run SQL queries against PostgreSQL from inside VS Code later in this chapter.

Try it

terminal
# From a terminal, inside any project folder:
$ code .
# The dot means "open the current folder in VS Code"
Common snag: If code . doesn't work, open VS Code manually once, press Cmd/Ctrl+Shift+P, type "Shell Command: Install 'code' command in PATH", and select it.

Tool Spotlight: The Terminal

15 min

Tool Spotlight

The Terminal (Command Line)

A text-based way to control your computer — no mouse required.

What it is

The terminal is a program that accepts typed commands instead of clicks: moving between folders, running scripts, and installing tools all happen here. On Windows it's usually PowerShell or the "Terminal" app; on Mac it's Terminal.app; VS Code has one built in under the menu Terminal → New Terminal.

Why data engineers use it

Data engineers live in the terminal because most real tools — Git, Docker, cloud CLIs, Airflow — are only fully controllable from the command line. There is no "skip this" option later in the bootcamp, so it's worth getting comfortable now.

How to get it running

  1. 1
    You already have one — open VS Code and go to Terminal → New Terminal (or press Ctrl+`).
  2. 2
    Confirm you're in your home folder by running pwd (Mac/Linux) or cd with no arguments (Windows) — it prints your current location.
  3. 3
    Practice moving around using the commands in the table below before continuing.
CommandWhat it doesExample
pwdPrint Working Directory — shows where you arepwd
cdChange Directory — move into a foldercd Desktop
cd ..Move up one foldercd ..
ls / dirList files in the current folder (Mac/Linux vs Windows)ls
mkdirMake a new foldermkdir de-bootcamp
python file.pyRun a Python scriptpython hello.py
terminal — moving into a new project folder
$ pwd
/Users/ana/Desktop

$ mkdir de-bootcamp
$ cd de-bootcamp
$ pwd
/Users/ana/Desktop/de-bootcamp
$ ls
# (empty — nothing in here yet)

Beginner trap: typing paths with the wrong slashes

Mac/Linux use forward slashes (data/raw); Windows PowerShell also accepts forward slashes in most commands, so when in doubt, use / — it works almost everywhere in this book.

Tool Spotlight: Git & GitHub

20 min

Tool Spotlight

Git & GitHub

Git is your project's time machine. GitHub is where that history becomes a public portfolio.

What it is

Git is a program installed on your computer that tracks every change you make to a project folder, in checkpoints called commits. GitHub is a website that stores a copy of that history online, so employers, teammates, and future-you can see it.

Why data engineers use it

Every mini-project and portfolio project in this bootcamp gets committed to Git and pushed to GitHub. Your GitHub profile is your resume as a career shifter — it's proof you can actually build things, not just say you can.

How to get it running

  1. 1
    Download Git from git-scm.com/downloads and install with default options.
  2. 2
    Verify it worked: run git --version in your terminal.
  3. 3
    Create a free account at github.com if you don't already have one.
  4. 4
    Tell Git who you are (one-time setup, shown in "Try it" below) — every commit gets stamped with this name and email.

Try it

terminal — one-time setup
$ git --version
git version 2.44.0

$ git config --global user.name "Ana Dela Cruz"
$ git config --global user.email "ana.delacruz@example.com"
Common snag: Don't skip the git config step — without it, your first commit will fail with an "author identity unknown" error.

How Git Actually Works

Git moves your work through three places. Understanding this diagram now will save you real confusion later — almost every Git error a beginner hits comes from not knowing which of these three states their file is in.

1Working Directoryedit hello.py2Staging Areagit add .3Local Repositorygit commit -m "..."4GitHub (Remote)git push

A file moves left to right: you edit it, you `add` it to staging, you `commit` it to local history, you `push` it to GitHub.

TermMeaning
Repository (repo)A project folder that Git is tracking.
CommitA saved checkpoint, with a short message describing what changed.
Stage / git addMarking which changed files should go into the next commit.
PushUploading your local commits to GitHub.
CloneDownloading a copy of a GitHub repo onto your computer.
the five commands you'll type more than any others in this bootcamp
git init                        # start tracking this folder
git status                      # what changed since the last commit?
git add .                       # stage everything that changed
git commit -m "first commit"    # save a checkpoint
git push                        # upload to GitHub

A daily habit, not a one-time event

Run git add . && git commit -m "..." every time you finish a small, working piece of code — not just at the end of the day. Small commits with clear messages are what a real engineering team (and a hiring manager skimming your GitHub) expects to see.

Activity: Your First Repository

15 min
Hands-on Activity 1

Create the de-bootcamp Project and Commit Hello Data World

Put everything from this chapter together: create a folder, initialize Git, write one line of Python, and push it to GitHub.

  1. Open a terminal and run mkdir de-bootcamp && cd de-bootcamp.
  2. Run git init to start tracking the folder.
  3. Open the folder in VS Code with code .
  4. Create a new file named hello.py containing one line: print("Hello, BrightCart!")
  5. Run it from the VS Code terminal: python hello.py — confirm it prints the message.
  6. Stage and commit: git add . && git commit -m "add hello script"
  7. On GitHub.com, create a new empty repository named de-bootcamp, then follow the "push an existing repository" instructions GitHub shows you to connect and git push.

Deliverable: A GitHub repository with one commit and a working hello.py file — this is repo #1 of your portfolio.

Chapter 1 Checklist — before moving on, you should be able to:

  • Run python --version and see a version number, not an error
  • Open a folder in VS Code using code .
  • Move between folders in a terminal using cd, ls, and mkdir
  • Explain the difference between git add, git commit, and git push
  • Have a GitHub repository containing your first commit
02
WEEK 1 · PYTHON FUNDAMENTALS I

Variables, Types & Operators

The building blocks every Python script is made of. We'll represent one BrightCart order in code and never let go of that example.

What Is a Variable?

A variable is a labeled box in your computer's memory. You put a value inside it, and you use the label to find that value again later. In Python you create one with a single equals sign — there's no need to declare a type up front, Python figures that out for you (this is called dynamic typing).

representing one row from customers.csv as variables
customer_id = "C001"
customer_name = "Ana Dela Cruz"
order_total = 792.34
is_paid = True

print(customer_name)   # Output: Ana Dela Cruz

Read `=` as "gets set to", not "equals"

In math, x = 5 is a statement of fact. In Python, customer_id = "C001" is an instruction: take the value on the right, and store it in the box named on the left. That's why count = count + 1 makes perfect sense in code but none in algebra.

Variable Naming Rules

Python is strict about what a variable name can look like, and the community is strict about what it *should* look like. Both matter — one crashes your program, the other makes your code readable to the next engineer (often future-you).

RuleExample
✓ snake_case (recommended)customer_id, total_sales
✗ No spacescustomer id — this crashes
✗ No leading numbers1st_order — this crashes
⚠ Case sensitiveTotal and total are different variables

Core Data Types

Every value in Python has a type, and the type decides what you're allowed to do with it — you can add two numbers, but adding a number to text will raise an error. These four types cover the vast majority of what you'll touch in this bootcamp.

TypeDescriptionBrightCart example
str (string)Text, always in quotes"Manila"
int (integer)Whole numbers1001  (an order_id)
floatDecimal numbers24.99  (a unit_price)
bool (boolean)True or FalseTrue  (is_paid)
use type() any time you're unsure what you're working with
order_id = 1001            # int
customer_city = "Manila"   # str
unit_price = 24.99         # float
is_paid = True              # bool

print(type(unit_price))    # <class 'float'>

Type Casting: Input Is Always Text

This trips up nearly every beginner exactly once: Python's input() function always returns a string, even if the person types a number. If you try to do math on it directly, Python either crashes or does something you didn't expect.

the beginner trap
qty = input("Quantity: ")   # user types: 3
total = qty * 10
# qty is the STRING "3", not the number 3
# Python repeats the text instead of doing math:
print(total)   # "333333333333333333333333333333"
the fix — wrap input() in int() or float()
qty = int(input("Quantity: "))   # convert to int immediately
total = qty * 10
print(total)   # 30
FunctionConverts to
int(x)whole number
float(x)decimal number
str(x)text

Operators: Doing Things With Values

Operators are the symbols that combine values — arithmetic for math, comparisons for true/false questions, and logical operators for combining multiple conditions.

CategoryOperatorsExample (BrightCart order_items)
Arithmetic+ - * / // % **quantity * unit_price
Comparison== != > < >= <=status == "paid"
Logicaland, or, notstatus == "paid" and amount > 100
computing one order_items line total
quantity = 2
unit_price = 24.99
line_total = quantity * unit_price
print(line_total)          # 49.98

is_big_order = line_total > 40 and quantity >= 2
print(is_big_order)        # True

`=` vs `==`

= assigns a value. == asks "are these equal?" and returns True/False. Mixing them up (if status = "paid":) is a syntax error in Python — which is actually a mercy; some languages let this typo through silently and cause much worse bugs.

Working With Strings

Text data from the real world is almost never clean — extra spaces, inconsistent capitalization, stray characters. String methods are the tools you'll reach for constantly, both here and later when cleaning BrightCart's messier exports in Phase 2.

string methods you'll use constantly
city = "  manila  "

print(city.strip())        # "manila"        (removes leading/trailing spaces)
print(city.strip().title())# "Manila"        (capitalizes each word)
print(city.upper())        # "  MANILA  "
print(city.lower())        # "  manila  "

name = "Ana" + " " + "Dela Cruz"
print(name)                 # "Ana Dela Cruz"   (+ joins/concatenates strings)

email = "ana.delacruz@brightmail.com"
print(email.split("@"))    # ['ana.delacruz', 'brightmail.com']

# f-strings: the modern way to build text with variables inside
print(f"{name} spent ${line_total}")
MethodWhat it does
.strip()removes leading/trailing whitespace
.upper() / .lower()changes case
.title()capitalizes each word
.split(sep)breaks a string into a list on a separator
.replace(old, new)swaps one substring for another

Chapter 2 Checklist — before moving on, you should be able to:

  • Create variables of type str, int, float, and bool
  • Explain why input() needs int() or float() around it for math
  • Use + - * / and comparison operators correctly
  • Clean a messy string using .strip(), .title(), or .lower()
  • Build a sentence with an f-string like f"{name} spent {total}"
03
WEEK 1 · PYTHON FUNDAMENTALS II

Control Flow & Data Structures

How to make decisions, repeat work, and hold more than one value at a time — the tools that turn single-line scripts into real programs.

Conditionals: if / elif / else

Conditionals let your code make decisions. Python checks each condition top to bottom and runs the first block that evaluates to True — everything else is skipped.

classifying one BrightCart order
status = "paid"
amount = 792.34

if status == "cancelled":
    print("No action needed")
elif status == "paid" and amount >= 500:
    print("VIP order — flag for priority shipping")
elif status == "paid":
    print("Standard paid order")
else:
    print("Needs follow-up: " + status)

Indentation is not optional

Python uses indentation (4 spaces, consistently) to know what's inside an if block and what isn't. Mixing tabs and spaces is one of the most common beginner errors — configure VS Code to insert spaces when you press Tab (it does this by default for Python files).

Loops: for and while

A for loop repeats code once per item in a collection — the workhorse of data engineering, since almost everything you process is a list of records. A while loop repeats until a condition becomes False, useful when you don't know in advance how many times you'll need to run.

for loop — summing a list of order amounts
order_amounts = [792.34, 59.99, 218.00, 38.97]

total = 0
for amount in order_amounts:
    total = total + amount

print(f"Total revenue: {total}")   # Total revenue: 1109.3
while loop — a pattern you'll reuse for API pagination in Week 2
page = 1
has_more_pages = True

while has_more_pages:
    print(f"Fetching page {page}...")
    if page == 3:              # pretend the API told us this was the last page
        has_more_pages = False
    page += 1                  # same as: page = page + 1

The infinite loop

A while loop with a condition that never becomes False will run forever and freeze your program. Always make sure something inside the loop moves you toward the exit condition — in the example above, that's page += 1.

Lists: An Ordered Collection

A list holds multiple values in one variable, in order, accessed by position (starting at 0, not 1 — this catches everyone once). Lists are the closest thing Python has to a spreadsheet column, and you'll use them constantly before you meet pandas in Phase 2.

lists — the last line is a list comprehension, a compact filter (optional, come back to this later if it's confusing)
cities = ["Manila", "Cebu City", "Davao City", "Singapore"]

print(cities[0])          # "Manila"    <- index 0 is the FIRST item
print(cities[-1])         # "Singapore" <- -1 is the LAST item
print(len(cities))        # 4

cities.append("Tokyo")    # add to the end
print(cities)             # ['Manila', 'Cebu City', 'Davao City', 'Singapore', 'Tokyo']

philippine_cities = [c for c in cities if c != "Singapore" and c != "Tokyo"]
print(philippine_cities)  # ['Manila', 'Cebu City', 'Davao City']

Dictionaries: Labeled Data

A dictionary stores key–value pairs instead of positions — this is how you'll represent one row of a table before pandas does it for you automatically. If a list is a column, a dictionary is closer to a single record.

dictionaries — one BrightCart order as a record
order = {
    "order_id": 1001,
    "customer": "Ana Dela Cruz",
    "amount": 792.34,
    "status": "paid"
}

print(order["customer"])       # "Ana Dela Cruz"
print(order.get("status"))     # "paid"
print(order.get("discount", 0))  # 0  <- .get() with a default avoids a crash if the key is missing

order["shipped"] = True        # add a new key
print(order.keys())            # dict_keys(['order_id', 'customer', 'amount', 'status', 'shipped'])

A list of dictionaries is exactly how you'll represent a whole table — and it's exactly the shape a JSON API response usually arrives in, which is why this pattern matters so much.

a list of dictionaries = a small table
orders = [
    {"order_id": 1001, "customer": "Ana Dela Cruz", "amount": 792.34, "status": "paid"},
    {"order_id": 1002, "customer": "Ben Santos",    "amount": 59.99,  "status": "paid"},
    {"order_id": 1004, "customer": "Carlo Reyes",   "amount": 38.97,  "status": "pending"},
]

paid_total = 0
for o in orders:
    if o["status"] == "paid":
        paid_total += o["amount"]

print(f"Paid revenue: {paid_total}")   # Paid revenue: 852.33

Tuples & Sets (Brief)

Two more collection types worth recognizing, even if you'll reach for them less often than lists and dictionaries.

TypeOrdered?Can change after creation?Duplicates allowed?Written as
listYesYesYes[1, 2, 3]
tupleYesNoYes(1, 2, 3)
setNoYesNo{1, 2, 3}
a classic use of set(): finding distinct values, before you learn SQL's DISTINCT
payment_methods = ["gcash", "credit_card", "gcash", "paypal", "gcash"]

unique_methods = set(payment_methods)
print(unique_methods)     # {'gcash', 'credit_card', 'paypal'}  -- duplicates gone

Chapter 3 Checklist — before moving on, you should be able to:

  • Write an if / elif / else chain that checks multiple conditions
  • Write a for loop that sums values from a list
  • Explain why a while loop needs something that changes each time it runs
  • Access a list item by index, including negative indexing
  • Build a dictionary representing one record, and a list of dictionaries representing a small table
04
WEEK 1–2 · PYTHON FUNDAMENTALS III

Functions, Files & Your First Data Extraction

Functions turn repeated code into reusable tools. Files and APIs are how real data gets into your programs in the first place.

Functions: Reusable Blocks of Logic

A function is a named, reusable block of code. Instead of retyping the same logic for every order, you write it once, give it a name, and call it as many times as you need with different inputs (called parameters or arguments).

anatomy of a function: def, parameters, a default value, and return
def calculate_line_total(quantity, unit_price):
    return quantity * unit_price

# call it as many times as you need:
print(calculate_line_total(2, 24.99))   # 49.98
print(calculate_line_total(1, 129.00))  # 129.0

def classify_order(amount, threshold=500):
    """threshold has a DEFAULT value -- callers can override it or skip it"""
    if amount >= threshold:
        return "VIP"
    return "standard"

print(classify_order(792.34))         # "VIP"       (uses default threshold=500)
print(classify_order(300, 250))       # "VIP"       (override: threshold=250)
PartWhat it means
defkeyword that starts a function definition
calculate_line_totalthe function's name — use a verb, describe what it does
(quantity, unit_price)parameters — the inputs the function expects
returnsends a value back to whoever called the function

A good function does one thing

If you find yourself describing a function with "and" ("it calculates the total and prints a receipt and saves to a file"), split it into three functions. Small, single-purpose functions are dramatically easier to test and reuse — this habit pays off enormously once you're writing ETL pipelines in Phase 2.

Handling Errors: try / except

Real data is unpredictable — a field might be blank, a file might not exist, an API might time out. try / except lets your program catch an error and respond sensibly instead of crashing.

skip bad values instead of letting one bad row kill the whole script
raw_quantities = ["2", "3", "N/A", "1"]

total_units = 0
for q in raw_quantities:
    try:
        total_units += int(q)
    except ValueError:
        print(f"Skipping bad value: {q!r}")

print(total_units)   # 6  (the 'N/A' was skipped, not crashed on)

Don't catch every error blindly

A bare except: (with no error type) silently swallows everything, including bugs in your own code — making them nearly impossible to find. Always name the error you expect, like except ValueError:.
WEEK 1PORTFOLIO PROJECT

Portfolio Project — BrightCart Order Total Calculator

None

  • Write a function calculate_line_total(quantity, unit_price) that returns the line total.
  • Write a function classify_order(amount) that returns "VIP" for totals ≥ 500 and "standard" otherwise.
  • Build a small command-line loop using input() that lets a user enter a quantity and price, prints the total, and asks "Add another item? (y/n)" until they say no.
  • Print a final receipt-style summary and commit the script as order_calculator.py.

Reading & Writing CSV Files

Before pandas (Phase 2) makes this effortless, it's worth understanding what's actually happening: a CSV file is plain text with commas separating values, and Python's built-in csv module turns each line into a list — or, with DictReader, into a dictionary using the header row as keys.

reading customers.csv with csv.DictReader — each row becomes a dictionary
import csv

with open("data/customers.csv", "r") as f:
    reader = csv.DictReader(f)
    for row in reader:
        print(row["first_name"], row["city"])

# Ana Manila
# Ben Cebu City
# Carlo Davao City
# ...
writing a new CSV file — the pattern you'll reuse for every pipeline's output
import csv

vip_customers = [
    {"customer_id": 1, "customer_name": "Ana Dela Cruz", "total_spent": 792.34},
    {"customer_id": 12, "customer_name": "Liam Brooks", "total_spent": 476.00},
]

with open("data/vip_customers.csv", "w", newline="") as f:
    writer = csv.DictWriter(f, fieldnames=["customer_id", "customer_name", "total_spent"])
    writer.writeheader()
    writer.writerows(vip_customers)

with open(...) as f:

The with keyword automatically closes the file when you're done with it, even if an error happens inside the block. Always use with open(...) instead of a bare open(...) — it's the difference between a script that occasionally corrupts files and one that never does.

Working With JSON & APIs

JSON (JavaScript Object Notation) is the format almost every API speaks — and it maps directly onto Python dictionaries and lists, which is exactly why you learned those first. An API is just a website's URL that returns data instead of a webpage.

a JSON order record — notice it's basically a Python dict with a list inside it
{
  "order_id": 1001,
  "customer": "Ana Dela Cruz",
  "items": [
    {"product": "Wireless Mouse", "quantity": 2},
    {"product": "Water Bottle", "quantity": 1}
  ],
  "status": "paid"
}
json.load() turns JSON text into Python dictionaries/lists automatically
import json

with open("data/order_1001.json") as f:
    order = json.load(f)     # JSON text -> Python dict

print(order["customer"])            # "Ana Dela Cruz"
for item in order["items"]:
    print(item["product"], item["quantity"])
requests — the standard library for calling real APIs (install with: pip install requests)
import requests

response = requests.get("https://api.example.com/orders", timeout=30)
response.raise_for_status()   # crash loudly if the request failed, instead of silently continuing

data = response.json()        # parses JSON straight into a Python dict/list
print(data[0]["customer"])

Always set a timeout

Without timeout=30, a stalled server can freeze your script forever with no error at all. This one keyword argument will save you a genuinely confusing debugging session down the road.
WEEK 2PORTFOLIO PROJECT

Portfolio Project — BrightCart Order Extractor

None

  • Write a function load_orders_csv(path) that reads orders.csv with csv.DictReader and returns a list of dictionaries.
  • Write a function summarize_by_status(orders) that counts how many orders fall into each status.
  • Read one sample order as JSON (create a small .json file by hand) and print its item list.
  • Write the status summary out to a new file, status_summary.csv, using csv.DictWriter.
  • Commit the script as order_extractor.py — this is the direct ancestor of the ETL pipelines you'll build in Phase 2.

Chapter 4 Checklist — before moving on, you should be able to:

  • Write a function with parameters, a default value, and a return statement
  • Explain why try / except is safer than letting a script crash on bad data
  • Read a CSV file into a list of dictionaries with csv.DictReader
  • Write a list of dictionaries out to a new CSV file with csv.DictWriter
  • Explain what JSON is and why json.load() turns it directly into Python dicts/lists

Like learning this way?

This is a free sample of our full 12-week live Data Engineering Bootcamp — with mentor code reviews, a cohort, and a capstone project.

See the Bootcamp
05
WEEK 3 · SQL FUNDAMENTALS

Talking to a Database

SQL is how you ask a database questions. We'll load BrightCart into a real database and start asking it things.

What Is a Relational Database?

A relational database stores data in tables — grids of rows and columns, like a spreadsheet, except tables can be linked together by shared ID columns (that's the "relational" part). PostgreSQL, MySQL, and SQLite are all RDBMS (Relational Database Management Systems) — programs that store and let you query this kind of data.

Python variables live only while your script runs. A database persists — BrightCart's orders stay there whether your laptop is on or off, and multiple programs (or people) can read and write to it at the same time. That durability and shareability is exactly why production systems use databases instead of just CSV files.

Tool Spotlight: PostgreSQL

20 min

Tool Spotlight

PostgreSQL ("Postgres")

A free, open-source relational database — the one this entire bootcamp is built around.

What it is

PostgreSQL is a server program: it runs quietly in the background on your computer (or in the cloud, later in Phase 2) and listens for SQL commands from clients like the terminal, VS Code, or a Python script.

Why data engineers use it

It's free, it's what most real companies actually use for OLTP workloads, and everything you learn here — joins, window functions, indexes — transfers directly to Snowflake, BigQuery, and Redshift in Phase 2.

How to get it running

  1. 1
    Go to postgresql.org/download and choose your OS (the Windows/Mac installer bundles pgAdmin, a GUI client, automatically).
  2. 2
    During install you'll set a password for the default postgres user — write it down, you'll need it constantly.
  3. 3
    Keep the default port, 5432, unless something else is already using it.
  4. 4
    Once installed, open a terminal and connect with psql -U postgres to confirm it's running.

Try it

terminal — psql is Postgres's own command-line client
$ psql -U postgres
Password for user postgres: ********
psql (16.2)
Type "help" for help.

postgres=# CREATE DATABASE brightcart;
CREATE DATABASE
postgres=# \q
Common snag: "psql: command not found" usually means Postgres's bin folder isn't on your PATH. On Windows, search for "SQL Shell (psql)" in the Start Menu instead — it's installed alongside the database and skips the PATH issue entirely.

Tool Spotlight: A SQL Client (DBeaver / pgAdmin)

10 min

Tool Spotlight

DBeaver (or pgAdmin)

A visual window into your database — browse tables, run queries, and see results in a grid.

What it is

A SQL client is a GUI application that connects to a database server and lets you explore tables, write and run queries, and view results — without typing raw psql commands for everything.

Why data engineers use it

You'll write plenty of SQL directly, but a visual table browser makes it dramatically faster to sanity-check your data ("does this table actually have 45 rows like I expect?") before you write a single query.

How to get it running

  1. 1
    Download DBeaver Community Edition (free) from dbeaver.io — it works with every database you'll meet in this bootcamp, not just Postgres.
  2. 2
    Open DBeaver, click New Database Connection, choose PostgreSQL.
  3. 3
    Enter host localhost, port 5432, database brightcart, username postgres, and the password you set during install.
  4. 4
    Click Test Connection, then Finish.
Common snag: pgAdmin (bundled with the Postgres installer) does the same job and is a perfectly good alternative — use whichever one you already have open.

Loading BrightCart Into Postgres

Time to turn the .sql files from the Introduction into a real, queryable database. You'll run the schema file first (it creates empty tables) and then the seed file (it fills them with data).

terminal — run these from the folder containing the .sql files
$ psql -U postgres -d brightcart -f 01_schema.sql
CREATE TABLE
CREATE TABLE
CREATE TABLE
CREATE TABLE
CREATE INDEX
CREATE INDEX
CREATE INDEX

$ psql -U postgres -d brightcart -f 02_seed_clean_data.sql
INSERT 0 16
INSERT 0 14
INSERT 0 45
INSERT 0 63

Prefer clicking over typing?

In DBeaver or pgAdmin, you can instead open each .sql file and click "Execute Script" (not just "Execute" — that only runs one statement). Same result, different interface.

SELECT, WHERE, ORDER BY, LIMIT

Every SQL query starts the same way: tell it what columns you want, from which table, which rows qualify, and how to sort the result. This one pattern answers most of the questions a business ever asks.

the 5 most fundamental keywords in SQL, in the order you'll type them
SELECT first_name, last_name, city
FROM customers
WHERE country = 'Philippines'
ORDER BY city ASC
LIMIT 5;
ClauseWhat it does
SELECTwhich columns to return (or * for all of them)
FROMwhich table to read
WHEREfilters rows before they're returned
ORDER BYsorts the result — add DESC for descending
LIMITcaps how many rows come back
a realistic BrightCart question: "what's still waiting on us?"
-- Every order that is still pending, most recent first
SELECT order_id, order_date, payment_method
FROM orders
WHERE status = 'pending'
ORDER BY order_date DESC;

SQL strings use single quotes

WHERE status = "paid" will error in Postgres — use single quotes for text: WHERE status = 'paid'. Double quotes in SQL mean something entirely different (they quote column/table names, not values).

Aggregate Functions & GROUP BY

Aggregate functions collapse many rows into one summary number — count, sum, average, min, max. GROUP BY is what lets you get one summary per category instead of one summary for the whole table.

FunctionReturns
COUNT(*)number of rows
SUM(col)total
AVG(col)average
MIN(col) / MAX(col)smallest / largest value
how many orders are in each status?
SELECT status, COUNT(*) AS order_count
FROM orders
GROUP BY status
ORDER BY order_count DESC;
statusorder_count
paid39
pending3
cancelled2
refunded1
HAVING filters groups AFTER aggregation — WHERE can't do this, it runs before grouping
-- Total revenue per payment method, but only for orders worth summarizing
SELECT payment_method,
       SUM(quantity * unit_price) AS revenue,
       COUNT(*) AS line_items
FROM order_items oi
JOIN orders o ON o.order_id = oi.order_id
WHERE o.status = 'paid'
GROUP BY payment_method
HAVING SUM(quantity * unit_price) > 200
ORDER BY revenue DESC;

WHERE vs HAVING

WHERE filters individual rows before grouping happens. HAVING filters the groups after SUM/COUNT/etc. have already been calculated. If your condition mentions an aggregate function, it has to go in HAVING.

Chapter 5 Checklist — before moving on, you should be able to:

  • Explain what a relational database is and why it's more durable than a CSV file
  • Load the BrightCart schema and seed data into a real Postgres database
  • Write a SELECT ... WHERE ... ORDER BY ... LIMIT query from memory
  • Use COUNT, SUM, and AVG with GROUP BY
  • Explain the difference between WHERE and HAVING
06
WEEK 3–4 · SQL DEEP DIVE

Joins, Subqueries & Window Functions

Real BrightCart questions almost always need more than one table. This is how you combine them.

Primary Keys, Foreign Keys & the ERD

A primary key uniquely identifies each row in a table (customer_id in customers). A foreign key is a column in one table that points at a primary key in another (orders.customer_id points at customers.customer_id). This is the entire mechanism that lets SQL connect tables together.

JOIN Types

A JOIN combines rows from two tables based on a matching column. Which rows survive when there isn't a match depends entirely on which join type you pick — this is the single most important SQL concept to get comfortable with.

Join typeKeeps...
INNER JOINonly rows that match in both tables
LEFT JOINall rows from the left table, matched data if it exists, else NULL
RIGHT JOINall rows from the right table (rarely needed — just swap table order and use LEFT)
FULL JOINeverything from both sides, matched where possible

The "find what's missing" pattern

LEFT JOIN ... WHERE right_table.id IS NULL is how you find customers with no orders, products that never sold, or — later in Phase 2 — records that failed to load. Memorize this shape.

Window Functions

A window function calculates something across a set of related rows without collapsing them into one row the way GROUP BY does — you keep every row, and get a ranking, running total, or comparison alongside it. This is the tool that makes "top N per group" and "running total" questions easy.

FunctionReturns
ROW_NUMBER()1, 2, 3... with no ties
RANK()ties share a rank, next rank skips (1, 1, 3)
SUM() / AVG() OVER (...)running total / running average
LAG() / LEAD()the previous / next row's value

Chapter 6 Checklist — before moving on, you should be able to:

  • Explain the difference between INNER JOIN and LEFT JOIN in one sentence
  • Use LEFT JOIN ... WHERE x IS NULL to find "missing" records
  • Rewrite a nested subquery as a WITH CTE
  • Use ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...) to rank rows within a group
07
WEEK 4 · DIMENSIONAL MODELING

Building a Reporting Layer

The tables you query for daily operations aren't the tables you want to build dashboards on. Here's the design pattern that fixes that.

OLTP vs OLAP

customers, orders, and order_items are OLTP tables (Online Transaction Processing) — designed so BrightCart's checkout page can insert one order quickly and safely. A dashboard asking "what were our total sales by category, by month?" has completely different needs — that's OLAP (Online Analytical Processing), and the design pattern for it is called a star schema.

The Star Schema: Facts & Dimensions

A fact table holds the numbers you want to measure (sales_amount, quantity) plus foreign keys pointing out to dimension tables, which hold the descriptive context (who, what, when).

Chapter 7 Checklist — before moving on, you should be able to:

  • Explain OLTP vs OLAP in one sentence each
  • Draw (or describe) a star schema with one fact table and at least two dimensions
  • Build fact_sales and at least two dimension views from the BrightCart OLTP tables
08
PHASE 1 COMPLETE

Wrap-Up & What's Next

You went from an empty folder to a working reporting layer on a real database. Here's what that's actually worth.

What You Can Now Do

Python

Variables, control flow, functions, file I/O, and basic JSON/API handling — the full toolkit for scripting a data task.

Git & GitHub

Track changes with commits and publish a public portfolio employers can actually look at.

SQL

SELECT through window functions — enough to answer almost any business question a stakeholder asks.

Dimensional Modeling

Design a star schema and know when a view, materialized view, or table is the right tool.

Coming Up in Phase 2: Weeks 5–8

Phase 1 was about talking to data one script and one query at a time. Phase 2 is about making that repeatable — turning BrightCart's dirty_orders.csv into an automated pipeline that cleans itself, and moving your work from your laptop into the cloud.

Ready to keep going?

Phase 2 (pandas, ETL pipelines, cloud, warehousing) and Phase 3 (Airflow, Spark, Docker, capstone) continue below — the first chapter of each is free too.

Start Phase 2 →