1. Lighthouse

Level 8 · Boss: the inherited KPI pack

The request

From Priya Shah, Head of Data

“The analyst who built our monthly KPI pack has left, and Catherine wants it for the board on Friday. All three queries run, and all three look off to me. Please fix them and tell me what was wrong.”

Your goal

Step 1 of 3. For completed orders placed in 2025, return one row per month with: month (a DATE, first of the month), orders, net_revenue (rounded to 2) and shipping_revenue (each order's shipping_fee counted once, rounded to 2). Sort by month ascending.

  • Read from the orders tablenot done yet
  • Read from the order_items tablenot done yet
  • Show exactly month orders net_revenue shipping_revenuenot done yet

Step 1 · your query

SQL

Loading the editor…

    ResultPreview of orders · press Run to see yours
    Preview of orders
    order_idcustomer_idorder_datestatuschannel
    10011252023-01-01 20:51:19completedweb
    10021562023-01-02 07:58:46completedweb
    1003222023-01-02 10:25:27completedmarketplace
    1004482023-01-02 11:21:51completedmarketplace

    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 orders · press Run to see yours
      Preview of orders
      order_idcustomer_idorder_datestatuschannel
      10011252023-01-01 20:51:19completedweb
      10021562023-01-02 07:58:46completedweb
      1003222023-01-02 10:25:27completedmarketplace
      1004482023-01-02 11:21:51completedmarketplace

      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.

        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
        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
        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

        Inherited reports rarely have just one bug. This boss gives you three queries from the same pack. Each runs, each returns a tidy table, and each tells the board something false.

        Step 1: monthly revenue and shipping

        Two bugs from this chapter. The join to order_items repeats every order’s shipping_fee once per line, so shipping is inflated in every month: January shows $1,223.15 instead of $752.17. And BETWEEN '2025-01-01' AND '2025-12-31' stops at midnight on Dec 31, so December has 623 orders instead of 634.

        Fix the fan-out first by summing lines per order in a CTE. Then switch to a half-open range. The order count, the revenue and the shipping total should all match the order system afterward.

        Step 2: cancellation rate per channel

        The starter returns 12 rows for 3 channels. GROUP BY channel, status splits each channel into one row per status, so the cancelled row shows 100% and every other row 0%. That is a grain bug: the extra column makes the grouping finer than the question.

        Fix the grouping and a second bug appears: // is integer division, so app’s rate reads 6 instead of 6.23. Use 100.0 * and divide with /.

        Step 3: repeat rate per segment

        A repeat rate of exactly 100% for both segments is impossible. The HAVING COUNT(*) >= 2 inside the CTE removes one-time buyers before the outer query counts buyers, so numerator and denominator are the same group. This is the WHERE versus HAVING lesson in another form: a filter applied at the wrong step changes what the aggregate means.

        Remove the HAVING. The outer FILTER (WHERE b.orders >= 2) already restricts the numerator. Business has 253 buyers and 188 repeat buyers, consumer 1,194 and 690.

        How to work through a broken pack

        1. For each query, say in one sentence what one output row should be. Compare with the row count.
        2. Rebuild one number from the simplest possible query: shipping from orders alone, December’s count with a half-open range, buyers with a plain COUNT(DISTINCT customer_id).
        3. Fix one bug, rerun, compare again. A second bug often hides behind the first.

        Common mistakes

        • Treating COUNT(DISTINCT o.order_id) in step 1 as proof that the query has no fan-out. It protects the count, not the sums.
        • Fixing step 2’s division but not its grouping: 0% and 100% rows are still wrong.
        • Moving step 3’s HAVING into the outer query: that filters segments, not customers.

        In SQL Server

        T-SQL has no FILTER clause. Write conditional counts with CASE, which also works in DuckDB:

        SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) AS cancelled_orders,
        ROUND(100.0 * SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) / COUNT(*), 2)

        On the job

        Write down what was wrong in each query when you hand the pack back. The board will ask why last quarter’s numbers changed, and “shipping was counted once per line” is an answer they can accept.

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