1. Lighthouse

Level 2 · Check and compare coupon orders in SQL

The request

From Laura Bennett, Head of Finance

“We agreed the question: what each coupon cost us, what its orders were worth, and how they compare with orders on the same days without a code. Before any of that, show me the discounts on the order lines really match the codes. I don't want to build a budget on a broken column.”

Your goal

Step 1 of 3. Check the discounts. Join order_items to orders and label each line with coupon = COALESCE(o.coupon_code, 'none'). Return coupon, orders (distinct order ids), lines, min_discount and max_discount (the smallest and largest discount_pct), sorted by coupon. Use all orders, whatever the status.

  • Read from the orders tablenot done yet
  • Read from the order_items tablenot done yet
  • Show exactly coupon orders lines min_discount max_discountnot 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

        The coupon lives in two places: the code on orders.coupon_code and the discount on every line in order_items.discount_pct. Every number in this case is built from the second, so first prove it agrees with the first. One grouped query does it:

        SELECT COALESCE(o.coupon_code, 'none') AS coupon,
               MIN(i.discount_pct) AS min_discount,
               MAX(i.discount_pct) AS max_discount
        FROM order_items AS i
        JOIN orders AS o ON o.order_id = i.order_id
        GROUP BY coupon;

        If a code’s minimum and maximum are equal, all its lines carry the same discount. Three other things to look for in the same result: a code written two ways (welcome10 next to WELCOME10), orders without a code that still carry a discount, and how many orders have no code at all. COALESCE matters here: without it the NULL group is easy to filter away by accident.

        What each coupon cost

        Step 2 rolls the lines up to one row per order first, in a CTE, because a coupon is an order-level choice: the average order value must divide by orders, not by lines. From there the cost sheet is plain aggregation:

        • revenue: what customers paid, after the discount;
        • aov: revenue per order;
        • discount_cost: value before discount minus value after, the money given away;
        • revenue_share_pct: each code’s slice of all net revenue, divided by a scalar subquery (SELECT SUM(net) FROM order_totals) so the denominator is the same total for every row.

        Like for like: the same days

        Every coupon order averages less than an order without a code. That sounds damning, but the seasonal codes run in particular weeks, and Black Friday baskets are smaller across the board. The fair comparison puts each coupon next to the orders placed on the same days without a code.

        The second CTE, coupon_days, lists the days each seasonal code was used. Joining it back to order_totals on the day gives every order placed on those days, once per coupon. Conditional aggregation then splits them:

        AVG(CASE WHEN t.coupon = d.coupon THEN t.net END) AS coupon_aov,
        AVG(CASE WHEN t.coupon = 'none'   THEN t.net END) AS no_coupon_aov

        WELCOME10 and FREESHIP are left out of this step because they run every day: for them, “the same days” means the whole year.

        Reading the result

        coupon coupon_basket coupon_aov no_coupon_aov
        BFCM25 536.54 402.41 483.06
        HOLIDAY20 482.84 386.27 479.56
        SPRING15 634.59 539.40 574.96
        SUMMER15 671.75 570.98 658.07

        The basket before the discount is about the same as, or a little above, a normal order on those days. So the codes did not make people buy much more; they mostly paid less for a similar basket.

        Pitfalls

        • A coupon order counted once per line inflates orders; count in the order-level CTE.
        • Filtering quantity > 0 matters: three coupon lines have a zero or negative quantity.
        • FREESHIP’s cost is shipping, which is not in these totals. Mention it rather than reporting $0.

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