SQL · Guides · October 30, 2026 · 11 min read
SQL vs Excel for data analysis: what each is good at and how to switch

Excel and SQL are not competitors so much as tools for different stages of the same work. SQL is better at getting, joining and summarising data: it works on millions of rows, it reads data straight from where it lives, and a query is a written record of exactly what you did, which you can rerun tomorrow. Excel is better at looking at, adjusting and presenting a small result: what-if models, quick charts, manual corrections, and a file anyone in the company can open. Most analysts use both every day: SQL to produce a table of a few hundred rows, Excel or a BI tool to work with it.
If you already know Excel, you know more SQL than you think. A pivot table is GROUP BY. VLOOKUP is a JOIN. A filter is WHERE. This guide maps the Excel habits you have onto SQL, with queries that run on the airline database from the Nomto SQL course (104 airports, 33,121 flights, 262,788 bookings, money in US dollars). Every result table below is the real output.
The short answer: when to use which
| Task | Better in | Why |
|---|---|---|
| Pull data from a company database | SQL | the data lives there; exporting is a manual step that goes stale |
| More than about a million rows | SQL | an Excel sheet holds at most 1,048,576 rows |
| Joining several tables | SQL | one JOIN instead of a column of lookups per table |
| A report you rebuild every week | SQL | rerun the query; no copy-paste steps to forget |
| Checking how a number was produced | SQL | the query is the full recipe |
| What-if scenarios and financial models | Excel | change a cell, see every dependent number update |
| Quick one-off look at a small file | Excel | open, sort, eyeball, done |
| Manual edits, notes, colour-coding | Excel | SQL does not let you type into a result |
| Charts for a meeting in five minutes | Excel | built in and familiar to everyone |
| Sharing with people who do not code | Excel | everyone has it and can open it |
A useful rule: if the job is “find, combine and count”, start in SQL. If the job is “play with this table and show it to someone”, Excel is fine, and often better.
Why the row limit matters more than it looks
In this course database, ticket_flights (one row per passenger per flight segment) has 1,045,726 rows, which only just fits in a sheet. The app events table does not fit at all:
SELECT COUNT(*) FROM app_events;
| count_star() |
|---|
| 1494723 |
And fitting is not the same as working comfortably: a workbook with a million rows of lookups and formulas recalculates slowly. In SQL the size of the table barely changes how you write the query.
Filters → WHERE
In Excel you click the filter arrow and tick values. In SQL you write the condition. Bookings made in August for $1,000 or more, and how much they add up to:
SELECT COUNT(*) AS bookings, SUM(total_amount) AS revenue
FROM bookings
WHERE book_date >= '2026-08-01'
AND total_amount >= 1000;
| bookings | revenue |
|---|---|
| 16081 | 29341684.00 |
The equivalents:
| Excel filter | SQL |
|---|---|
| tick “Arrived” and “Departed” | WHERE status IN ('Arrived', 'Departed') |
| Number filter: greater than or equal | WHERE total_amount >= 1000 |
| Text filter: begins with | WHERE city LIKE 'Novo%' |
| Date filter: between | WHERE book_date >= '2026-08-01' AND book_date < '2026-09-01' |
| (Blanks) | WHERE actual_departure IS NULL |
One difference catches Excel users: in SQL an empty cell is NULL, and = NULL never matches anything. Use IS NULL.
Sorting and Top 10 → ORDER BY and LIMIT
“Sort largest to smallest, look at the top five” is:
SELECT book_ref, book_date, total_amount
FROM bookings
ORDER BY total_amount DESC
LIMIT 5;
In Excel, sorting changes the sheet. In SQL it only changes this result; the table stays as it was.
VLOOKUP / XLOOKUP → JOIN
In flights the airports and aircraft appear only as codes. In Excel you would add three lookup columns, each pointing at another sheet. In SQL it is three joins in one query:
SELECT f.flight_no,
f.scheduled_departure,
dep.city AS from_city,
arr.city AS to_city,
a.model
FROM flights f
JOIN airports dep ON dep.airport_code = f.departure_airport
JOIN airports arr ON arr.airport_code = f.arrival_airport
JOIN aircrafts a ON a.aircraft_code = f.aircraft_code
WHERE f.departure_airport = 'SVO'
AND f.scheduled_departure >= '2026-08-01'
AND f.scheduled_departure < '2026-08-02'
ORDER BY f.scheduled_departure, f.flight_no
LIMIT 4;
| flight_no | scheduled_departure | from_city | to_city | model |
|---|---|---|---|---|
| PG0120 | 2026-08-01 09:00:00 | Moscow | Mirny | Boeing 737-300 |
| PG0591 | 2026-08-01 09:00:00 | Moscow | Tomsk | Sukhoi Superjet-100 |
| PG0241 | 2026-08-01 09:40:00 | Moscow | Cheboksary | Sukhoi Superjet-100 |
| PG0677 | 2026-08-01 09:40:00 | Moscow | Makhachkala | Boeing 737-300 |
Note that the same airports table is joined twice, once for each end of the flight, with different aliases. In Excel that would be two separate lookups against the same sheet.
Two behaviours differ from VLOOKUP, and both matter:
No match. VLOOKUP shows #N/A. An inner JOIN silently drops the row. If you want to keep it with an empty value, use LEFT JOIN. Here is the one aircraft model with no flights, which an inner join would hide:
SELECT a.model
FROM aircrafts a
LEFT JOIN flights f ON f.aircraft_code = a.aircraft_code
WHERE f.flight_id IS NULL;
| model |
|---|
| Airbus A320-200 |
Several matches. VLOOKUP returns the first match and ignores the rest. A JOIN returns every match, repeating the row once for each. A booking can have up to five tickets:
SELECT b.book_ref, b.total_amount, COUNT(t.ticket_no) AS tickets
FROM bookings b
JOIN tickets t ON t.book_ref = b.book_ref
GROUP BY b.book_ref, b.total_amount
ORDER BY tickets DESC, b.book_ref
LIMIT 3;
| book_ref | total_amount | tickets |
|---|---|---|
| 070133 | 1133.00 | 5 |
| 070682 | 8012.00 | 5 |
| 111C34 | 2120.00 | 5 |
So if you join bookings to tickets and then sum total_amount, each booking is counted once per ticket:
SELECT (SELECT SUM(total_amount) FROM bookings) AS correct,
SUM(b.total_amount) AS after_join
FROM bookings b
JOIN tickets t ON t.book_ref = b.book_ref;
| correct | after_join |
|---|---|
| 207669809.00 | 343206192.00 |
This is the most common SQL mistake people make when they come from Excel, because VLOOKUP never multiplies rows. Before joining, ask: for one row on my left, how many rows can match on the right? The SQL joins guide covers this in detail.
Pivot table → GROUP BY
Rows area = GROUP BY columns. Values area = aggregates. Revenue by cabin class, with each class’s share of the total (in a pivot: “Show values as % of grand total”):
SELECT fare_conditions,
COUNT(*) AS segments,
SUM(amount) AS revenue,
ROUND(100.0 * SUM(amount) / SUM(SUM(amount)) OVER (), 1) AS pct_of_revenue
FROM ticket_flights
GROUP BY fare_conditions
ORDER BY revenue DESC;
| fare_conditions | segments | revenue | pct_of_revenue |
|---|---|---|---|
| Economy | 920793 | 146956844.00 | 70.8 |
| Business | 107642 | 55051796.00 | 26.5 |
| Comfort | 17291 | 5661169.00 | 2.7 |
That ran over a million rows. SUM(SUM(amount)) OVER () is a window function that puts the grand total next to each group; window functions explains how.
A pivot with a Columns area (statuses down, months across) is one conditional count per column:
SELECT status,
COUNT(*) FILTER (WHERE EXTRACT(MONTH FROM scheduled_departure) = 7) AS jul,
COUNT(*) FILTER (WHERE EXTRACT(MONTH FROM scheduled_departure) = 8) AS aug,
COUNT(*) FILTER (WHERE EXTRACT(MONTH FROM scheduled_departure) = 9) AS sep,
COUNT(*) AS total
FROM flights
GROUP BY status
ORDER BY total DESC;
| status | jul | aug | sep | total |
|---|---|---|---|---|
| Arrived | 10821 | 5886 | 0 | 16707 |
| Scheduled | 0 | 10345 | 5038 | 15383 |
| On Time | 0 | 518 | 0 | 518 |
| Cancelled | 5 | 6 | 403 | 414 |
| Departed | 0 | 58 | 0 | 58 |
| Delayed | 0 | 41 | 0 | 41 |
Here Excel is honestly more convenient: a pivot builds the columns for you, while in SQL you list them. Many analysts do exactly this split: SQL produces the long table (status, month, count), and the pivot in Excel or the BI tool spreads it into columns.
The pivot’s Filters area is WHERE (before grouping), and “show only groups where the total is over N” is HAVING. See GROUP BY vs HAVING.
Formulas → expressions
| Excel | SQL |
|---|---|
=A2*B2, =ROUND(A2, 2) |
price * qty, ROUND(amount, 2) in SELECT |
=IF(A2>=1000, "large", "small") |
CASE WHEN total_amount >= 1000 THEN 'large' ELSE 'small' END |
=IFERROR(A2/B2, "") |
a / NULLIF(b, 0) |
=COUNTIF(range, "Cancelled") |
COUNT(*) FILTER (WHERE status = 'Cancelled') |
=SUMIFS(amount, class, "Business") |
SUM(amount) FILTER (WHERE fare_conditions = 'Business') |
=A2 & " " & B2 |
a || ' ' || b |
=LEFT(A2, 3), =UPPER(A2), =TRIM(A2) |
LEFT(s, 3), UPPER(s), TRIM(s) |
=EOMONTH, =YEAR, =A2-B2 on dates |
DATE_TRUNC, EXTRACT(YEAR FROM d), d1 - d2 |
| Remove Duplicates | SELECT DISTINCT / COUNT(DISTINCT col) |
a running total column =C1+B2 |
SUM(x) OVER (ORDER BY day) |
The biggest mental shift: an Excel formula lives in one cell and you drag it down. A SQL expression is written once and applies to every row. Nested IFs become a CASE, which reads top to bottom:
SELECT book_ref, total_amount,
CASE WHEN total_amount >= 1000 THEN 'large'
WHEN total_amount >= 300 THEN 'medium'
ELSE 'small'
END AS size
FROM bookings
ORDER BY book_ref
LIMIT 4;
| book_ref | total_amount | size |
|---|---|---|
| 00000F | 2657.00 | large |
| 000012 | 379.00 | medium |
| 000068 | 181.00 | small |
| 000181 | 1318.00 | large |
And the column you would add next to it, then pivot on, is one more GROUP BY:
SELECT CASE WHEN total_amount >= 1000 THEN 'large'
WHEN total_amount >= 300 THEN 'medium'
ELSE 'small'
END AS size,
COUNT(*) AS bookings
FROM bookings
GROUP BY size
ORDER BY bookings DESC;
| size | bookings |
|---|---|
| medium | 130009 |
| small | 68305 |
| large | 64474 |
Remove Duplicates, for example how many distinct cities have an airport:
SELECT COUNT(*) AS airports, COUNT(DISTINCT city) AS cities
FROM airports;
| airports | cities |
|---|---|
| 104 | 101 |
Where Excel genuinely wins
It is worth being honest about this, because “SQL is better” is not true for everything:
- Modelling. A budget, a forecast or a pricing scenario where you change one assumption and watch the rest move is what spreadsheets were built for. SQL has no cells to change.
- Small, messy, one-off data. A list someone emailed you, with merged cells and notes in the margin, is quicker to clean by hand than to load into a database.
- Manual input. Adding a comment, correcting one value, colouring the rows you want to discuss.
- Communication. Your manager can open a workbook. Most cannot open a query.
- Power Query and the Data Model. Modern Excel can load and join larger data than a sheet holds. If your company runs on Excel, these features close part of the gap, though the logic is still harder to review than a query.
What SQL gives you in return is reproducibility. A spreadsheet that went through twenty manual steps cannot easily be checked or rebuilt; a query can be read line by line, rerun on next month’s data, and reviewed by a colleague.
How to switch: a practical path
- Learn to read one row. Before every query, know what one row of each table means (a flight, a booking, a ticket segment). This replaces the habit of scrolling the sheet.
- Start with what you already do in Excel. Filter →
WHERE, sort →ORDER BY, pivot →GROUP BY. Redo one of your real weekly reports in SQL. - Then joins. Replace your VLOOKUPs, and check row counts before and after every join.
- Keep Excel for the last step. Export the small, final result to a sheet for charts and comments. That is not cheating; it is how most analysts work.
- Add
CASE, CTEs and window functions once the basics feel routine. The SQL cheat sheet has all of them on one page.
In the Nomto SQL room you run these queries in the browser on the airline database, with nothing to install, and the course starts from exactly the Excel-like operations: selecting columns, filtering, sorting and grouping. The basic SQL queries guide is a good first read.
FAQ
Is SQL harder to learn than Excel?
The basics are not. If you can build a pivot table and use VLOOKUP, you can learn SELECT, WHERE, GROUP BY and JOIN in a few weeks. What takes practice is thinking in whole tables instead of single cells, and noticing when a join multiplies rows.
Can SQL replace Excel completely?
For getting and summarising data, mostly yes. For modelling, manual edits and quick presentation, no. Most analysts use SQL to produce a small result and a spreadsheet or BI tool to work with it.
Do data analysts still use Excel?
Yes, widely, especially for the last step of an analysis and for sharing results. SQL is what gets the data; Excel, Google Sheets or a BI tool is often what people see.
What is the SQL equivalent of VLOOKUP?
A JOIN on the lookup column: FROM flights f JOIN aircrafts a ON a.aircraft_code = f.aircraft_code. Use LEFT JOIN if you want to keep rows with no match, the way VLOOKUP keeps the row and shows #N/A.
Also in Español, Português (Brasil), Tiếng Việt, 简体中文