🟢 How to See a Customer's Complete Booking History (Without Excel)


Hello Reader,

One thing before we get into this week's tutorial on the magic of JOINs:

I launched a new job board -- just for data people! 🥳

It's called DataJobsOnline.com (creative, I know). And I have been building out the data base of jobs over the past few weeks:

You get the idea...it's a job board! For data professionals like you.

If you're not job hunting right now, but know someone that is please let them know about DataJobsOnline.com -- it's completely free! I just launched it a few weeks back so I'm open to feedback (just hit reply -- I read everything that comes in).

Spread the word!


Imagine this: Your marketing manager walks up and says, "I need to see what expeditions a specific customer has booked with us. Can you pull that? The email is...mlaurent903@university.edu"

You have their customer database. You have their bookings database. But the customer name is in one table and the booking details are in another.

This is where many people get stuck. "How do I connect these tables?"

Once you understand the pattern, you can write one JOIN query and get the answer in 30 seconds.

This is the skill that unlocks almost everything in business analytics. Let me show you how simple it actually is.

The Business Problem

Your company has customer information in one table:

  • Names
  • Email addresses
  • Phone numbers
  • Experience levels

And booking information in a different table:

  • Booking dates
  • Expedition names
  • Prices
  • Status

They're connected by one thing: customer_id.

Your job is to bring them together.

The Query That Connects Them

Here's what I ran against the Summit Adventures database (the fake adventure tourism company I created to help people learn business analytics):

-- Show a customer's complete booking history
SELECT 
    c.first_name || ' ' || c.last_name AS customer_name,
    c.email,
    c.experience_level,
    e.expedition_name,
    b.booking_date::date AS booked_on,
    b.status AS booking_status,
    p.amount AS amount_paid
FROM customers c
    INNER JOIN bookings b ON c.customer_id = b.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 
        AND p.payment_status = 'completed'
WHERE c.email = 'mlaurent903@university.edu'
ORDER BY b.booking_date DESC;

Here's what we found for Michelle Laurent:

  • Customer: Michelle Laurent, Expert experience level
  • Multiple total bookings spanning 2024-2025
  • 3 completed expeditions: Wilderness Wildlife Tour ($2,893), Landscape Lens Workshop ($954), Landscape Lens Workshop ($749)
  • 3 cancelled: Landscape Lens Workshop, Indigenous Peoples Tour (twice), Mountain Vista Trek
  • Total spent: $4,596 on completed bookings

Michelle is a high-value expert customer who has spent over $4.5K but shows a concerning pattern -- a 50% cancellation rate across 6 bookings. Her completed trips span safari and photography categories. Marketing should:

  • Focus on safari and photography offerings (proven interests)
  • Investigate cancellation reasons (scheduling conflicts? Better competitor offers?)
  • Implement retention strategy for this high-value but at-risk customer segment

Breaking Down All The JOINS

Line 10: Making the first connection

INNER JOIN bookings b ON c.customer_id = b.customer_id

This says: "For every customer, show me their bookings where the customer_id matches."

Think of it like matching puzzle pieces. The customer_id is the edge that connects customers to bookings.

Lines 11-12: More connections

INNER JOIN expedition_instances ei ON b.instance_id = ei.instance_id
INNER JOIN expeditions e ON ei.expedition_id = e.expedition_id

Now we're going deeper. Each booking connects to a specific trip instance, and each instance connects to the expedition details (name, type, description).

This is like following a trail: Customer → Booking → Trip Instance → Expedition Details

Line 13: The Optional Connection

LEFT JOIN payments p ON b.booking_id = p.booking_id 

Notice this says LEFT JOIN, not INNER JOIN. That's because some bookings might not have payments yet (or were cancelled before payment).

LEFT JOIN says: "Show the booking even if there's no payment." INNER JOIN would only show bookings that have payments.

Apply this Pattern Across Different Industries

This is the exact pattern you'll use at almost every company:

E-commerce:

FROM customers c
    INNERJOIN orders o ON c.customer_id = o.customer_id
    INNERJOIN order_items oi ON o.order_id = oi.order_id

SaaS:

FROM users u
    INNERJOIN subscriptions s ON u.user_id = s.user_id
    INNERJOIN subscription_plans sp ON s.plan_id = sp.plan_id

Retail:

