Ian Klosowicz

The way to read and fix SQL you didn't write is to work backward from the output, not forward from the top. Start with what the final SELECT returns, break the query into pieces you can run on their own, figure out what one row actually represents, reproduce the problem before you touch anything, make the smallest change that fixes it, and check your result against the original output. Most of the job is understanding, not editing. Once you truly understand a query, the fix is usually one or two lines.
Inheriting a query you have never seen is one of the most common things an analyst does, and it is a real task in plenty of technical screens. Here is the process I use every time I open a model someone else built, broken into the order I actually do it.
Read the query from the bottom. The final SELECT tells you what the query is supposed to produce, and everything above it exists to serve that output. Starting at the top means reading 80 lines of setup with no idea what they are building toward.
Look at the last SELECT first and answer three questions:
Now you have a target. When you read the rest of the query, you're tracing how it builds toward that output instead of wandering through it. This one change, reading bottom to top, is the single biggest difference between people who can pick up a strange query and people who stall on it.
Run the query in parts instead of trying to understand it all at once. A query you did not write is a black box until you see what each piece returns, and the fastest way to see that is to execute the pieces.
How to break it down depends on how it is built:
Seeing the actual rows at each step turns guessing into checking. I do exactly this in Snowflake when I open a model to debug a number: I run each step and compare what it returns to what I expected, and the gap between those two is almost always where the bug lives.
Before you try to understand a messy query, reformat it. A query crammed onto long lines with no indentation hides its own structure, and you're not obligated to read it in the shape you found it.
Run it through a formatter, or do it by hand: each clause on its own line, columns stacked under SELECT, conditions indented under their JOIN and WHERE. This costs a minute and does two things at once. It makes the joins, filters, and grouping visible, and the act of reformatting forces you to look at every clause, so you notice the stray condition or the missing join as you go.
Reformatting is how you make a query legible enough to read. You're rewriting it into a form your eyes can parse, which is the first step to understanding what it does. Save the formatted version somewhere, because you'll read it many times before you are done.
Figure out what a single row represents at each stage, because that is the thing most bugs hide behind. The grain is the level of detail: one row per order, one row per customer, one row per customer per month. If you don't know the grain, you can't tell whether a number is right.
To find it, look at what makes a row unique. A GROUP BY tells you the grain of its output directly: GROUP BY customer_id, month means one row per customer per month. A join can change the grain without warning, fanning one row into several when the table it joins to has multiple matches per key.
Watch for the grain changing where you did not expect it:
Most wrong-number bugs in inherited SQL are grain bugs. Nail down what one row means at each step and the cause tends to show itself.
Reproduce the bug before you touch the query. If someone says a number is wrong, confirm it is wrong and pin down exactly how, because a fix you can't measure is a guess.
Get specific about what "wrong" means:
People send me inherited queries all the time asking why the output looks off, and the first thing I ask for is the specific row or number that is wrong. Half the time, finding that example is what reveals the cause, before anyone changes a line of SQL. You can't confirm a fix worked if you never measured the thing it was supposed to fix.
Once you know the cause, make the smallest change that addresses it and nothing more. The temptation with a query you find confusing is to rewrite the whole thing. Resist it, at least until the bug is fixed.
A big rewrite does two risky things at once: it tries to fix the bug and restructure the query, so if the result changes you can't tell which edit did it. A small, targeted change keeps the cause and effect clear. If the problem is a join fanning out rows, fix that join. If it is a missing filter, add that filter. Change one thing, then re-run.
There is a time to clean up an inherited query, and it is after the fix is in and verified, as a separate pass. Keeping the fix and the cleanup apart means that if something breaks, you know which change caused it. If you want to sharpen this instinct on realistic queries, working through messy SQL with guidance is part of the daily practice inside Analyst Hive.
Confirm the fix by comparing the new output to the old one, not by assuming it worked because the query runs. A query that runs clean can still be wrong, which is how the bug got there in the first place.
The checks that catch most mistakes:
Verifying against the old output is what separates a fix from a change that happens to run. Measure the same thing you measured when you reproduced the bug, and you'll know for certain whether you actually fixed it.
Where do I start when reading a SQL query I didn't write?
Start at the bottom, with the final SELECT. It tells you what the query produces, which gives you a target to read toward. Then work upward, running each CTE or subquery on its own to see what it returns. Reading top to bottom with no sense of the goal is what makes an unfamiliar query feel impossible.
How do I understand a long query with lots of CTEs?
Run each CTE on its own with SELECT * and a LIMIT, top to bottom, and look at what each returns. CTEs are already named steps, so reading their output in order shows you how the data is built up. Reformat the query first if it is messy, so the structure is visible while you work through it.
What is the grain of a query?
The grain is what a single row represents, like one row per order or one row per customer per month. It is the key to judging whether a result is correct. Joins can change the grain by fanning rows out, and aggregates collapse it back down. Most wrong-number bugs in inherited SQL come from a grain that changed where nobody expected it.
Should I rewrite a messy query or just fix it?
Fix it first with the smallest change that works, then clean up separately if it is worth it. Combining a fix with a rewrite makes it impossible to tell which edit changed the result if something breaks. Reformatting to read the query is fine, but hold structural rewrites until after the bug is fixed and verified.
How do I know my fix actually worked?
Compare the new output to the old one. Check that the row count moved the way you expected, that the specific wrong value is now right, and that rows which were already correct still are. If a total has to reconcile to a known figure, check it. A query running without errors is not proof the numbers are right.
How do I debug a query that returns too many rows?
Look at the joins first. Too many rows almost always means a join fanned out, because the table you joined to has more than one match per key. Run the query up to each join and watch where the row count jumps. That jump is your fan-out, and either a tighter join condition or aggregating the other table first will fix it.
Reading and fixing SQL you didn't write is a process, and it is one you get faster at with reps: start at the output, run the pieces, find the grain, reproduce the bug, change one thing, verify. It is also a skill that shows up on the job from week one and in plenty of interviews, which makes it worth practicing on purpose. Working through realistic, messy queries with someone pointing out the grain bug or the fan-out join is part of what Analyst Hive is built for: a 90-day daily-task program for aspiring data analysts who want to handle real SQL, including the queries they inherit.