1. Lighthouse

Level 2 · Check and compare the price rise in SQL

The request

From Thomas Whitaker, Head of Merchandising

“We agreed the question: for the products raised on March 1, units and revenue from March to August against the same months last year, next to the products we left alone. First show me you've found the right products and that the change really happened on March 1.”

Your goal

Step 1 of 2. Check the change. For every order line of the products whose price rose, label period ('before' if order_date < '2025-03-01', else 'after') and price_paid ('old' if the line's unit_price equals the product's lowest price, else 'new'). Return period, price_paid and lines (the number of order lines), sorted by period descending, then price_paid descending. Use all order lines, whatever the status.

  • Read from the orders tablenot done yet
  • Read from the order_items tablenot done yet
  • Show exactly period price_paid linesnot 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 products · press Run to see yours
      Preview of products
      product_idnamecategorysubcategoryunit_price
      101Vale Velvet LoveseatFurnitureSofas1659.00
      102Alder Oak Coffee TableFurnitureTables919.00
      103Orchard Rattan Dining ChairFurnitureChairs789.00
      104Upland Oak BookcaseFurnitureStorage429.00

      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
      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
      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
      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 you compare anything, prove that you are looking at the right products and the right date. Thomas says “about 30 products, March 1”. The data can confirm both, because order_items stores the price paid on every line, while products.unit_price only holds today’s list price.

      A product whose price changed has two different prices across its order lines. That is a grouped condition, so it belongs in HAVING:

      SELECT product_id, MIN(unit_price) AS old_price, MAX(unit_price) AS new_price
      FROM order_items
      GROUP BY product_id
      HAVING MIN(unit_price) <> MAX(unit_price);

      This returns 29 products. The check in step 1 then labels each of their lines twice, by date and by price, and counts the combinations. If the change happened cleanly on March 1, only two combinations exist: before with the old price, after with the new one. Any other row is a finding in itself.

      Like for like, with a control group

      Step 2 answers the agreed question. Three choices make the comparison fair:

      • Same months, a year apart. March to August 2025 against March to August 2024. The season is the same on both sides.
      • Like-for-like products. Only products launched before January 1, 2024, so every product was on sale for the whole of both windows.
      • A control group. Every other product is labelled unchanged. Its growth is the baseline: what the store did anyway.

      The pattern for two periods side by side is conditional aggregation, the same one used for year-over-year reports:

      SUM(CASE WHEN yr = 2024 THEN quantity END) AS u24,
      SUM(CASE WHEN yr = 2025 THEN quantity END) AS u25

      Keep these raw sums in a CTE and compute the percentage in the final SELECT, so it is calculated from unrounded numbers.

      Reading the result

      price_group units_change_pct revenue_change_pct
      raised 21.0 44.3
      unchanged 9.2 13.4

      If the rise had hurt demand, raised products would grow less than unchanged ones. They grew more. Revenue grew faster than units for raised products because each unit earned about 6% more and because sales shifted towards the dearer items in the group.

      Pitfalls

      • BETWEEN '2025-03-01' AND '2025-08-31' on a timestamp drops the evening of August 31; use a half-open range.
      • Counting cancelled or refunded orders adds sales that never happened.
      • IN (SELECT ...) is fine for 29 ids; on millions of rows, a join to the CTE is the faster habit.

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