FROM customers c
    INNERJOIN purchases p ON c.customer_id = p.customer_id
    INNERJOIN products pr ON p.product_id = pr.product_id

The structure stays the same. You're following the relationships from one table to the next.

The KEY Concept (Literally)

Every table has a unique identifier (usually something like customer_id, booking_id, product_id).

When that same field appears in another table, it's called a foreign key - it's the connection point.

Look for these patterns:

  • customers.customer_id = bookings.customer_id
  • bookings.booking_id = payments.booking_id
  • expeditions.expedition_id = expedition_instances.expedition_id

These are the puzzle edges that let you connect tables.

Common Beginner Mistakes

Mistake #1: Forgetting the ON clause

-- WRONG - This creates a mess
INNER JOIN bookings b

You must tell SQL HOW to connect the tables:

-- RIGHT
INNER JOIN bookings b ON c.customer_id = b.customer_id

Mistake #2: Using INNER JOIN when you need LEFT JOIN

INNER JOIN only shows rows where BOTH tables have matching data. LEFT JOIN shows all rows from the first table, even if the second table has no match.

If you want to see customers who haven't booked anything yet, you need LEFT JOIN.

Mistake #3: Not using table aliases

When you have multiple tables, always use aliases (c, b, e, p):

FROM customers c

This makes your query readable. Otherwise you're typing customers.customer_id everywhere instead of c.customer_id.

Try This Right Now

Open your company database. Find two related tables (usually connected by an ID field).

Try this pattern:

SELECT 
    table1.some_column,
    table2.some_column
FROM table1
    INNERJOIN table2 ON table1.id_field = table2.id_field
LIMIT 10;

Replace:

  • table1 with your first table name
  • table2 with your second table name
  • id_field with the matching column (like customer_id, order_id, etc.)

You'll see the two tables connected. That's your first JOIN.

JOINs are Used Everywhere

JOINs are how you answer 80% of business questions:

  • "Show me which customers bought which products"
  • "Which sales reps closed which deals?"
  • "What subscriptions do our enterprise customers have?"

All of these require connecting multiple tables. Once you understand the pattern, it becomes second nature.

Building Confidence, One Query at a Time

Here's the truth: JOINs feel scary until you write your first successful one.

I spent months avoiding multi-table queries. Every time a manager asked for analysis requiring multiple data sources, I'd freeze. "I'll get back to you," I'd say, then spend hours Googling and trying random approaches.

Then one day, it clicked. A colleague showed me: "It's just matching IDs, like connecting dots."

That simple reframe changed it for me.

I wrote my first successful 3-table JOIN. Then a 4-table JOIN. Within weeks, I was writing them like it was nothing.

The imposter syndrome around JOINs disappeared because I understood the concept: tables connect through matching IDs. That's it.

Once you understand that, JOINs become second nature.

You can learn this too. Start with two tables. Get comfortable. Then add a third. Before long, you'll wonder why it ever seemed hard.

Until next time,
Brian

P.S. Try writing your first JOIN this week and hit reply to tell me what you found. Celebrating small wins is how we build confidence. I read every response. And if you haven't yet, please let me know what you want to learn more about:

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

Hey Reader, Imagine you're a marketing analyst and your marketing director walks over and says: "I need our customers broken into spending tiers (VIP, Standard, and Low Value) so I can send different emails to each group." You have the data. But every customer just has a dollar amount. There's no "tier" column in the database. How do you create categories that don't exist yet? This is where CASE WHEN becomes one of the most useful tools in your SQL toolkit. The problem is that raw data...

Hey Reader, This week's newsletter is different. Not a regular "how to solve problem X with SQL" kinda thing. Because I've realized something: you can write perfect queries and still never get the career impact you want. You need "soft skills" like presenting your results to leadership in addition to "technical skills" like using CTEs and WINDOW functions. Let me show you what I mean. The Scenario Wednesday, 2pm. Q2 review meeting. CMO: "Our booking conversion is down. What's causing it?"...

Hello Reader, Imagine this scenario: Your CMO walks into Monday's meeting with a question: "I need to see our top 3 highest-revenue expeditions in EACH category - hiking, climbing, safari, cultural, and photography." Most analysts would write 5 separate queries, one per category. Or export to Excel and manually filter. An hour of work, minimum. Confident analysts write one query using window functions. Takes 3 minutes. Here's the skill that separates intermediate analysts from advanced ones....