1. Lighthouse

Level 6 · Boss: customer base profile

The request

From Jordan Reyes, Head of Marketing

“I'm planning next year's acquisition budget. I need monthly 2025 signups split into consumer and business, the top email providers our customers use, and a referral source breakdown where unknown sources are labeled, not blank.”

Your goal

Step 1 of 3. For customers with signup_date in 2025, return one row per month with: month (first day of the month, as a DATE), consumer_signups (segment 'consumer'), business_signups (segment 'business') and total_signups. Sort by month ascending.

  • Read from the customers tablenot done yet
  • Show exactly month consumer_signups business_signups total_signupsnot done yet
  • 12 rows, sorted as askednot done yet

Step 1 · your query

SQL

Loading the editor…

    ResultPreview of customers · press Run to see yours
    Preview of customers
    customer_idfirst_namelast_nameemailcity
    1OwenHudsonowen.hudson@icloud.comSan Francisco
    2DouglasMendozadmendoza69@gmail.comAtlanta
    3HenryBrookshbrooks@lakesidecafe.comLos Angeles
    4BrianGrahambgraham@bluecedarhomes.comSeattle

    Mote checks your result

    Press Run. Each row that matches the goal becomes a firefly in your lantern.

    Step 2 · your query

    SQL

    Loading the editor…

      ResultPreview of customers · press Run to see yours
      Preview of customers
      customer_idfirst_namelast_nameemailcity
      1OwenHudsonowen.hudson@icloud.comSan Francisco
      2DouglasMendozadmendoza69@gmail.comAtlanta
      3HenryBrookshbrooks@lakesidecafe.comLos Angeles
      4BrianGrahambgraham@bluecedarhomes.comSeattle

      Mote checks your result

      Press Run. Each row that matches the goal becomes a firefly in your lantern.

      Step 3 · your query

      SQL

      Loading the editor…

        ResultPreview of customers · press Run to see yours
        Preview of customers
        customer_idfirst_namelast_nameemailcity
        1OwenHudsonowen.hudson@icloud.comSan Francisco
        2DouglasMendozadmendoza69@gmail.comAtlanta
        3HenryBrookshbrooks@lakesidecafe.comLos Angeles
        4BrianGrahambgraham@bluecedarhomes.comSeattle

        Mote checks your result

        Press Run. Each row that matches the goal becomes a firefly in your lantern.

        Tables

        Harbor & Pine store data, ready to query.

        customers2,018 rows · 9 columns
        • customer_idinteger
        • first_namevarchar
        • last_namevarchar
        • emailvarchar
        • cityvarchar
        • statevarchar
        • signup_datedate
        • segmentvarchar
        • referral_sourcevarchar
        First 5 rows of customers
        customer_idfirst_namelast_nameemailcitystatesignup_datesegmentreferral_source
        1OwenHudsonowen.hudson@icloud.comSan FranciscoCA2022-06-02consumersearch
        2DouglasMendozadmendoza69@gmail.comAtlantaGA2022-06-02consumerNULL
        3HenryBrookshbrooks@lakesidecafe.comLos AngelesCA2022-06-04businessNULL
        4BrianGrahambgraham@bluecedarhomes.comSeattleWA2022-06-06businesspinterest
        5KyleJimenezkjimenez27@yahoo.comIndianapolisIN2022-06-07consumerNULL
        products120 rows · 8 columns
        • product_idinteger
        • namevarchar
        • categoryvarchar
        • subcategoryvarchar
        • unit_pricedecimal
        • unit_costdecimal
        • launched_ondate
        • is_activeboolean
        First 5 rows of products
        product_idnamecategorysubcategoryunit_priceunit_costlaunched_onis_active
        101Vale Velvet LoveseatFurnitureSofas1659.00NULL2023-09-11true
        102Alder Oak Coffee TableFurnitureTables919.00503.502024-05-16true
        103Orchard Rattan Dining ChairFurnitureChairs789.00429.872023-05-27true
        104Upland Oak BookcaseFurnitureStorage429.00214.702022-12-20true
        105Avery Leather Bed FrameFurnitureBeds449.00232.702021-08-31true
        orders12,000 rows · 7 columns
        • order_idinteger
        • customer_idinteger
        • order_datetimestamp
        • statusvarchar
        • channelvarchar
        • shipping_feedecimal
        • coupon_codevarchar
        First 5 rows of orders
        order_idcustomer_idorder_datestatuschannelshipping_feecoupon_code
        10011252023-01-01 20:51:19completedweb0.00NULL
        10021562023-01-02 07:58:46completedweb0.00NULL
        1003222023-01-02 10:25:27completedmarketplace4.99WELCOME10
        1004482023-01-02 11:21:51completedmarketplace4.99WELCOME10
        1005822023-01-02 11:43:36completedweb0.00WELCOME10
        order_items30,338 rows · 6 columns
        • order_item_idinteger
        • order_idinteger
        • product_idinteger
        • quantityinteger
        • unit_pricedecimal
        • discount_pctdecimal
        First 5 rows of order_items
        order_item_idorder_idproduct_idquantityunit_pricediscount_pct
        11001121931.990.00
        21001154311.990.00
        31002154411.990.00
        410021941149.000.00
        51002193219.990.00
        returns1,673 rows · 5 columns
        • return_idinteger
        • order_item_idinteger
        • returned_ondate
        • reasonvarchar
        • refund_amountdecimal
        First 5 rows of returns
        return_idorder_item_idreturned_onreasonrefund_amount
        1142023-01-10damaged83.69
        2422023-01-10wrong_item19.99
        31052023-01-14wrong_item10.99
        4242023-01-16changed_mind26.99
        51262023-01-16late20.99
        employees60 rows · 8 columns
        • employee_idinteger
        • first_namevarchar
        • last_namevarchar
        • departmentvarchar
        • titlevarchar
        • manager_idinteger
        • hire_datedate
        • salaryinteger
        First 5 rows of employees
        employee_idfirst_namelast_namedepartmenttitlemanager_idhire_datesalary
        1CatherineHaleExecutiveChief Executive OfficerNULL2016-03-01285000
        2RobertKimExecutiveChief Operating Officer12017-01-09235000
        3MeganCollinsSalesHead of Sales12018-04-16172000
        4ChloeGutierrezSalesAccount Executive32020-01-2172000
        5FrankBellSalesAccount Executive32023-05-2173000
        web_sessions40,000 rows · 7 columns
        • session_idinteger
        • customer_idinteger
        • started_attimestamp
        • devicevarchar
        • sourcevarchar
        • pages_viewedinteger
        • convertedboolean
        First 5 rows of web_sessions
        session_idcustomer_idstarted_atdevicesourcepages_viewedconverted
        1NULL2023-01-01 23:02:24mobilepaid1false
        2182023-01-01 15:07:49desktoppaid1false
        3NULL2023-01-01 16:55:46desktopdirect7false
        4NULL2023-01-01 12:43:28mobileorganic3false
        5NULL2023-01-01 13:06:21mobilesocial2false
        marketing_spend3,276 rows · 3 columns
        • spend_datedate
        • channelvarchar
        • amountdecimal
        First 5 rows of marketing_spend
        spend_datechannelamount
        2023-01-01paid492.99
        2023-01-01email37.38
        2023-01-01social200.53
        2023-01-02paid709.13
        2023-01-02email64.13

        The lesson

        This boss combines the chapter’s tools into a profile marketing can plan with.

        Step 1: signups by month and segment

        This is a small pivot: months down the side, segments across the top. DATE_TRUNC builds the month key and conditional aggregation splits the count:

        SUM(CASE WHEN segment = 'business' THEN 1 ELSE 0 END) AS business_signups

        signup_date is already a DATE, so the half-open range compares cleanly. Including total_signups lets the reader verify that the two segment columns add up, a small courtesy that builds trust.

        Step 2: email domains

        The domain is everything after the @. DuckDB and Postgres have SPLIT_PART(s, delimiter, n), which splits a string and returns the nth piece (1-based):

        SPLIT_PART(LOWER(TRIM(email)), '@', 2) AS domain

        Clean first, then split: ' Owen@ICloud.com' and 'owen@icloud.com' must land in the same group. Dialects vary a lot here:

        database domain of an email
        DuckDB, Postgres, Snowflake SPLIT_PART(email, '@', 2)
        BigQuery SPLIT(email, '@')[SAFE_OFFSET(1)] (0-based)
        MySQL SUBSTRING_INDEX(email, '@', -1)
        Any of them SUBSTRING(email FROM POSITION('@' IN email) + 1)

        A count of customer rows by domain is a first look, not a final answer: duplicate customer records inflate it slightly. You will deduplicate customers properly in chapter 7.

        Step 3: labeling unknowns

        COALESCE(a, b, ...) returns its first non-NULL argument. It is the standard way to give a NULL a label in a report:

        COALESCE(referral_source, 'unknown') AS referral_source

        Grouping by the coalesced value turns the NULL group into a readable “unknown” row. That row matters: if a third of customers have no recorded source, the attribution data has a gap, and Jordan should know before splitting a budget by source. Do not drop it with a WHERE.

        Use COALESCE for labels and for real defaults (a missing discount really is 0). Do not use it to hide unknown numbers: COALESCE(unit_cost, 0) would make unknown-cost products look 100% profitable.

        Dialect note

        COALESCE is standard and works everywhere. You will also see IFNULL(a, b) in MySQL and BigQuery, NVL in Oracle and Snowflake, and ISNULL in SQL Server. They take exactly two arguments; COALESCE takes any number.