SQL · Guides · October 30, 2026 · 11 min read

SQL vs Excel for data analysis: what each is good at and how to switch

A spreadsheet grid on one side of a desk and a database query window on the other, joined by an arrow

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:

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

  1. 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.
  2. 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.
  3. Then joins. Replace your VLOOKUPs, and check row counts before and after every join.
  4. 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.
  5. 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, 简体中文