The request

From Jessica Turner, Merchandising Manager
Your goal
Create price_plan from active Lighting products with product_id, name, subcategory, old_price and new_price (both start as unit_price). Use UPDATE to set new_price to ROUND(old_price * 1.06, 2) for the subcategories 'Pendants' and 'Sconces' only. Then return one row per subcategory with: subcategory, products, changed_products (rows where new_price differs from old_price), avg_old_price and avg_new_price (both rounded to 2), sorted by subcategory.
- Read from the
productstablenot done yet - Show exactly
subcategoryproductschanged_productsavg_old_priceavg_new_pricenot done yet - 4 rows, sorted as askednot done yet
Your query
Loading the editor…
Reference solution
| product_id | name | category | subcategory | unit_price |
|---|---|---|---|---|
| 101 | Vale Velvet Loveseat | Furniture | Sofas | 1659.00 |
| 102 | Alder Oak Coffee Table | Furniture | Tables | 919.00 |
| 103 | Orchard Rattan Dining Chair | Furniture | Chairs | 789.00 |
| 104 | Upland Oak Bookcase | Furniture | Storage | 429.00 |
Mote checks your result
Press Run. Each row that matches the goal becomes a firefly in your lantern.
What Jessica gets, and why
Tables
Harbor & Pine store data, ready to query.
products120 rows · 8 columns
product_idintegernamevarcharcategoryvarcharsubcategoryvarcharunit_pricedecimalunit_costdecimallaunched_ondateis_activeboolean
| product_id | name | category | subcategory | unit_price | unit_cost | launched_on | is_active |
|---|---|---|---|---|---|---|---|
| 101 | Vale Velvet Loveseat | Furniture | Sofas | 1659.00 | NULL | 2023-09-11 | true |
| 102 | Alder Oak Coffee Table | Furniture | Tables | 919.00 | 503.50 | 2024-05-16 | true |
| 103 | Orchard Rattan Dining Chair | Furniture | Chairs | 789.00 | 429.87 | 2023-05-27 | true |
| 104 | Upland Oak Bookcase | Furniture | Storage | 429.00 | 214.70 | 2022-12-20 | true |
| 105 | Avery Leather Bed Frame | Furniture | Beds | 449.00 | 232.70 | 2021-08-31 | true |
customers2,018 rows · 9 columns
customer_idintegerfirst_namevarcharlast_namevarcharemailvarcharcityvarcharstatevarcharsignup_datedatesegmentvarcharreferral_sourcevarchar
| customer_id | first_name | last_name | city | state | signup_date | segment | referral_source | |
|---|---|---|---|---|---|---|---|---|
| 1 | Owen | Hudson | owen.hudson@icloud.com | San Francisco | CA | 2022-06-02 | consumer | search |
| 2 | Douglas | Mendoza | dmendoza69@gmail.com | Atlanta | GA | 2022-06-02 | consumer | NULL |
| 3 | Henry | Brooks | hbrooks@lakesidecafe.com | Los Angeles | CA | 2022-06-04 | business | NULL |
| 4 | Brian | Graham | bgraham@bluecedarhomes.com | Seattle | WA | 2022-06-06 | business | |
| 5 | Kyle | Jimenez | kjimenez27@yahoo.com | Indianapolis | IN | 2022-06-07 | consumer | NULL |
orders12,000 rows · 7 columns
order_idintegercustomer_idintegerorder_datetimestampstatusvarcharchannelvarcharshipping_feedecimalcoupon_codevarchar
| order_id | customer_id | order_date | status | channel | shipping_fee | coupon_code |
|---|---|---|---|---|---|---|
| 1001 | 125 | 2023-01-01 20:51:19 | completed | web | 0.00 | NULL |
| 1002 | 156 | 2023-01-02 07:58:46 | completed | web | 0.00 | NULL |
| 1003 | 22 | 2023-01-02 10:25:27 | completed | marketplace | 4.99 | WELCOME10 |
| 1004 | 48 | 2023-01-02 11:21:51 | completed | marketplace | 4.99 | WELCOME10 |
| 1005 | 82 | 2023-01-02 11:43:36 | completed | web | 0.00 | WELCOME10 |
order_items30,338 rows · 6 columns
order_item_idintegerorder_idintegerproduct_idintegerquantityintegerunit_pricedecimaldiscount_pctdecimal
| order_item_id | order_id | product_id | quantity | unit_price | discount_pct |
|---|---|---|---|---|---|
| 1 | 1001 | 121 | 9 | 31.99 | 0.00 |
| 2 | 1001 | 154 | 3 | 11.99 | 0.00 |
| 3 | 1002 | 154 | 4 | 11.99 | 0.00 |
| 4 | 1002 | 194 | 1 | 149.00 | 0.00 |
| 5 | 1002 | 193 | 2 | 19.99 | 0.00 |
returns1,673 rows · 5 columns
return_idintegerorder_item_idintegerreturned_ondatereasonvarcharrefund_amountdecimal
| return_id | order_item_id | returned_on | reason | refund_amount |
|---|---|---|---|---|
| 1 | 14 | 2023-01-10 | damaged | 83.69 |
| 2 | 42 | 2023-01-10 | wrong_item | 19.99 |
| 3 | 105 | 2023-01-14 | wrong_item | 10.99 |
| 4 | 24 | 2023-01-16 | changed_mind | 26.99 |
| 5 | 126 | 2023-01-16 | late | 20.99 |
employees60 rows · 8 columns
employee_idintegerfirst_namevarcharlast_namevarchardepartmentvarchartitlevarcharmanager_idintegerhire_datedatesalaryinteger
| employee_id | first_name | last_name | department | title | manager_id | hire_date | salary |
|---|---|---|---|---|---|---|---|
| 1 | Catherine | Hale | Executive | Chief Executive Officer | NULL | 2016-03-01 | 285000 |
| 2 | Robert | Kim | Executive | Chief Operating Officer | 1 | 2017-01-09 | 235000 |
| 3 | Megan | Collins | Sales | Head of Sales | 1 | 2018-04-16 | 172000 |
| 4 | Chloe | Gutierrez | Sales | Account Executive | 3 | 2020-01-21 | 72000 |
| 5 | Frank | Bell | Sales | Account Executive | 3 | 2023-05-21 | 73000 |
web_sessions40,000 rows · 7 columns
session_idintegercustomer_idintegerstarted_attimestampdevicevarcharsourcevarcharpages_viewedintegerconvertedboolean
| session_id | customer_id | started_at | device | source | pages_viewed | converted |
|---|---|---|---|---|---|---|
| 1 | NULL | 2023-01-01 23:02:24 | mobile | paid | 1 | false |
| 2 | 18 | 2023-01-01 15:07:49 | desktop | paid | 1 | false |
| 3 | NULL | 2023-01-01 16:55:46 | desktop | direct | 7 | false |
| 4 | NULL | 2023-01-01 12:43:28 | mobile | organic | 3 | false |
| 5 | NULL | 2023-01-01 13:06:21 | mobile | social | 2 | false |
marketing_spend3,276 rows · 3 columns
spend_datedatechannelvarcharamountdecimal
| spend_date | channel | amount |
|---|---|---|
| 2023-01-01 | paid | 492.99 |
| 2023-01-01 | 37.38 | |
| 2023-01-01 | social | 200.53 |
| 2023-01-02 | paid | 709.13 |
| 2023-01-02 | 64.13 |
The lesson
UPDATE changes values in rows that already exist. It is the most dangerous statement an analyst
types, because it has no undo once committed, and one missing line changes every row in the table.
UPDATE … SET … WHERE
UPDATE price_plan
SET new_price = ROUND(old_price * 1.06, 2)
WHERE subcategory IN ('Pendants', 'Sconces');
SET says which columns get which new values. The expression can use the row’s current values, here
old_price. WHERE decides which rows change, using the same conditions you already know from SELECT.
Every row that matches is changed; every other row is left alone. You can set several columns at once:
SET a = ..., b = ....
Preview first, then update a copy
Two habits keep an UPDATE safe.
Run the WHERE as a SELECT first. Swap UPDATE ... SET ... for SELECT * FROM ... with the same
WHERE and look at what comes back. On the active Lighting catalog it returns 10 rows, 5 Pendants and 5
Sconces. If it had returned 20, you would know the condition was wrong before anything changed.
Work on a copy. The starter builds price_plan with CREATE TABLE ... AS SELECT, keeping the
current price twice: old_price stays as it is and new_price gets updated. The plan can be reviewed,
changed and thrown away without touching products, and the comparison is one GROUP BY:
| subcategory | products | changed_products | avg_old_price | avg_new_price |
|---|---|---|---|---|
| Floor Lamps | 5 | 0 | 138.20 | 138.20 |
| Pendants | 5 | 5 | 183.40 | 194.40 |
| Sconces | 5 | 5 | 216.20 | 229.17 |
| Table Lamps | 5 | 0 | 222.00 | 222.00 |
The two untouched subcategories are the proof that the WHERE did its job.
On a real database, wrap risky changes in a transaction: BEGIN TRANSACTION;, the UPDATE, a checking
SELECT, then COMMIT; if it looks right or ROLLBACK; if not. This game does the rollback for you
after every run, which is why each run starts by recreating price_plan.
Common mistakes
- No WHERE.
UPDATE price_plan SET new_price = ...changes all 20 rows. Most SQL clients will run it without a warning. - Updating the source table. Raising
products.unit_pricedirectly destroys the old price, so the comparison is impossible and every report readingproductschanges at once. - Rounding at the wrong time. Round each new price, as a shop would, then average. Averaging unrounded prices can differ by a cent.
- Comparing with NULL.
new_price <> old_priceis unknown when either side is NULL; on this copy every Lighting product has a price, but check before you count changed rows on other data.
In SQL Server
UPDATE ... SET ... WHERE is identical in T-SQL. SQL Server adds an OUTPUT clause that returns the
before and after values of every changed row, handy as a receipt:
BEGIN TRAN;
UPDATE dbo.PricePlan SET NewPrice = ROUND(OldPrice * 1.06, 2)
OUTPUT deleted.NewPrice, inserted.NewPrice
WHERE Subcategory IN ('Pendants', 'Sconces');
ROLLBACK; -- or COMMIT
In SSMS, BEGIN TRAN stays open until you commit or roll back, so finish it.
Lantern lit!
Practised: updatewherestaging tablecreate table as
- Solved
- No hints
- First run
