learn-data-engineering-with-phoebe / Builder session 2 of 10
Learn Data Engineering with Phoebe · Builder track · Session 2 of 10

Connect and extract

Every pipeline starts at a source you did not build and cannot change. This session walks the source zoo - relational databases, files, object storage, APIs, logs, streams - and shows how you pull data out of each without touching it. You will extract from Daybreak's tables, simulate a dropped file, and read a JSON payload back, all live on DuckDB in your browser.

🟠 Builder track Practitioners: DE · analysts · DS · PMs Runs in your browser · DuckDB 45 min
0-3 · Recap 3-20 · The source zoo & extraction 20-42 · Build-along: ingest three shapes 42-45 · Q&A
Part 0

Where we are in the pipeline

In b1 you ran the whole lifecycle once - generation, ingestion, storage, transformation, serving. Now we zoom in on the very first seam: generation → ingestion. Data is born in source systems Daybreak's app team owns; your job is to connect to each one and copy data out cleanly, without ever changing the source. Get this wrong and every stage downstream inherits the mess.

Live - presented in session Self-study - read after class ▶ Live pipeline - editable & runnable Official sources covered
★ What you walk out with today A working feel for the six source shapes and how each is read differently, the habit of extracting only the columns you need, and three live extractions - a Parquet, a CSV, and a JSON round trip - proving you can land data from any source shape into a file you control.
Part 1 · covers DLAI Source Systems M1

The source zoo 6 min live

"Source system" is any place data is generated before it reaches you. There is not one kind - there is a zoo, and each animal is read a different way. A data engineer's first skill is recognising which shape is in front of them, because the read pattern changes completely from one to the next.

Six source shapes Relational DB Daybreak's tables Files (CSV / JSON) a dropped export Object storage S3 / GCS buckets API returns nested JSON Logs append-only lines Streams events, session b6 Extract step SELECT or a reader read-only, never write Landed file you now control You extract FROM sources; you never change them. The landed copy is yours to reshape.
🔍 Click to zoom - six source shapes converge on one extract step, landing a copy you own
LiveWhat each shape means for how you read it3 min

