🟢 How to Explore a Brand New Database in 15 Minutes


Hello Reader,

Real quick: if you haven't completed this subscriber survey -- now's your chance. Most people are looking to learn more about Agentic Analytics and AI topics. I want to build something that would be useful to you, based on the work I've been doing for the past year or so. Interested? Take 5 minutes to let me know what's on your mind.

Imagine it's your first week at a new job. Someone from IT sends over database credentials and a two-line email: "Here's read access to the warehouse. Let us know if you have questions."

That's it. No documentation, no data dictionary.

And, of course, the analyst before you left months ago.

I've seen it so many times. And most beginners freeze here. They stare at forty unfamiliar tables, then quietly close the laptop and go looking for docs that don't exist.

Instead, you can start asking the database small, friendly questions. Do it in the right order (that you'll learn below) and within 15 minutes you'll understand the business better than some people who've worked there for years.

This is the first framework I teach in SQL for Business Impact, and it comes first for a reason. I call it the Mister Rogers Blueprint. Fred Rogers approached every new topic on his show the same way, whether it was a crayon factory or a symphony orchestra: with curiosity, without assumptions, one small question at a time. And you can do the same thing with an unfamiliar database.

Let me show you the whole process against the Summit Adventures database (the fictional adventure tourism company I created to help people learn business analytics).

Step 1: "What's in the Neighborhood?"

First question, every time: what tables exist, and how big are they?

-- Step 1: Survey the neighborhood
SELECT 'customers' AS table_name, COUNT(*) AS rows FROM customers
UNION ALL SELECT 'expeditions', COUNT(*) FROM expeditions
UNION ALL SELECT 'guides', COUNT(*) FROM guides
UNION ALL SELECT 'expedition_instances', COUNT(*) FROM expedition_instances
UNION ALL SELECT 'bookings', COUNT(*) FROM bookings
UNION ALL SELECT 'payments', COUNT(*) FROM payments
UNION ALL SELECT 'guide_assignments', COUNT(*) FROM guide_assignments
ORDER BY rows DESC;

Nothing fancy. Just row counts.

But look what they tell you:

There are 2,173 payments for 1,600 bookings, so some bookings carry multiple payments. Deposits? Installments? And 100 expeditions produce 500 instances, so each expedition runs several times. The 648 guide assignments across 500 trips even suggest some trips get a second guide. Interesting!

You haven't done any real analysis yet and you already have three hypotheses about how this business works.

Step 2: "What Lives Here?"

Pick the table you're most curious about and look at a few rows:

-- Step 2: Meet the neighbors
SELECT * FROM customers LIMIT 5;

Scan for data types, NULL gaps, and value ranges. For example...

  • Do the ages look reasonable?
  • Do the emails look real?

Then check the categorical columns:

-- Step 2b: What categories exist?
SELECT experience_level, COUNT(*) 
FROM customers 
GROUP BY experience_level 
ORDER BY COUNT(*) DESC;

This query teaches you the vocabulary of the data. You can't filter on values you don't know exist. I've watched students write WHERE status = 'complete' when the database says 'completed', get zero rows back, and assume the data was broken.

Step 3: "How Do Things Connect?"

Now trace the relationships. Start with one booking and follow it through the system:

-- Step 3: Follow the relationships
SELECT
    b.booking_id,
    c.first_name || ' ' || c.last_name AS customer,
    e.expedition_name AS expedition,
    INITCAP(e.expedition_type::text) AS type,
    ei.start_date,
    b.status AS booking_status,
    COUNT(p.payment_id) AS payment_count,
    COALESCE(SUM(p.amount), 0) AS total_paid
FROM bookings b
INNER JOIN customers c
    ON b.customer_id = c.customer_id
INNER JOIN expedition_instances ei
    ON b.instance_id = ei.instance_id
INNER JOIN expeditions e
    ON ei.expedition_id = e.expedition_id
LEFT JOIN payments p
    ON b.booking_id = p.booking_id
GROUP BY
    b.booking_id,
    c.first_name,
    c.last_name,
    e.expedition_name,
    e.expedition_type,
    ei.start_date,
    b.status
ORDER BY b.booking_id
LIMIT 10;

Does that trail look familiar? It's the one from Your First JOIN, where we followed a single customer through her booking history. Customer to booking to trip instance to expedition, with payments attached. That article taught the mechanics of connecting tables.

This is where the mechanics pay off:

  • one query
  • five tables
  • and now you have a better understanding of the data model, step by step.

Step 4: "What Surprises Me?"

Now go hunting for things that don't match your expectations:

-- Step 4: Find the surprises
-- Bookings without payments
SELECT b.status, COUNT(*) AS bookings,
    COUNT(p.payment_id) AS with_payment,
    COUNT(*) - COUNT(p.payment_id) AS without_payment
FROM bookings b
    LEFT JOIN payments p ON b.booking_id = p.booking_id
GROUP BY b.status;

Cancelled bookings that still show payments usually mean there's a refund process hiding in the data. Confirmed bookings with no payment at all? Worth a conversation with someone in finance.

While you're at it, check the data quality:

SELECT
    COUNT(*) FILTER (WHERE email NOT LIKE '%@%.%') AS bad_emails,
    COUNT(*) FILTER (
        WHERE date_of_birth IS NULL
           OR date_of_birth > CURRENT_DATE
           OR date_of_birth < DATE '1920-01-01'
    ) AS suspicious_birth_dates,
    COUNT(*) FILTER (WHERE phone IS NULL) AS missing_phones
FROM customers;

This flags:

  • Invalid email format
  • Missing or invalid birth dates
  • Missing phone numbers

Step 5: "What Story Does This Tell?"

Finally, ask a business question. Something simple you're genuinely curious about:

SELECT
    INITCAP(e.expedition_type::text) AS type,
    COUNT(*) AS bookings,
    '$' || TO_CHAR(SUM(b.total_amount), 'FM999,999') AS revenue,
    '$' || TO_CHAR(ROUND(AVG(b.total_amount), 0), 'FM999,999') AS avg_booking
FROM expeditions e
JOIN expedition_instances ei
    ON e.expedition_id = ei.expedition_id
JOIN bookings b
    ON ei.instance_id = b.instance_id
WHERE b.status <> 'cancelled'
GROUP BY e.expedition_type
ORDER BY SUM(b.total_amount) DESC;

So there you go. 5 steps and 15 minutes and you have a good understanding of the database.

Now you can walk into a meeting and say cultural expeditions lead revenue on fewer bookings, while photography drives the most volume at the lowest per-booking value.

Where Beginners Go Wrong

Three mistakes I see over and over:

  1. Waiting for documentation. You don't need permission or a data dictionary to start. If you know how to ask, the database is the documentation.
  2. Jumping straight to the business question. Skip Steps 1 through 3 and your Step 5 query will run fine and return the wrong answer.
  3. Assuming the data is clean. It never is!

Why This Works Everywhere

This framework doesn't JUST work for SQL. You can use any tool and any data set that you come across in your work.

  1. Survey: What exists?
  2. Sample: What does it look like?
  3. Connect: How do things relate?
  4. Surprise: What's unexpected?
  5. Story: What business question can I answer?

The same sequence works just as well on a spreadsheet a colleague emails you or a Python DataFrame. That's why it comes first in SQL for Business Impact. Every other skill builds on the habit of exploring before concluding.

Try This Today

Find a dataset you've never explored.

A public one from Kaggle works, or a table at your company you've never queried.

Run the five steps and time yourself.

I bet you come in under 15 minutes.

If you find something interesting, hit reply and tell me about it.

Those replies are my favorite thing to read.

Until next time,

Brian

Brian Graves, creator of Analytics in Action

Say 👋 on X/Twitter, LinkedIn, or book a call with me. You can always reply to these emails. I check them all.

Starting With Data

Learn to build analytics projects with SQL, Tableau, Excel, and Python. For data analysts looking to level up their career and complete beginners looking to get started. No fluff. No theory. Just step-by-step tutorials anyone can follow.

Read more from Starting With Data

Hello Reader, This week's newsletter is different. No SQL code. No database queries. I lead a team of 30 data professionals, and we use tools like Claude and Copilot to get oriented, draft, review, and explain routine analysis. The useful part is moving faster without handing over the judgment your team relies on. We call this "human in the loop". Agentic Analytics means using AI as part of your analytical process: give it context, check its work, and keep responsibility for the decision. I...

Hello Reader, Happy 4th of July to my fellow Americans -- glad you're with me today. 🎇 I'm planning out newsletters for the rest of the year and want to start incorporating more Python, Tableau, and Agentic Analytics. Can you take this short survey for me? Thanks! If you've been working as an analyst, you'll be familiar with a request like this: "This weekly report is exactly what we needed. Can we get it every Monday morning, with a chart, emailed to the whole leadership team automatically?"...

Hello Reader, A while back, I shared some tips about formatting SQL output for spreadsheets. Today we're going the other direction. Doing the kind of analysis that makes spreadsheet formulas feel clunky. Here's the kind of request that analysts get every single day: "Show me monthly revenue and how the trend is moving." If you pull monthly revenue into a spreadsheet, you'd probably create a running total column, then a 3-month moving average column, then a month-over-month change column....