📊Data Science
Foundations · Lesson 3/8

SQL for Data — Ask Your Data Questions

35 min 1 exercise 1 quiz Graded labs: 1

What you'll learn

  • Why SQL is a must-have for data scientists
  • SELECT, WHERE, GROUP BY, ORDER BY
  • Aggregations: COUNT, SUM, AVG
  • You'll replicate a GROUP BY query in code

Data usually lives in a database 🗄️

You met SQL in the backend track — for data scientists it's just as essential, because company data almost always lives in SQL databases. Before you can analyze anything in pandas, you often have to pull it with SQL. It's consistently one of the most-requested skills in data job listings.

The analyst's core query pattern:

SELECT   city, AVG(age) AS avg_age, COUNT(*) AS people
FROM     users
WHERE    age >= 18
GROUP BY city
ORDER BY avg_age DESC;

Read it top to bottom like a sentence:

  • SELECT — which columns/summaries you want (here: city, average age, count).
  • FROM — which table.
  • WHERE — filter rows before grouping (adults only).
  • GROUP BY — collapse rows into groups (one row per city).
  • ORDER BY — sort the result (highest average age first).

The aggregation functions do the summarizing across a group:

  • COUNT(*) — how many rows
  • SUM(x) — total
  • AVG(x) — average
  • MIN(x) / MAX(x) — smallest / largest

This one query answers "what's the average age and headcount per city, for adults, ranked?" — the exact kind of question a business asks daily. Notice it's the same filter → group → aggregate → sort loop as pandas. SQL and pandas are two dialects of the same idea; data scientists fluently switch between them.

Combining tables: JOIN 🔗

Real databases split data across many tables to avoid repetition — an orders table doesn't repeat a customer's full name and email on every row; it just stores a customer_id and points to a separate customers table. To answer a real question, you usually need to combine them. That's what JOIN does.

SELECT   orders.id, customers.name, orders.amount
FROM     orders
JOIN     customers ON orders.customer_id = customers.id;

This says: "for every order, look up the matching customer (where the ids line up) and pull their name alongside the order." The most common type is an INNER JOIN (what plain JOIN means by default) — it only keeps rows that have a match in both tables. If an order references a customer_id that doesn't exist in customers, that order is silently dropped from the result.

That's often not what you want. A LEFT JOIN keeps every row from the first (left) table, filling in NULL for any customer fields it couldn't find a match for:

SELECT   orders.id, customers.name
FROM     orders
LEFT JOIN customers ON orders.customer_id = customers.id;
-- every order appears, even ones with a missing/deleted customer

Why this matters for a data scientist: choosing INNER vs LEFT JOIN silently changes how many rows end up in your analysis. Picking the wrong one is one of the most common, hardest-to-notice bugs in a data pipeline — your row count can shrink and nobody notices until a total looks wrong.

Exercise 1

Replicate SELECT category, SUM(amount) GROUP BY category. Given a list of (category, amount) sales, write total_by_category(sales) returning a dict of each category → its total amount.

Example: [("food", 10), ("toys", 5), ("food", 20)] → {"food": 30, "toys": 5}

Lab exercise 1

PythonGraded in Code Lab

Write the query. The script below builds a tiny SQLite database in memory (customers and orders) and runs your SQL. Your job is the query.

Standard input holds one number N. Return every customer whose total order amount is at least N, as two columns name and total (the sum of that customer's order amounts), ordered by total descending and then by name. Customers with no orders must not appear.

Use a bound parameter (?) for N instead of building the SQL string yourself. The script prints each row as name total.

Visible tests (4)
  • Customers who spent at least 100
  • A high threshold keeps only the biggest spender
  • Threshold 0 lists everyone who ordered
  • No customer reaches 5000
Example run
Input
100
Expected output
Chen 750
Amal 550
Bilal 120
Starter code
import sqlite3

n = int(input())

db = sqlite3.connect(":memory:")
db.executescript('''
CREATE TABLE customers (id INTEGER PRIMARY KEY, name TEXT, country TEXT);
CREATE TABLE orders (id INTEGER PRIMARY KEY, customer_id INTEGER, amount INTEGER);
INSERT INTO customers VALUES (1,'Amal','EG'),(2,'Bilal','JO'),(3,'Chen','SG'),(4,'Dana','AE'),(5,'Eli','US');
INSERT INTO orders (customer_id, amount) VALUES
  (1,120),(1,80),(1,300),(2,45),(2,55),(3,500),(3,250),(4,99),(1,50),(2,20);
''')

More lines in Code Lab: 7

Try in Code Lab

Hints and instant feedback are in Code Lab. Optional practice: it does not block finishing the lesson.

JavaScript and Python also run in your browser without an account; other languages use the secure server runner and need a free account.

Quick quiz

Question 1 of 3

In SQL, what does GROUP BY do?

Question 2 of 3

WHERE vs GROUP BY order — which is right?

Question 3 of 3

Why do data scientists need SQL?

Summary

  • SQL pulls data from databases; it's essential for data scientists
  • SELECT / WHERE / GROUP BY / ORDER BY + aggregations (COUNT/SUM/AVG)
  • Same filter→group→aggregate→sort loop as pandas
  • You replicated a GROUP BY SUM query

Practice: free interactive SQL at sqlbolt.com and mode.com/sql-tutorial. Next: Data Visualization — turning your query results into charts that tell a story.

Practice

Reading is not learning until you run something. Pick one.

Graded lab exercises

Open an example in Code Lab