▶ Free previewModuleSQL for analysts

Why analysts learn SQL even with Excel

Everything you have done so far used bisi-provisions-sales-clean.csv, a single flat table with branch, item, category and amount_ngn sitting conveniently in every row.

Real business data does not look like that. Open bisi-provisions.db and the same business looks like this:

TableRowsWhat's in it
sales2,010One row per line sold — but with IDs, not names
products20Product names, categories, cost and list prices
customers19Customer names, home branch, first purchase
branches3Branch names and opening dates

And the first row of sales reads:

sale_id  sale_date   receipt_no  branch_id  customer_id  product_id  qty  unit_price_ngn  payment_type  cashier
1        2026-01-02  R10001      B1         C00          P01         1    86500           POS           Amaka

No branch name. No item name. No category. And no amount column at all.

Somebody flattened that into the CSV you've been using. This module teaches you to do it yourself, which is what an analyst is actually hired to do.

Words in this lesson

WordWhat it means, in plain English
DatabaseData stored in linked tables, built to be queried.
QueryA question written in SQL.
SQLStructured Query Language, the language for asking databases questions.
TableA set of rows and columns, like a sheet.
Primary keyThe column that uniquely identifies a row, like product_id.
Foreign keyA column pointing at another table's key, like sales.product_id.
NormalisedEach fact stored once, in one place.

Four reasons analysts learn SQL

1. The data lives there. Almost every till system, bank portal, hospital record and web app stores its data in a database. The CSV you get is an export — someone's decision about what to include. Learn SQL and you stop waiting for exports, and stop accepting someone else's choice of columns.

2. Size. A spreadsheet struggles past a few hundred thousand rows (lesson 1.5). Databases handle millions without noticing. Bisi's half-year is 2,010 rows; five years across thirty branches would be far past comfortable in Sheets.

3. Repeatability. A query is text. Save it, re-run it next month, and it does exactly the same thing to the new data. No dragging, no ranges to update, no chance of a formula that quietly stopped at row 938. This is the change-log discipline from lesson 1.2 made automatic.

4. It is the most-asked-for skill on analyst job adverts. Not because it's hard, but because it's the boundary between someone who receives data and someone who can go and get it.

Why the database looks so inconvenient

The CSV repeats "Rice (50kg bag)" on all 42 rows where it sold. The database stores it once, in products, and refers to it as P01.

That's called normalising, and it has three practical consequences:

ConsequenceWhy it matters
Each fact is stored onceCorrect a misspelled product name in one place and every sale is fixed
Bad spellings are almost impossibleYou can't type "ikeja" into branch_id; it must match a real branch
You must join to see namesWhich is why lesson 6.4 exists

Look back at lesson 2.4, where you spent a whole lesson merging nine spellings of three branch names. That entire problem cannot happen in this database. The branch is B1, B2 or B3, and the name lives in one row of one table.

That is what people mean when they say a database enforces quality that a spreadsheet cannot.

The missing amount column

sales has qty and unit_price_ngn, but no amount. That is deliberate, and it is the same principle as lesson 2.2.

An amount is derived: quantity times unit price. Store it as well and you have two versions of one fact, which can disagree — exactly the disagreement you found on rows 58 and 428 of the raw export. Store only what you can't calculate, and calculate the rest on the way out:

SELECT qty * unit_price_ngn AS amount FROM sales;

But why is there a price in the sales table at all?

If products has list_price_ngn, why does each sale carry its own unit_price_ngn?

Because the list price is what a product costs today, and a sale must record what it cost on the day it sold. Prices change. If sales didn't store their own price, every historical figure would silently rewrite itself the next time Bisi put her prices up.

Hold on to that. In lesson 6.4 you'll find that four of Bisi's products did change price during this period, and that the products table hasn't been updated — a fact with real consequences for anyone analysing her second quarter.

Getting the file open

You already downloaded bisi-provisions.db in Module 1. If not, it's on the first lesson of this course with the other datasets.

The friendly option: DB Browser for SQLite. According to its website, DB Browser for SQLite (DB4S) is an open source visual tool for people who want to create, search and edit SQLite database files, giving a spreadsheet-like interface to the database in addition to a full SQL query facility. It works on Windows, macOS and most versions of Linux, and downloads are at sqlitebrowser.org/dl/.

Open the file, click Browse Data to look around, and Execute SQL to run queries.

The official option: the sqlite3 command line. SQLite's documentation describes sqlite3 as a command-line program that lets you run SQL statements interactively against a database, and says it is the officially supported way of accessing SQLite files interactively. You start it by typing sqlite3 followed by the database filename:

sqlite3 bisi-provisions.db

