1. Lighthouse

Level 4 · Remove bad lines with DELETE

The request

From Kevin Brooks, Operations Manager

“The warehouse planning sheet should only see 2025 lines that actually shipped. Can you build a staging copy of the 2025 order lines, throw out the junk and the orders that never went out, and tell me what's left by status?”

Your goal

Create lines_2025 with order_item_id, order_id, product_id, quantity, unit_price, discount_pct (from order_items) and status (from orders) for orders placed in 2025. Then DELETE the lines with quantity <= 0 and the lines whose status is 'cancelled' or 'pending'. Finally return status, lines (row count) and units (sum of quantity) per remaining status, sorted by status.

  • Read from the orders tablenot done yet
  • Read from the order_items tablenot done yet
  • Show exactly status lines unitsnot done yet

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

    DELETE removes whole rows. Like UPDATE, it changes every row its WHERE matches, and without a WHERE it matches every row.

    DELETE FROM … WHERE

    DELETE FROM lines_2025 WHERE quantity <= 0;
    DELETE FROM lines_2025 WHERE status IN ('cancelled', 'pending');

    You can write it as one statement with OR, but two statements read like a checklist and each can be previewed on its own. Preview first:

    SELECT status, COUNT(*) AS lines
    FROM lines_2025
    WHERE quantity <= 0 OR status IN ('cancelled', 'pending')
    GROUP BY status;

    On the 2025 copy, the staging table starts with 12,017 lines: 10,810 completed, 296 refunded, 684 cancelled and 227 pending. Ten completed lines have a quantity of zero or below, the data-entry errors documented in the dataset. After both deletes, 10,800 completed lines (21,365 units) and 296 refunded lines (597 units) remain.

    Refunded orders stay on purpose: they did ship and came back, so the warehouse handled them. Deciding what to keep is a business question; ask the person who requested the table rather than guessing.

    DELETE vs TRUNCATE vs DROP

    Three statements remove data, at three different levels:

    statement removes table afterwards
    DELETE FROM t WHERE ... the matching rows still there, other rows kept
    TRUNCATE t (or TRUNCATE TABLE t) every row, fast still there, empty, same columns
    DROP TABLE t the table itself gone, columns and all

    Use DELETE to clean selected rows, TRUNCATE to empty a load table before tomorrow’s import, and DROP TABLE when the table is no longer needed at all, such as a scratch table at the end of an analysis. DROP TABLE IF EXISTS t avoids an error when it is already gone.

    Should you delete at all?

    On the store’s real order_items, deleting the bad lines would erase evidence: support could no longer see that an order had a zero-quantity line. In source tables, analysts filter (WHERE quantity > 0) or flag (CASE WHEN quantity <= 0 THEN 'bad_qty' END) instead. Deleting belongs in staging copies like this one, where the source is untouched.

    Common mistakes

    • Running a DELETE with no WHERE. Some editors run the selected text only, so a half-selected statement can be the whole table.
    • Using < instead of <= and keeping the eight zero-quantity lines in the full dataset.
    • Using NOT IN ('completed', 'refunded') when a status column has NULLs: rows with a NULL status would survive, because NULL NOT IN (...) is unknown.
    • Dropping a table when you only wanted it empty.

    In SQL Server

    DELETE and DROP TABLE IF EXISTS (2016+) are the same. Emptying a table is TRUNCATE TABLE dbo.Lines2025; (the word TABLE is required). In SSMS, a habit worth keeping:

    BEGIN TRAN;
    DELETE FROM dbo.Lines2025 WHERE Quantity <= 0;
    SELECT @@ROWCOUNT AS deleted_rows;  -- check, then COMMIT or ROLLBACK

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