1. Lighthouse

Level 8 · Boss: a clean customer view

The request

From Priya Shah, Head of Data

“Every team cleans customers differently, so their numbers never match. Let's build one clean customer view and use it for three reports: how many real people we have per segment, revenue by state, and which referral sources turn into buyers.”

Your goal

Step 1 of 3. Return one row per segment with: segment, raw_records (customer rows before deduplication), people (rows kept by the dedupe rule) and states (number of distinct clean states among the kept rows).

  • Read from the customers tablenot done yet
  • Show exactly segment raw_records people statesnot done yet
  • 2 rowsnot 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
        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
        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
        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

        Cleaning pays off when one cleaned table feeds many reports. This boss builds a clean customer view once, with every rule from the chapter, and answers three questions from it.

        The clean customer view

        Write the rules down before the SQL:

        • A person is a cleaned email, LOWER(TRIM(email)).
        • The surviving record is the earliest signup, ties broken by the lowest customer_id.
        • State is UPPER(TRIM(state)). Referral source is COALESCE(referral_source, 'unknown').
        • Revenue comes only from completed orders and lines with a positive quantity.
        WITH clean_customers AS (
          SELECT customer_id, UPPER(TRIM(state)) AS state
          FROM (
            SELECT *, ROW_NUMBER() OVER (
              PARTITION BY LOWER(TRIM(email)) ORDER BY signup_date, customer_id) AS rn
            FROM customers
          ) AS ranked
          WHERE rn = 1
        )

        Every step repeats this CTE. On the job it would be a saved view, and every dashboard would select from it.

        Step 1: how much did cleaning change?

        Before reporting, show the effect of the rules. Of 1,732 consumer rows, 1,716 are distinct people. Of 286 business rows, 284. Reporting this next to the numbers makes the cleaning auditable.

        Step 2: revenue by clean state

        Aggregate revenue per customer first, then join: one row per customer on both sides, so no fan-out. With raw state values, California would appear as CA, ' CA', 'ca' and 'CA ', with 234, 2, 2 and 2 buyers. After cleaning it is one row, the top state with 240 buying customers.

        Step 3: which sources turn into buyers?

        people counts everyone in the clean view; buyers counts those with at least one completed order. A LEFT JOIN keeps the people who never bought; COUNT(b.customer_id) skips their NULLs. The unknown group is still the biggest at 648 people, down from 666, because the 18 duplicate records all had no referral source. NULLIF protects the division in case a group is ever empty.

        Common mistakes

        • Inconsistent rules between steps. If step 2 uses the raw state, its total no longer matches step 1.
        • Joining orders before deduping. It works here only because duplicates have no orders. In real data, re-point the dropped ids to the kept id first.
        • INNER JOIN for buyers. It removes non-buyers, so every source shows 100%.

        In SQL Server

        The same query works with ISNULL or COALESCE and TOP 10 instead of LIMIT 10 (SELECT TOP 10 c.state, ... ORDER BY net_revenue DESC). Save the CTE as a view with CREATE VIEW clean_customers AS SELECT .... A view cannot hold the ORDER BY without TOP.

        Pick what may run on this site. You can change this any time from "Cookie settings" at the bottom of every page.