1. Lighthouse

Level 2 · Check and measure the channel mix in SQL

The request

From Daniel Foster, Mobile Product Manager

“We agreed the question: each channel's share of completed orders and revenue by year, and whether the app's growth is new business or orders moving over from the website. Start by showing me the data is fit for it. December 2025 isn't fully settled yet.”

Your goal

Step 1 of 3. Check the data. For each channel, count all orders by status: return channel, completed, cancelled, refunded, pending, and pending_late (pending orders placed on or after 2025-12-22), sorted by channel. Use the orders table alone.

  • Read from the orders tablenot done yet
  • Show exactly channel completed cancelled refunded pending pending_latenot done yet
  • 3 rows, sorted as askednot 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 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.

        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

        What you are checking, and why

        Before measuring shares, make sure the two columns the analysis rests on are clean: channel and status. One query does both, by pivoting statuses into columns with conditional counts:

        COUNT(CASE WHEN status = 'completed' THEN 1 END) AS completed

        COUNT skips NULLs, and a CASE without ELSE returns NULL when the condition fails, so only matching rows are counted. Three things to read in the result:

        • The number of rows. One per channel value. A fourth row such as App would mean casing to fix.
        • Pending orders. Daniel warned that December is not settled. pending_late shows how many pending orders are simply recent ones.
        • Cancellations by channel. If one channel cancelled far more, using completed orders would tilt the mix. Here they are all close to 6%.

        Shares within each year

        A share of total needs each row’s group total next to it. A window function does that without collapsing the rows:

        orders * 100.0 / SUM(orders) OVER (PARTITION BY year)

        PARTITION BY year restarts the total for every year, so each year’s three shares add up to 100%. Aggregate to year and channel in a CTE first, then apply the window to the result.

        Mix or growth?

        A rising share has two possible stories. Either the app grew while the others held steady, or the app held steady while the others fell. Usually it is some of both, and only the counts tell them apart. Step 3 puts 2024 and 2025 side by side with conditional aggregation:

        COUNT(CASE WHEN year = 2024 THEN 1 END) AS o24,
        COUNT(CASE WHEN year = 2025 THEN 1 END) AS o25

        Reading the result

        channel orders_2024 orders_2025 orders_change_pct aov_2025
        app 1239 1904 53.7 523.27
        marketplace 543 630 16.0 515.01
        web 1891 1761 -6.9 557.03

        The store grew in 2025 and the marketplace kept pace with it. The app grew far faster, and the website shrank. The data cannot say which customers moved; a customer-level follow-up would show how many 2024 web buyers ordered through the app in 2025.

        Pitfalls

        • Counting COUNT(*) after joining to order_items counts lines, not orders. Use COUNT(DISTINCT o.order_id) or roll up to orders first.
        • A share without the counts behind it can hide a shrinking market.
        • shipping_fee is what the customer paid for delivery, not the fee the marketplace charges us.

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