The shape of the source decides the read pattern. There is no universal connector - part of the craft is matching tool to shape.

  • Relational (Daybreak's tables): query with SQL. A SELECT is your extract. Structured, typed, easy - the friendliest source.
  • Files (CSV / JSON): someone drops an export in a folder. You read the file, not a database. Formats vary wildly.
  • Object storage (S3 / GCS): files at cloud scale. Same idea as local files, but you point a reader at a bucket path.
  • API: you call an endpoint over HTTP and get JSON back. Rate limits and pagination make this its own skill (card below).
  • Logs: append-only text lines, semi-structured, huge volume. Often parsed and batched.
  • Streams: unbounded events arriving continuously - a different mindset entirely, covered in b6.
Real world

You extract FROM sources, you don't change them. A source system belongs to another team and often runs the live product. Writing to it, locking its tables, or hammering it with heavy queries can take the app down. The golden rule: read what you need, land your own copy, and do all your work on the copy.

Self-studyWhy the source zoo exists at all2 min read

Each source shape was built for its own job, not for you. An app database is tuned for fast single-row writes, not big analytical scans. An API is built to serve a mobile screen, not to dump a million rows. Logs are optimised for cheap appends. None of them were designed to feed a warehouse - which is exactly why ingestion (b3) and storage (b4-b7) exist: to translate whatever the source hands you into something analytics can use.

Part 2 · the relational source

Extracting from a relational source 6 min live

Daybreak's core data lives in a relational database, the friendliest source shape. Your extract is a SELECT. The one decision that matters early: do you drag the whole table, or only the columns you actually need? Column discipline is free performance and it starts at extraction.

Full-table extract - drags every column id date status chan cust notes wide, slow, wasteful Column-subset extract - only what you need id date chan narrow, fast, cheap Don't drag columns you won't use. Trim at the SELECT and every later stage is lighter.
🔍 Click to zoom - trimming columns at extract time is free speed for every stage after
LiveExtract a trimmed projection of orders3 min

This is your extract from a relational source: a SELECT that names only the columns the pipeline needs, not SELECT *. Press ▶ Run (first run downloads the ~8 MB engine once, then cached).

SELECT order_id, order_date, channel
FROM orders
WHERE status = 'completed'
LIMIT 8;
LiveReading files - the "someone dropped a file" pattern3 min

Half your sources arrive as files, not databases. We can simulate that exactly: write orders out to a CSV in the browser's virtual file system, then read it back as if a partner team had dropped it in a folder. The extract is now a file reader, not a SQL query against the source.

COPY (SELECT * FROM orders) TO 'drop/orders.csv' (FORMAT CSV, HEADER);
SELECT order_id, order_date, status
FROM read_csv_auto('drop/orders.csv')
LIMIT 5;
Part 3 · semi-structured sources

Reading JSON, the API shape 5 min live

APIs almost always hand back JSON - a semi-structured shape with nested objects and arrays, not neat rows and columns. You cannot browse a browser out to an external API here, but you can practise the exact read: write Daybreak data to JSON, then read it back and watch DuckDB flatten it into a table.

LiveRound-trip customers through JSON3 min

Write customers to a JSON file, then read it with read_json_auto. An API response looks just like this file's contents - DuckDB infers the columns and flattens the structure into rows for you.

COPY (SELECT * FROM customers) TO 'api/customers.json' (FORMAT JSON);
SELECT customer_id, name, city, plan
FROM read_json_auto('api/customers.json')
LIMIT 5;
Self-studyAPIs in the wild - rate limits & pagination3 min read

The browser cannot call external APIs, so this part is concept, honestly labelled. When you extract from a real API three things bite you that a database never does:

  • Rate limits: the API caps how many calls you can make per minute. Blow past it and you get throttled or blocked. You add backoff and retry logic.
  • Pagination: the API returns 100 rows at a time with a "next page" token. A full extract means looping until the token runs out - not one query.
  • Nested JSON: a single response often nests orders inside customers inside a wrapper. You flatten it after landing, not during the call.
The honest pattern Land the raw JSON responses to files first, then parse and flatten from the files. Never mix "call the API" and "reshape the data" in one step - decouple them so a parsing bug does not force you to re-hit a rate-limited API.
Demo 1 of 2

Ingest three source shapes ★ 6 min · everyone builds

One script, three source shapes landed to three formats: orders to Parquet, customers to CSV, products to JSON. Then read all three back and prove each one landed with a single count summary. This is a miniature multi-source ingestion.

Extract + land: COPY three source tables out to three files, one per format.

Read back: point a reader at each landed file - Parquet path, read_csv_auto, read_json_auto.

Prove it: a UNION-style summary counts rows in each file, showing all three sources are safely yours.

-- extract + land three source shapes into three formats
COPY (SELECT * FROM orders)   TO 'land/orders.parquet' (FORMAT PARQUET);
COPY (SELECT * FROM customers) TO 'land/customers.csv' (FORMAT CSV, HEADER);
COPY (SELECT * FROM products)  TO 'land/products.json' (FORMAT JSON);

-- read each back and prove it landed
SELECT 'orders.parquet' AS landed_file, count(*) AS rows
FROM 'land/orders.parquet'
UNION ALL
SELECT 'customers.csv', count(*) FROM read_csv_auto('land/customers.csv')
UNION ALL
SELECT 'products.json', count(*) FROM read_json_auto('land/products.json')
ORDER BY landed_file;
Real world

This is what an ingestion job actually does. Swap the local files for S3 buckets and the tables for a database plus an API plus a log stream, and this exact pattern - extract each source to its own landed file, then confirm counts - is running nightly at real companies. The confirm-counts step is the difference between "the job ran" and "the job worked".

Demo 2 of 2

Your turn: land your own sources ★ 8 min · build your own

Each editor starts fresh from Daybreak's raw source. Write the extract, run it, read the count, fix if it is wrong. All three run against the source tables.

LiveQ1 · Extract completed orders to Parquet3 min

Extract only completed orders' id, date, and channel to a Parquet file, then read it back to confirm the count.

COPY (SELECT order_id, order_date, channel
      FROM orders
      WHERE status = 'completed')
TO 'out/completed.parquet' (FORMAT PARQUET);

SELECT count(*) AS completed_rows FROM 'out/completed.parquet';
LiveQ2 · Land order_items to CSV, then sum revenue4 min

COPY order_items to a CSV, read it back with read_csv_auto, and sum quantity times unit price to get total revenue - all from the landed file, not the source.

COPY (SELECT * FROM order_items) TO 'out/items.csv' (FORMAT CSV, HEADER);

SELECT ROUND(SUM(quantity * unit_price), 2) AS revenue
FROM read_csv_auto('out/items.csv');
Self-studyQ3 · Why extract to a file at all?3 min

A thought exercise, no SQL. Why land data to a file instead of just querying the source every time you need it? Three reasons that matter in production:

  • Decoupling: once data is in your file, your pipeline no longer depends on the source being up, fast, or unchanged. The source team can restart their database and your job keeps running on the landed copy.
  • Replay: if a downstream transform has a bug, you re-run it against the saved file - you do not have to re-extract from the source, which may have changed or aged out.
  • Not hammering prod: repeatedly running heavy analytical queries against a live app database can slow or crash the product. Extract once, then work on your own copy as much as you like.
Homework

Try it yourself - this week ◐ 20-30 min total

Source material

Official sources covered

This session teaches the working core of the DeepLearning.AI Data Engineering Professional Certificate (Joe Reis) and Reis & Housley's Fundamentals of Data Engineering, run on a live engine instead of slides. This page covers:

DLAI Source Systems, Data Ingestion & Pipelines - M1: Source systemsPart 1 · the source zoo and how each shape is read differently
DLAI Storage & Queries - M1: How data is readParts 2-3 · SELECT as extract, reading files and JSON
Fundamentals of Data Engineering (Reis & Housley) - source systemsPart 1 · the "you do not own generation" mindset, applied
Check yourself

Three questions before you go 🎯 ◐ 90 seconds

1 · What is a source system?

A source system is where data is born, upstream of your pipeline - and you usually do not own or control it. Recognising its shape decides how you read it.

2 · Why extract data to a file instead of querying the source every time?

A landed copy frees your pipeline from the source's uptime, lets you replay transforms without re-extracting, and keeps heavy queries off a live app database.

3 · How does a CSV source differ from a JSON source?

CSV is flat, tabular text. JSON carries nested objects and arrays - the shape APIs return - which DuckDB flattens into rows when you read it with read_json_auto.

Builder session 2 cheat sheet · pin this

Source systemAnywhere data is born before you: database, file, object storage, API, log, stream. You rarely own it.
The golden ruleYou extract FROM sources, you don't change them. Read what you need, land your own copy, work on the copy.
SELECT is your extractFor a relational source, a SELECT is the read. Name the columns you need - never blind SELECT *.
Column disciplineTrim columns at extract time. Narrow extracts are free speed for every stage downstream.
Files & CSVread_csv_auto('f.csv') reads a dropped file. Flat rows and columns; loses types on the way in.
JSON & APIsread_json_auto('f.json') flattens nested JSON to rows - the shape APIs return.
API gotchasRate limits, pagination, nesting. Land raw responses to files first, then parse - decouple the two.
Running projectLanding Daybreak's sources to files you control. Next: b3, ingestion patterns - full vs incremental.