1. Lighthouse

Level 8 · Boss: rescue the slow discount report

The request

From Patrick Owens, Chief Operating Officer

“The discount report is the slowest thing we own, and I need it for the pricing review. Three fixes, please: the Discounted Sales measure, a share-of-sales measure on top of it that doesn't repeat itself, and the order lines import, which loads far more than the report uses.”

Your goal

Step 1 of 3. Step 1. The old Discounted Sales is in the editor as a comment. Write Discounted Sales again with the same numbers (completed orders, lines with discount_pct above 0, sum of order_items[line_total]) using CALCULATE with column filters and no FILTER. The matrix has 'Date'[Year] on its rows.

  • Fill [Discounted Sales] on every row of the visualnot done yet
  • The Total row right toonot done yet

Step 1 · your measure

DAX
Visual
A matrix: rows = 'Date'[Year]; values = [Sales], [Discounted Sales]
Measures in the report
Sales = CALCULATE(SUM(order_items[line_total]), orders[status] = "completed")
Data pane: tables and columns
  • order_items order_item_id, order_id, product_id, quantity, unit_price, discount_pct, line_total
  • orders order_id, customer_id, order_date, status, channel, shipping_fee, coupon_code
  • products product_id, name, category, subcategory, unit_price, unit_cost, launched_on, is_active
  • customers customer_id, first_name, last_name, email, city, state, signup_date, segment, referral_source
  • returns return_id, order_item_id, returned_on, reason, refund_amount
  • Date Date, Year, Quarter, Month Number, Month, Year Month, Year Quarter, Day, Weekday Number, Weekday

Loading the editor…

    Visual[Discounted Sales] fills in when you press Check
    The visual before your answer
    YearSalesDiscounted Sales
    2,0231,600,510.52
    2,0241,993,604.39
    2,0252,300,169.53
    Total5,894,284.44

    Mote checks your result

    Press Check. Mote fills in your answer and compares it with what the report needs.

    Step 2 · your measure

    DAX
    Visual
    A matrix: rows = 'Date'[Year]; values = [Sales], [Discounted Share]
    Measures in the report
    Sales = CALCULATE(SUM(order_items[line_total]), orders[status] = "completed")
    Data pane: tables and columns
    • order_items order_item_id, order_id, product_id, quantity, unit_price, discount_pct, line_total
    • orders order_id, customer_id, order_date, status, channel, shipping_fee, coupon_code
    • products product_id, name, category, subcategory, unit_price, unit_cost, launched_on, is_active
    • customers customer_id, first_name, last_name, email, city, state, signup_date, segment, referral_source
    • returns return_id, order_item_id, returned_on, reason, refund_amount
    • Date Date, Year, Quarter, Month Number, Month, Year Month, Year Quarter, Day, Weekday Number, Weekday

    Loading the editor…

      Visual[Discounted Share] fills in when you press Check
      The visual before your answer
      YearSalesDiscounted Share
      2,0231,600,510.52
      2,0241,993,604.39
      2,0252,300,169.53
      Total5,894,284.44

      Mote checks your result

      Press Check. Mote fills in your answer and compares it with what the report needs.

      Step 3 · your query steps

      Power Query
      The sheet for this level
      order_item_idorder_idproduct_idquantityunit_pricediscount_pctstatuschannel
      1289181244813561590completedapp
      2289191244812931090completedapp
      32892012448180220.990completedapp
      4289211244815531690completedapp
      52892212449185112.9910completedweb
      62892312449186120.9910completedweb
      72892412449198-144.9910completedweb
      82892512449136115910completedweb
      92892612450185112.990completedweb
      102892712451166128.990completedmarketplace
      112892812451115215990completedmarketplace
      122892912452118116090completedmarketplace
      132893012452166128.990completedmarketplace
      142893112453121331.9920completedapp
      152893212454160170.990completedapp
      162893312454156153.990completedapp
      172893412454180120.990completedapp
      18289351245515521690completedapp
      192893612455166228.990completedapp
      202893712456189176.990completedweb
      212893812456198244.990completedweb
      222893912456180120.990completedweb
      232894012456193119.990completedweb
      242894112457152213.990completedmarketplace
      252894212457142138.990completedmarketplace
      262894312457128154.990completedmarketplace
      272894412458193119.990completedapp
      282894512458146116.990completedapp
      292894612459193119.990completedweb
      30289471246017321390completedweb
      312894812460171327.990completedweb
      32289491246013821990completedweb
      332895012461171127.9920completedmarketplace
      342895112462151160.9920completedapp
      352895212462166128.9920completedapp
      362895312463141113.990completedweb
      372895412464182217.990completedweb
      382895512464193119.990completedweb
      392895612464122129.990completedweb
      40289571246413811990completedweb
      412895812465151260.9920completedweb
      422895912465158117920completedweb
      432896012465190143.9920completedweb
      4428961124651991104.9920completedweb
      45289621246613014090completedweb
      462896312467165125.9920completedapp
      472896412467157410920completedapp
      48289651246818111790completedweb
      492896612468193319.990completedweb
      502896712468172118.990completedweb
      51289681246914023290completedmarketplace
      52289691246911011790completedmarketplace

      Loading the editor…

        ResultPress Check to compare the query result

        Mote checks your result

        Press Check. Mote fills in your answer and compares it with what the report needs.

        The lesson

        Patrick’s report is slow for the usual three reasons at once: a measure that works row by row, a measure that repeats itself, and an import that carries far more than anyone reads. This case fixes all three, in the order you would at work.

        What you are working in

        A practice report and a practice Power Query editor, not Power BI. Steps 1 and 2 are DAX measures checked cell by cell in a matrix with 'Date'[Year] on its rows; step 1 also checks that FILTER is gone. Step 3 is a list of Applied Steps on the imported order lines, checked against the expected table.

        In Power BI Desktop: before you start, Optimize → Performance analyzer → Start recording → Refresh visuals and note the DAX query times. Edit the measures in the formula bar, open the query with Home → Transform data, and record again at the end.

        Step 1: from FILTER to column filters

        The old measure walks every order line, looks up the order’s status with RELATED, keeps the discounted completed lines, then sums them. Both conditions are plain tests on one column each, which is exactly what CALCULATE filter arguments are for:

        Discounted Sales = CALCULATE(SUM(order_items[line_total]),
            order_items[discount_pct] > 0, orders[status] = "completed")

        The status filter on orders reaches order_items through the relationship, and the year on the matrix rows reaches it through 'Date' → orders. Same numbers, one storage engine scan.

        Step 2: a share without repeats

        Discounted Share =
        VAR discounted = [Discounted Sales]
        VAR total = [Sales]
        RETURN IF(total > 0, DIVIDE(discounted, total))

        Each measure is evaluated once per cell and reused. DIVIDE protects the Total row and any year with no sales. Format it as a percentage in Power BI with Measure tools → Format → Percentage.

        Step 3: import only discounted lines

        The source brings eight columns for every line of the day. The report needs four, and only for discounted lines:

        Filter rows: discount_pct > 0
        Choose columns: order_id, product_id, quantity, discount_pct

        Both steps fold into the SQL query that Power Query sends, so SQL Server returns the small result and Power BI never stores the rest. Filter first: once you choose the four columns, the filter still works here, but in real reports the column you filter on is often one you drop.

        What to check afterwards

        Record Performance analyzer again. The discount visuals’ DAX query time should drop, the refresh should be shorter, and the model smaller (DAX Studio’s View Metrics shows each table’s size). If one visual is still slow, its measure is next in line.

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