SQLite's docs also note that if the named file doesn't exist, a new empty database is created automatically — so check your spelling, or you'll get an empty database and a confusing set of zero results.

Start with DB Browser. The visual table list makes the structure obvious in a way the command line doesn't.

Your first three queries

Type these. They take a minute and they orient you completely.

SELECT * FROM branches;
branch_idbranch_nameopened_on
B1Yaba2021-03-01
B2Ikeja2023-06-15
B3Surulere2025-11-01
SELECT COUNT(*) FROM sales;

→ 2010

SELECT COUNT(*) AS lines, SUM(qty * unit_price_ngn) AS revenue
FROM sales
WHERE sale_date < '2026-04-01';

→ 938 lines, 11026300

That is the same 938 rows and the same ₦11,026,300 you proved in lesson 2.5. Two completely different tools, one answer. If you ever get a different number from a database than from a spreadsheet, one of them is wrong and it is your job to find out which.

Already a question worth asking

Look at that branches table again. Surulere's opened_on says 2025-11-01.

But every module since lesson 3.4 has told you Surulere's first sale was 2 February 2026. Three months apart.

SELECT b.branch_name, b.opened_on, MIN(s.sale_date) AS first_sale
FROM branches b
JOIN sales s ON s.branch_id = b.branch_id
GROUP BY b.branch_name, b.opened_on;
branch_nameopened_onfirst_sale
Yaba2021-03-012026-01-02
Ikeja2023-06-152026-01-02
Surulere2025-11-012026-02-02

Yaba and Ikeja are easy: they opened years before this data starts, so their first sale is simply the first day in the file. Surulere is not: the branch record claims November, and the sales say February.

Which is right? The data cannot tell you. Maybe the shop was fitted out in November and didn't trade until February. Maybe someone typed the wrong date into the branch record. Maybe November's sales were never migrated into this system.

The professional move is not to pick one. It is to write it down and ask:

"The branches table gives Surulere an opening date of 2025-11-01, but its first recorded sale is 2026-02-02. I've used February for all comparisons because that's when revenue starts, but this needs confirming — if sales from November to January exist somewhere, every Surulere figure in this report is understated."

You will meet this constantly. Reference tables and transaction tables disagree, because one is maintained by a person and the other is generated by a machine. Noticing is the job.

What SQL will and won't do for you

SQL is good atSQL will not
Filtering millions of rows fastTell you which question to ask
Combining tablesDecide whether a comparison is fair
Repeating exactly, foreverNotice that Surulere opened late
Aggregating and groupingFormat a chart for a shop owner

Everything in Modules 1 to 5 still applies. SQL is a faster way to get numbers; it is not a substitute for knowing whether the numbers mean anything.

Common mistakes

MistakeBetter
Treating the CSV export as "the data"It is someone's choice of columns; the database is the source
Expecting names in the sales tableIt stores IDs; join to get names (lesson 6.4)
Looking for an amount columnAmounts are derived: qty * unit_price_ngn
Mistyping the filename in sqlite3It creates an empty database instead of an error
Assuming the database is automatically correctSurulere's opening date disagrees with its sales
Thinking SQL replaces analytical judgementIt replaces the typing, not the thinking

The short version

  • Bisi's database has four tables: sales (2,010), products (20), customers (19), branches (3).
  • sales holds IDs, not names, and no amount column — amounts are qty * unit_price_ngn.
  • Normalising stores each fact once, which makes lesson 2.4's nine branch spellings impossible.
  • Each sale stores its own price, because list prices change and history must not rewrite itself.
  • Analysts learn SQL for access, size, repeatability, and because it's the most requested skill.
  • Tools: DB Browser for SQLite (visual, spreadsheet-like, plus full SQL) or the sqlite3 CLI (officially supported).
  • The database gives the same 938 lines / ₦11,026,300 you proved in Module 2.
  • Already found: branches.opened_on says Surulere opened 2025-11-01, but its first sale is 2026-02-02. Flag it and ask.

Try it yourself

  1. Open bisi-provisions.db in DB Browser for SQLite and look at all four tables in Browse Data.
  2. Run SELECT * FROM branches; and SELECT * FROM products;. Write down which column is each table's primary key.
  3. Run the Q1 count and sum. Confirm 938 and 11026300 against your Module 2 figure.
  4. Find the customers row with id C00. Write one sentence on what it represents and why it exists.
  5. Run the opening-date query. Write the note you'd send Bisi about Surulere, in two sentences.
  6. Count the rows in sales for the whole file, and explain in one sentence why it's 2,010 and not 938.
  7. Look at products and find the row for P01. Explain why the sales table stores a price as well.

You'll know you've got it when you can describe, without looking, what each of the four tables holds and how they connect — and when you'd rather be given the database than the spreadsheet.