1. Lighthouse

Level 4 · Cost per conversion without dividing by zero

The request

From Jordan Reyes, Head of Marketing

“The cost-per-conversion chart for 2025 shows 'Infinity' for social in a few months and the axis is useless. A month with no conversions should just be blank. Can you fix the query?”

Your goal

For 2025, return one row per marketing channel and month with: channel, month (the first day of the month, as a DATE), spend (total marketing_spend.amount, rounded to 2), conversions (web_sessions with converted = true whose source is that channel) and cost_per_conversion (spend divided by conversions, rounded to 2, NULL when conversions is 0). Sort by channel, then month.

  • Read from the web_sessions tablenot done yet
  • Read from the marketing_spend tablenot done yet
  • Show exactly channel month spend conversions cost_per_conversionnot done yet

Your query

SQL

Loading the editor…

    ResultPreview of web_sessions · press Run to see yours
    Preview of web_sessions
    session_idcustomer_idstarted_atdevicesource
    1NULL2023-01-01 23:02:24mobilepaid
    2182023-01-01 15:07:49desktoppaid
    3NULL2023-01-01 16:55:46desktopdirect
    4NULL2023-01-01 12:43:28mobileorganic

    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.

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

    The lesson

    Rates are divisions, and a denominator can be zero: a channel with no conversions, a day with no orders, a new product with no sales. What happens next depends on the database. Postgres, SQL Server and Snowflake stop the whole query with a “division by zero” error. DuckDB returns Infinity for decimal division, which flows straight into a chart.

    NULLIF in one line

    NULLIF(a, b) returns NULL when a = b, and a otherwise. Wrap the denominator:

    spend / NULLIF(conversions, 0)

    When conversions is 0 the division becomes spend / NULL, which is NULL: no error, no Infinity, and a blank on the chart, which is the honest answer.

    The real zeros in this data

    Social is Harbor & Pine’s weakest converting source. In May 2025 the store spent $10,170.19 on social and those sessions brought 0 conversions out of 204 social sessions. September and October 2025 also had 0. Paid, with far more spend, never hit zero in 2025, but its lowest month (February) had a single conversion for $20,740.71. NULLIF handles the zeros; a cost of $20,000 per conversion is a business conversation, not a data problem.

    NULLIF for sentinel values

    The second use of NULLIF is cleaning. Older systems often store “unknown” as a sentinel such as 0, -1 or an empty string. Turn the sentinel into a real NULL so aggregates skip it:

    SELECT AVG(NULLIF(quantity, 0)) AS avg_quantity
    FROM order_items;

    Eight order lines have quantity 0 (data-entry errors). AVG(quantity) is 1.9954 and AVG(NULLIF(quantity, 0)) is 1.9959. The difference is tiny here, but with a sentinel like -1 on thousands of rows it decides the answer. For text: NULLIF(TRIM(coupon_code), '').

    Common mistakes

    • Wrapping the whole ratio. NULLIF(spend / conversions, 0) still divides by zero first.
    • Inventing a value. COALESCE(conversions, 1) or a ratio of 0 makes up a number. If the chart must show something, show NULL as “no conversions” in the label.
    • Filtering out the zero rows. It removes the error and also the evidence that money was spent with no result.

    In SQL Server

    NULLIF is identical and is the standard T-SQL guard against error 8134 (divide by zero). Beware of integer division too: conversions / sessions between two INT columns returns 0, so multiply by 1.0 first.

    SELECT channel, spend / NULLIF(conversions, 0) AS cost_per_conversion FROM monthly_marketing;

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