1. Lighthouse

Level 8 · Boss: import, clean and publish

The request

From Jessica Turner, Merchandising Manager

“We're merging the new supplier's lamps into our Lighting range. I need the combined list loaded into one table, cleaned up, and a small summary by price tier with margins that I can hand to Laura in finance. Please show me that nothing got lost along the way.”

Your goal

Step 1 of 3. Create lighting_feed (sku INTEGER, product_name VARCHAR, category VARCHAR, unit_cost DECIMAL(10, 2), list_price DECIMAL(10, 2)). Load every Lighting product from products with INSERT ... SELECT (product_id, name, category, unit_cost, unit_price), keep the starter's three supplier rows, then profile the feed: return category, feed_rows (row count), with_cost (rows with a unit_cost) and with_price (rows with a list_price) per category value exactly as stored, sorted by category.

  • Read from the products tablenot done yet
  • Show exactly category feed_rows with_cost with_pricenot done yet
  • 3 rows, sorted as askednot done yet

Step 1 · 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.

    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.

      Step 3 · 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.

        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
        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
        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

        This boss runs a small import end to end: the kind of job where a supplier’s list joins your catalog and someone downstream relies on the result. Because every run starts fresh, each step repeats the earlier steps’ statements (the starters include them) and adds its own.

        Step 1: load and profile

        An INSERT can take its rows from a query instead of a VALUES list:

        INSERT INTO lighting_feed
        SELECT product_id, name, category, unit_cost, unit_price
        FROM products
        WHERE category = 'Lighting';

        The columns are matched by position, so the SELECT must list them in the table’s order. The supplier’s three rows follow as VALUES.

        Then profile before you clean. Grouping by the raw category shows what actually arrived: 21 rows say Lighting, one says LIGHTING and one says ' lighting ' with spaces. Counting unit_cost and list_price per group shows one row without a cost (the Calder Arc Floor Lamp) and one without a price (the Wren Ceramic Table Lamp). A profile like this is your list of cleaning jobs.

        Step 2: clean with UPDATE and DELETE

        UPDATE lighting_feed SET category = 'Lighting' WHERE LOWER(TRIM(category)) = 'lighting';
        DELETE FROM lighting_feed WHERE list_price IS NULL;

        The UPDATE’s WHERE normalizes before comparing, so one condition catches every spelling. The DELETE removes the product that cannot be sold yet. The product without a cost stays: it has a price, so it belongs in the catalog, and its margin is simply unknown. Deleting it would also understate the premium tier. The cleaned feed has 22 rows.

        Step 3: publish a summary with a check

        CREATE TABLE lighting_summary AS
        SELECT CASE WHEN list_price >= 300 THEN 'premium' ELSE 'core' END AS tier,
               COUNT(*) AS products,
               COUNT(unit_cost) AS with_cost,
               ROUND(AVG(list_price), 2) AS avg_price,
               ROUND(100 * AVG((list_price - unit_cost) / list_price), 1) AS avg_margin_pct
        FROM lighting_feed
        GROUP BY 1;

        AVG skips NULLs, so the margin average covers the 5 premium products that have a cost while products still counts all 6. Showing with_cost next to the margin tells the reader how many rows the margin rests on. Core products average $135.81 with a 56.4% margin; premium ones average $407.33 with 53.9%.

        The final query adds rows_match: the summary’s product counts must add up to the feed. If a tier CASE had a gap (say > 300 and < 300, losing a product at exactly $300), the check would turn false.

        Common mistakes

        • Listing INSERT … SELECT columns in a different order from the table, which silently puts names in the category column or costs in the price column.
        • Cleaning before profiling, and fixing only the spellings you happened to notice.
        • Deleting rows with a NULL cost: they are real products.
        • Publishing a summary without a check that it covers every row.

        In SQL Server

        INSERT INTO ... SELECT and UPDATE/DELETE are the same in T-SQL. The summary would be built with SELECT ... INTO dbo.LightingSummary FROM ..., and the check written as a CASE WHEN ... THEN 1 ELSE 0 END column, since T-SQL has no boolean column type. A real supplier file would come in through the SSMS Import Flat File wizard; in DuckDB it is INSERT INTO lighting_feed SELECT * FROM read_csv('feed.csv').

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