Power BI’s AI Toolkit: Evaluating Copilot for DAX Development in Power BI Desktop

If you have ever felt that slight surge of hope when clicking the Copilot button in Power BI Desktop, you are not alone. You have a specific piece of business logic in mind, and you are curious whether Copilot can actually save you time writing, formatting, and documenting a DAX measure.

The honest frustration in 2026 is not that AI does not work. It is the uncertainty of when to rely on it. If you have ever prompted Copilot for a measure only to find yourself spending more time validating its response than writing the measure yourself, you know the feeling.

It is not about being for or against AI. It is about knowing exactly where the tool hits its stride and where it starts to trip over its own logic.

To cut through that uncertainty, we put Copilot to work on a practical sales model across five realistic scenarios: writing measures, building advanced logic, explaining complex DAX, debugging errors, and documenting the semantic model. Along the way, each result gets a simple rating:

🟢 Green Light — Copilot handles it reliably. Safe to use with a quick review.

🟡 Yellow Light — Copilot provides a strong starting point, but the output needs validation before it goes into production.

🔴 Red Light — Copilot struggles or misleads. A human needs to drive.

A sample report is available for download at the end of this post so you can follow along and test these prompts against the same data.

Let’s find out where each scenario lands.


The Data Model: A Standard Sales Model

Instead of testing Copilot on isolated or theoretical formulas, we are putting it to work on a simplified but practical dataset.

The model consists of four core tables connected through a standard star schema:

  • Sales: The fact table. Each row is a single transaction with a SalesID, Amount, SalesDate, and CustomerID.
  • Product: 15 products across 6 categories: Smartphones, Laptops, Tablets, TVs, Headphones, and Watches, each with a Product Code and Product Image.
  • Employee: 17 employees spanning four departments: Retail, Sales, Marketing, HR, and R&D — with JobTitle, Department, and a ManagerID for hierarchy.
  • Region: Three regions: the United States, Europe, and Asia

The Sales table connects to each dimension through its ProductID, EmployeeID, and RegionID. Column names are descriptive, and the schema is intentionally straightforward.

Why does that matter? Copilot’s output quality is directly tied to how well the model is structured. Column names like Amount and SalesDate give it far more to work with than Col1 or Fld_003. A little upfront investment in clean, readable naming pays off quickly when we start prompting.

The Five Scenarios

The goal is to move beyond simple sums and see how Copilot handles the tasks that actually eat up our time. Here is what we are testing:

  1. Establishing the Basics: Foundational measures like total sales and department-filtered aggregations, to confirm Copilot correctly reads the model structure and relationships.
  2. Building Advanced Logic: More complex calculations like year-over-year growth and percentage of total, where filter context starts to matter.
  3. Explaining Existing Measures: Handing Copilot DAX that is already in the model and asking it to translate the logic into plain language.
  4. Debugging Logical Errors: Providing a measure that runs without error but returns incorrect results, and seeing whether Copilot can identify the real problem.
  5. Generating Measure Descriptions & Comments: Using Copilot to auto-document completed measures directly in the model, and evaluating whether the output is accurate and usable.

Because of the generative nature of Copilot, it may not produce the exact same DAX or explanations for you as shown here. The patterns and behaviors should be consistent, but the specific wording may vary.

This is not an attempt to break the tools with impossible requests. It is a straightforward evaluation of how much repetitive, manual DAX work we can safely offload to Copilot in a standard reporting environment.


TL;DR — Key Findings at a Glance

If you are short on time, here is the summary. We tested Copilot in Power BI Desktop across five scenarios using the sales model described above. Each scenario was tested using both the Copilot pane and the DAX query view inline Copilot where applicable.

The findings:

  • 🟢 Establishing the Basics: Copilot correctly identified table relationships and column names without being told. Basic measure and filtered aggregations came back clean and accurate.
  • 🟡 Building Advanced Logic – Copilot Pane: Strong DAX patterns for YoY growth and percentage contribution, but both measures carry dependencies that Copilot does not resolve on its own. The Date table requirement and the ALL vs ALLSELECTED design decision still need a human in the loop.
  • 🟢 Building Advanced Logic – DAX Query View: Writing measures through the inline Copilot added a meaningful validation step. The % of Total Sales by Product measure used ALLSELECTED correctly without prompting, and the Suggest measures feature returned six well-structured analytical measures in a single query with inline comments and a preview ready to run.
  • 🟡 Explaining Complex DAX Measures – Copilot Pane: Once the full DAX is pasted in, Copilot handles even the most complex measures accurately. A multi-variable measure combining SUMMARIZE, TOPN, CONCATENATEX, and HTML rendering was broken down step by step in plain language.
  • 🟢 Explaining Complex DAX Measures – DAX Query View: The Define and Evaluate workflow eliminates the manual paste step and produces a more structured, variable-by-variable explanation. The better entry point for thorough measure audits.
  • 🟡 Debugging DAX Logical Errors: Correctly diagnosed an ALL vs ALLSELECTED filter context issue and provided two well-reasoned solutions with clear guidance on when to use each. Yellow light only because the fix still requires the developer to understand which option fits the specific report design.
  • 🟢 Generating Measure Descriptions – Model View: Accurate, readable, and business-aware descriptions generated in seconds. Descriptions travel with the model metadata and surface as tooltips in the Fields pane.
  • 🟢 Adding Inline Comments – DAX Query View: Copilot added meaningful, explanatory comments to an existing measure, explaining the reasoning behind each step rather than just restating variable names. Combined with measure descriptions, this covers documentation for both developers and report authors

Copilot earned five Green Lights and three Yellow Lights across eight tests. No Red Lights. The gaps that produced Yellow Lights were not about DAX correctness. They were about model architecture decisions and report design context that Copilot simply does not have access to. The developers who get the most value from Copilot are those who know enough DAX to validate the results.


Getting Started: How to Access Copilot in Power BI Desktop

Before jumping into the scenarios, it helps to know where Copilot lives in Power BI Desktop and how to reach it for each type of task. There are three entry points, and each one serves a different purpose.

Note: Copilot in Power BI Desktop requires access to a paid Fabric capacity (F2 or higher) or Power BI Premium capacity (P1 or higher). A Power BI Pro or Premium Per User (PPU) license alone is not sufficient. If the Copilot button in your ribbon appears greyed out or disabled, confirm with your administrator that Copilot is enabled at the tenant level. Review licensing and other requirements or limitations here: Copilot for Power BI overview.

The Copilot Pane (Report View)

This is the primary entry point for writing measures, debugging logic, and asking questions about the data model.

  1. Open the Copilot pane: Open the report in Power BI Desktop and select Copilot in the ribbon. The first time we use Copilot, we will be prompted to select a compatible workspace to associate with the report.
  2. Describe the measure: Once connected, the Copilot pane opens on the right side of the screen. Describe the measure in plain language. Reference the relevant table or column and be as specific as the business requirement calls for.
  3. Debug with Copilot: For debugging, paste the broken measure directly into the chat and describe what it should return versus what it is actually returning.

The more specific our prompt, the better the output. “Calculate total sales” will get us a basic SUM. “Calculate total sales for employees in the Retail department only” gives Copilot enough context to apply the correct filter across the right table relationship.

The DAX Query View (Inline Copilot)

The DAX query view has its own separate inline Copilot, distinct from the report view pane. It is particularly useful for writing DAX queries, understanding existing measures, and learning DAX concepts in context.

  1. Open the inline Copilot. Switch to DAX query view using the left navigation bar. It is the fourth icon from the top. Open the inline Copilot by clicking the Copilot button in the query editor or pressing CTRL + I.
  2. Use the inspire buttons. Three inspire buttons give us a quick starting point for the most common tasks:
    • Write DAX query: describe what you want in plain language, and Copilot generates the DAX, with automatic syntax validation and a retry if the first attempt contains errors.
    • Suggest measures: reviews your data and suggests new measures in a DAX query for further analysis.
    • Explain a DAX topic: ask about a specific function or concept and get a contextual explanation tied to your actual model.
  3. Work with an existing measure. To use Copilot with an existing measure, right-click it in the Data pane and select Define and Evaluate. This loads the full measure definition into the query editor, where we can then use the Explain or Write prompts to work with it.

The Model View (Measure Descriptions)

This entry point is specifically for generating measure descriptions and is easy to miss if you spend most of your time in Report view.

  1. Select the measure. Switch to Model view in Power BI Desktop. In the Data pane, select the measure we want to document.
  2. Generate the description. In the Properties pane on the right, locate the Description field. Click Create with Copilot to generate a description, then select Keep it to save it.

The description is stored directly in the .pbix file and surfaces as a tooltip when users hover over the measure in the Fields pane. It is a small detail that makes a meaningful difference in shared models and self-service environments.

Note: Copilot will only generate descriptions for measures in a valid state with no errors. Resolve any measure issues before attempting to generate a description.


Scenario 1: Establishing the Basics
Does Copilot Understand Your Data Model?

Before throwing complex logic at Copilot, the first step is confirming it understands the data model. If it cannot identify the correct columns for a simple aggregation, it will not stand a chance with more complex requests.

Test 1: Total Sales Amount

The Prompt: Provide a DAX measure that calculates the Total Sales Amount

Copilot’s Response:


🟢 Green Light

Test 2: Total Retail Sales

The Prompt: Create a measure for Total Retail Sales that calculates the total sales amount for employees in the Retail department only

Copilot’s Response:

🟢 Green Light


Scenario 2: Building Advanced DAX Logic
Where Filter Context Starts to Matter

With the basics confirmed, the next step is to see how Copilot handles more complex measures. This is where filter context starts to matter and where the quality of the data model structure begins to influence the output.

Test 1: Year-Over-Year Growth

Calculating Year-over-Year (YoY) growth is one of the more common requirements in sales reporting. It requires a solid understanding of date filters and is one of the first places where DAX relies on model structure rather than just formula syntax.

The Prompt: Create a measure that calculates year-over-year growth for total sales.

Copilot’s Response:

🟡 Yellow Light — Strong DAX, external dependency required

Overlooked Detail: One thing that is easy to miss here is the Date Table assumption. Copilot explicitly noted this measure requires a “proper date table.”

In our sample dataset, we have a SalesDate column in the Sales table, but for time intelligence functions like SAMEPERIODLASTYEAR to work reliably, Power BI requires a continuous date column and a correctly configured, marked Date table. If the measure references SalesDate directly from the fact table, it may appear to work but can return incomplete or incorrect results under certain filter conditions. This dependency is not always obvious from the generated DAX alone.

Alternative Approach:

Copilot used SAMEPERIODLASTYEAR, which is a common and readable pattern. An alternative for more complex or flexible requirements is DATEADD:

Total Sales YoY Growth % =
VAR CurrentSales = [Total Sales Amount]
VAR LastYearSales =
CALCULATE(
[Total Sales Amount],
DATEADD(DateTable[Date], -1, YEAR)
)
RETURN
DIVIDE(CurrentSales - LastYearSales, LastYearSales)

Both approaches achieve similar results. SAMEPERIODLASTYEAR is more concise and easier to read. DATEADD provides more flexibility and uses the same structure whether shifting by days, months, quarters, or years. In practice, the choice often comes down to readability versus flexibility and how the Date table is structured.

Test 2: Percentage Contribution by Region

Let’s look at how Copilot handles a different kind of problem: calculating how much a specific segment contributes to the overall total. In this case, we want to understand how much each region contributes to total sales.

This requires calculating a value in the current filter context and comparing it against a total that ignores that filter.

The Prompt: Provide a measure that calculates the percentage contribution of each region to total sales.

Copilot’s Response:

🟡 Yellow Light — Correct answer, incomplete solution

Overlooked Detail: The difference between ALL and ALLSELECTED is easy to miss here but it matters significantly in interactive reports. ALL(Region) removes every filter on the Region table, including filters applied by slicers, meaning the denominator is always the grand total regardless of what users have selected. ALLSELECTED would preserve slicer selections while still ignoring the visual’s own row context. Copilot applied a standard pattern correctly, but it did not account for the nuances of how the report will actually be used.

Alternative Approach:

An alternative is to use REMOVEFILTERS, which makes the intent of the measure clearer to anyone reading it later:

% of Total Sales by Region =
DIVIDE(
[Total Sales Amount],
CALCULATE(
[Total Sales Amount],
REMOVEFILTERS(Region)
)
)

REMOVEFILTERS is generally preferred in team environments because its name explicitly describes what it does. ALL can be used both to clear filters and to return a table of values, which can lead to confusion when someone else reads the measure later.

DAX Query View Approach
Writing Measures with Inline Copilot

The Copilot pane is not the only way to generate DAX. For developers who prefer to validate output before committing it to the model, the DAX query view inline Copilot offers a useful alternative. Write the query, run it, review the results, and keep it if it looks right.

Here is a % of Total Sales by Product measure generated using DAX query view:

  1. Open a new query tab. Switch to DAX query view using the left navigation bar. Open a new query tab and press CTRL + I to open the inline Copilot.
  2. Enter the prompt and press Enter.
  3. Preview the results. Before keeping the query, select Run or press F5 to preview the results directly in the query editor.
  4. Add the measure to the model. Once satisfied with the output, select Keep query. Then use the Update model with changes button to add the measure to the data model.

The Prompt: Create a % of Total Sales by Product measure

Copilot’s Response:

Before keeping the query, select Run or press F5 to preview the results directly in the query editor.

🟢 Green Light

Bonus: Let Copilot Suggest What to Build Next

One of the more practical features in DAX query view is the Suggest measures inspire button. Rather than prompting Copilot for a specific measure, we can ask it to analyze the model and recommend what to build next.

The Prompt: Based on my data suggest new measures in a DAX query for further analysis and try them out with one or more suitable columns

Copilot’s Response:

🟢 Green Light

Overlooked Detail: Two of the six suggested measures — Sales Last 12 Months and Sales 3M Rolling Avg — reference 'DateTable'[Date], which assumes a properly configured Date table is present in the model.

As noted in the YoY Growth scenario, these measures will not work reliably without one. Copilot surfaced the right patterns, but the model still needs the right foundation beneath them.


Scenario 3: Explaining Complex DAX Measures
From Black Box to Plain Language

One of the most practical ways to use Copilot is not writing new DAX. It is making sense of DAX that already exists in the model. Whether we are inheriting a report from a colleague or revisiting logic written six months ago, Copilot can help translate complex measures into plain language.

This test uses the [Top Performers] measure — a multi-variable measure that summarizes sales by region and employee, identifies the top three performers, and returns a fully formatted HTML string for use in a custom visual. It is a realistic example of the kind of measure that is genuinely difficult to read cold.

Test 1: Asking Copilot to Explain Without the DAX

The Prompt: Explain the logic of the [Top Performers] measure. What is it calculating, and how is it displaying results?

Copilot’s Response:

The Follow-Up Prompt: Here is the DAX formula for [Top Performers] so you can explain it:

Top Performers =
VAR _groupedByRegionEmployee =
SUMMARIZE(
Sales,
Regions[Region],
Employee[EmployeeID],
"EmployeeTotalSalesMetricCY", [Sales Metric (CY)]
)
VAR _top3Performers =
TOPN(
3,
_groupedByRegionEmployee,
[EmployeeTotalSalesMetricCY], DESC,
Employee[EmployeeID], ASC
)
VAR _belowTop5Count =
COUNTROWS(_groupedByRegionEmployee) - COUNTROWS(_top3Performers)
VAR _top5HTML =
CONCATENATEX(
TOPN(3, _top3Performers, [EmployeeTotalSalesMetricCY], DESC),
VAR _imageURL = LOOKUPVALUE(Employee[Image], Employee[EmployeeID], Employee[EmployeeID])
VAR _salesMetricCY = [EmployeeTotalSalesMetricCY]
VAR _rank = RANKX(_top3Performers, [EmployeeTotalSalesMetricCY],,DESC,Dense)
RETURN
"<div style='width:50px; height:50px; border-radius:50%; overflow:visible; border:2px solid #0b4d78;'>
<img src='" & _imageURL & "' style='width:100%; height:100%; object-fit:cover; border-radius:50%;' />
<div style='position:absolute; bottom:-8px; right:-8px; width:35px; height:35px; border-radius:50%; background-color:#0b4d78; color:white; font-size:9px;'>
" & FORMAT(_salesMetricCY, "$#,0,.0K") & "
</div>
</div>",
"",
[EmployeeTotalSalesMetricCY], DESC
)
VAR _additionalCircle =
IF(
_belowTop5Count > 0,
"<div style='width:30px; height:30px; border-radius:50%; background-color:#bfbfbf; color:white; font-size:16px; margin-left:5px;'>
+" & FORMAT(_belowTop5Count, "0") & "
</div>",
""
)
VAR _finalOutput =
"<div style='display:flex; align-items:center; justify-content:flex-end; overflow:hidden; max-width:400px; padding-right:5px; padding-bottom:8px'>
" & _top5HTML & _additionalCircle &"
</div>"
RETURN
_finalOutput

Copilot’s Response:

Certainly! Here’s a step-by-step explanation of what the [Top Performers] measure is doing and how it displays results:

What is it Calculating?

  1. Grouping Sales by Region and Employee:
    • The measure first creates a table (_groupedByRegionEmployee) that summarizes sales data by both region and employee. For each combination, it calculates the [Sales Metric (CY)]
  2. Selecting the Top 3 Performers:
    • It then identifies the top 3 employees (per region) based on their [Sales Metric (CY)] using the TOPN function. If there are ties it uses EmployeeID as a secondary sort.
  3. Counting Additional Employees:
    • It calculates how many employees are not in the top 3 by subtracting the count of top performers from the total count.
  4. Building the HTML for Display
    • For each of the top 3 employees, it constructs an HTML snippet:
      • Shows the employee’s profile image (from Employee[Image]).
      • Displays their sales metric as a badge
      • Uses CSS to style the image and badge for a visually appealing display
    • If there are more employees beyond the top 3, it adds a grey circle with a “+N” indicator, where N is the number of additional employees.
  5. Combining Everything for Output:
    • The final output is a single HTML string that visually displays the top 3 performers (with image and sales badges) and, if applicable, a circle indicating how many more employees there are.

How is it displaying the results?

Visual Output: The measure returns an HTML string designed for use in a Power BI visual that supports HTML rendering.

What You See:

  • Up to three employee profile images, each with a badge showing their sales metric
  • If there are more than three employees, a grey circle with “+N” to indicate additional performers not shown.
  • The layout is styled to be compact, visually appealing, and aligned to the right.

Summary

  • Purpose: Highlights the top 3 sales performers per region, visually, with profile images and sales badges
  • Display: Uses HTML and CSS for a rich, custom visual experience, including a “+N” indicator for additional employees beyond the top 3.

If you need a breakdown of any specific part of the DAX or want to know how to adapt this logic, let me know!

🟢 Green Light

Overlooked Detail: Copilot explained what the measure is doing accurately, but it did not surface deeper technical considerations unprompted, such as the performance implications of nesting TOPN inside CONCATENATEX, whether repeated TOPN calls could be consolidated, or the assumption that LOOKUPVALUE will always return a single match.

These are the kinds of questions that should always be asked when reviewing measure definitions.

If we need that level of analysis, we can follow up with a specific prompt such as “Are there any performance concerns with this measure?” Copilot responds well to targeted follow-up questions; it just does not volunteer that analysis on its own.

Alternative Approach — Explaining Measures in DAX Query View

In the main test, explaining the [Top Performers] measure required pasting the full DAX formula directly into the Copilot pane. The DAX query view offers a cleaner path to the same result and goes one step further by letting us run the measure and explain it in the same workflow.

  1. Load the measure into DAX query view. Switch to DAX query view using the left navigation bar. In the Data pane, right-click the [Top Performers] measure and select Define and Evaluate. This automatically loads the full measure definition into the query editor as a DEFINE MEASURE and EVALUATE statement, with no manual copying required.
  2. Open the inline Copilot and explain the query. Select the DAX query in the editor and press CTRL + I to open the inline Copilot. Use the Explain this query inspire button or type the prompt directly.

The Prompt: Explain this DAX query

Copilot’s Response:

🟢 Green Light

Overlooked Detail: Compare the two explanation workflows side by side. The report view pane required the full DAX to be pasted manually, and Copilot’s response, while accurate, was structured around the user’s framing of the question.

The DAX query view response was driven by the query structure itself, which produced a more methodical, variable-by-variable breakdown.

Neither is strictly better. The pane is faster for a quick question, while the query view is better for a thorough audit. Knowing which to reach for depending on the situation is the more important skill.


Scenario 4: Debugging DAX Logical Errors
Finding What the Red Squiggle Misses

Identifying syntax errors is straightforward. Power BI flags them immediately with a red underline. The real challenge is logical debugging: when a measure runs without error but returns results that are incorrect for the business requirement. This test determines whether Copilot can identify why a measure is technically functional but practically broken.

The Setup

For this test, we want to create a dynamic % of Total Sales measure that displays correctly across multiple dimensions — Products, Regions, and Departments — while also respecting report-level filters like Year. Here is the measure as it currently exists in the model:

% of Total Sales =
VAR CurrentSales = SUM('Sales'[Amount])
VAR TotalSales = CALCULATE(SUM('Sales'[Amount]), ALL('Sales'))
RETURN
DIVIDE(CurrentSales, TotalSales)

The measure uses ALL(Sales), which removes every filter from the Sales table, including slicers and report-level filters.

For our requirements, this is a common mistake that effectively freezes the denominator to the grand total of the entire dataset, regardless of what filters are applied.

For example, when users apply a Year slicer to focus on the previous year, the numerator correctly reflects only the previous year’s sales, but the denominator still reflects all years.

The result is percentages that appear unexpectedly small and never add up to 100% within the filtered context.

We see the percentages are smaller than expected and bring the problem to Copilot.

The Prompt:

I have a measure for % of Total Sales, but when I filter my report to the year 2025, the percentages are small when viewed on multiple visuals across various dimensions and don’t add up to 100%. How do I make this dynamic so I can display the percent of total for multiple dimensions?

% of Total Sales =
VAR CurrentSales = SUM('Sales'[Amount])
VAR TotalSales = CALCULATE(SUM('Sales'[Amount]), ALL('Sales'))
RETURN
DIVIDE(CurrentSales, TotalSales)

Copilot’s Response:

The issue you’re experiencing is due to the use of ALL(‘Sales’) in your measure. This removes all filters from the Sales table—including filters on Year, Product, Department, etc.—so your denominator is always the grand total sales for all years and all data, not just for the filtered year (e.g., 2025). As a result, when you filter to 2025, the numerator is only 2025 sales, but the denominator is still the total for all years, making the percentages appear small and not sum to 100% within the filtered context.

To make the measure dynamic and context-aware (so that it always sums to 100% within the current filter, such as year, product, or department), you should use the REMOVEFILTERS or ALLSELECTED function, depending on your needs:

  • REMOVEFILTERS: Removes filters only from the specified columns or tables, but keeps filters from outside the specified scope (such as report/page/visual filters).
  • ALLSELECTED: Keeps filters applied by slicers and visuals, but ignores filters from the current visual’s breakdown.

Here’s how you can rewrite your measure for dynamic percent of total within the current filter context:

Option 1: Using REMOVEFILTERS (recommended for most scenarios)

% of Total Sales (Dynamic) =
VAR CurrentSales = SUM('Sales'[Amount])
VAR TotalSales = CALCULATE(
SUM('Sales'[Amount]),
REMOVEFILTERS('Sales')
)
RETURN
DIVIDE(CurrentSales, TotalSales)

But this still ignores filters on the Sales table. If you want the denominator to respect external filters (like Year = 2025), but ignore breakdowns within the visual (like Product or Department), use ALLSELECTED.

Option 2: Using ALLSELECTED (best for visuals with slicers/filters)

% of Total Sales (Dynamic) =
VAR CurrentSales = SUM('Sales'[Amount])
VAR TotalSales = CALCULATE(
SUM('Sales'[Amount]),
ALLSELECTED('Sales')
)
RETURN
DIVIDE(CurrentSales, TotalSales)

How to choose:

  • Use REMOVEFILTERS if you want the denominator to always be the total for the current report/page/visual filter context (e.g., Year = 2026).
  • Use ALLSELECTED if you want the denominator to respect slicers and filters, but ignore breakdowns within the visual.

Try replacing your measure with the ALLSELECTED version above. When you filter to 2026, the percentages across your breakdown (e.g., by Product, Department, etc.) will sum to 100% for that year.

🟡 Yellow Light

If you are building reports where slicers play a central role in user decision-making, providing essential context before users make their selections can be extremely beneficial. 

The design of those slicers matters just as much as the DAX behind them. For a deeper look at how to turn static slicers into interactive decision aids that give users meaningful context before they make a selection, check out Design Meets Data: From Static Slicers to Interactive Decision Aids.

What if your slicers could answer questions before you even select them? With a little preview context, they can guide users toward the right decisions instantly.

Overlooked Detail: The distinction between REMOVEFILTERS and ALLSELECTED is subtle but consequential.

If a filter is applied at the visual level, ALLSELECTED will calculate the percentage based on the filtered data, whereas REMOVEFILTERS may still calculate against the broader filter context.

In an interactive report with multiple slicers and cross-filtering visuals, these two measures can return noticeably different results from the same visual.


Scenario 5: Generating Measure Descriptions and Comments
Documenting the Semantic Model

One of the most tedious parts of semantic model development is documentation. Measures get built, reports get published, and descriptions rarely get written. This test determines how useful Copilot can be in handling that documentation work and whether the output is accurate and professional enough to actually use.

Test 1 — Generating Measure Descriptions in Model View

For this test, we navigate to Power BI Desktop’s Model view, select the Total Sales YoY Growth % measure, and use the Create with Copilot option in the Properties pane.

Copilot’s Response:

🟢 Green Light

Test 2: Adding Inline Comments in DAX Query View

Measure descriptions in the Properties pane are valuable for self-service users browsing the Fields pane, but they are not visible to the developer editing the measure.

Inline comments inside the DAX formula serve a different purpose. They document the logic for anyone who opens the measure editor, making complex calculations easier to read, audit, and maintain over time.

DAX query view Copilot can add those comments automatically.

  1. Load the measure. Switch to DAX query view using the left navigation bar. In the Data pane, right-click the Total Sales YoY Growth % measure and select Define and Evaluate. This loads the full measure definition into the query editor.
  2. Add comments with Copilot, Press CTRL + I to open the inline Copilot. Enter the prompt and press Enter.
  3. Commit the changes. Select Keep query and then Update model with changes to commit the commented measure definition back to the model.

The Prompt: Add comments to this MEASURE

Copilot’s Response:

🟢 Green Light

Overlooked Detail: Copilot scoped the comments specifically to the DEFINE MEASURE block. It did not attempt to comment the EVALUATE portion of the query loaded by Define and Evaluate.

This is the right behavior. The comments that travel with the measure when we select Update model with changes are the ones inside the DEFINE MEASURE block. Copilot correctly focused its documentation effort where it counts.


Practitioner’s Verdict:
How Far Can You Trust Copilot for DAX Development?

After running Copilot through five scenarios — writing measures, building advanced logic, explaining existing DAX, debugging logical errors, and documenting the semantic model — here is the honest assessment.

Copilot is a capable and practical assistant for semantic model development. Across the five scenarios, it earned five Green Lights and three Yellow Lights. No Red Lights, but the Yellow Lights carry real weight and are worth understanding before we start offloading DAX work to AI.

Where Copilot hits its stride:

The results were strongest where the task was well-defined and the model was clean. Basic and intermediate measures came back quickly and accurately. The debugging scenario performed well — correctly diagnosing an ALL vs ALLSELECTED issue, explaining the underlying cause clearly, and offering two well-reasoned solutions with guidance on when to use each. Measure descriptions were accurate, readable, and business-aware rather than just restating the formula name.

The explanation scenario highlighted something equally valuable. Feed Copilot a complex, multi-variable measure with HTML rendering logic and it will walk through it step by step in plain language. For onboarding new team members, auditing inherited models, or simply revisiting logic written months ago, that capability is genuinely useful.

The DAX query view inline Copilot adds further value. The Suggest measures feature returned six well-structured analytical measures in a single prompt, complete with inline comments and a live preview. The Add comments workflow produced useful documentation rather than just variable name restatements. These are the features that make the biggest difference in team environments where maintainability matters.

Where we still have to drive:

The three Yellow Lights pointed to the same underlying pattern: Copilot answers the question we ask, not necessarily the question we should have asked.

The YoY Growth measure was syntactically strong but assumed a properly configured Date table without helping us verify or build one.

The percentage contribution measure was correct for the prompt, but did not anticipate how ALL versus ALLSELECTED would behave in an interactive report with slicers.

In both cases, Copilot did its job. The gap was in the prompt, the model structure, or the report design context that Copilot simply does not have access to.

This is the part that matters most: Copilot does not know our report. It does not know how users interact with slicers or what the business actually needs versus what was typed into the prompt. That context lives with the developer, not the AI.

The broader DAX community reinforces this point. The continued investment in deep technical content covering advanced concepts signals that DAX expertise is not becoming less relevant in an AI-assisted world. If anything, it is becoming more important. We need to know what correct DAX looks like to evaluate what Copilot hands us.

Copilot in Power BI Desktop is a practical productivity tool for Power BI semantic model development. It handles the repetitive, time-consuming parts of DAX work reliably enough to trust, with a close eye and a solid foundation in the fundamentals.

The tool earns its keep. Your expertise still earns its keep, too.

The sample report is available at the link below!

In an era where AI is moving from experimental to essential, this toolkit is designed to help Power BI developers explore AI workflows, learn Power BI AI tools, and get started on their journey to modernize the way they build!


What’s Next: A Look at What’s Coming

This post focused on one specific question: how useful is Copilot in Power BI Desktop for the day-to-day work of semantic model development?

Across five scenarios, the answer is encouraging. Copilot is a practical addition to a developer’s workflow, particularly for first drafts, documentation, and explaining DAX logic to team members.

But this post only covers one piece of what AI-assisted Power BI development looks like in 2026.

There is a meaningful difference between using AI as a conversational assistant inside a tool and configuring AI with the right context, tools, and environment to take real action on a model.

As that setup becomes more capable, the tasks we can delegate to AI grow significantly, from generating a single measure to planning and executing complex modeling workflows with minimal manual intervention.

That progression is what the rest of this series will explore:

  • Copilot in Power BI Desktop for Report Development: staying within the built-in experience but shifting focus from semantic model development to report design, visual creation, and data exploration
  • AI-Assisted Development: stepping outside Power BI Desktop to pair a more capable AI interface with direct, programmatic access to the semantic model. Two practical approaches worth exploring here: VS Code with GitHub Copilot and the Power BI Modeling MCP Server, and Claude Desktop with MCP Servers, both of which unlock capabilities the built-in Copilot pane cannot match.
  • Agentic Development: the most advanced tier, where community projects like GitHub – data-goblin/power-bi-agentic-development or articles such as Introducing AI and agentic development for Power BI explore what fully autonomous Power BI development workflows look like when agents plan and execute tasks end to end.

Each post in the series will follow the same format used here — real prompts, real outputs, and a clear-eyed view of where AI earns its keep and where the human still needs to drive.


Thank you for reading! Stay curious, and until next time, happy learning.

And, remember, as Albert Einstein once said, “Anyone who has never made a mistake has never tried anything new.” So, don’t be afraid of making mistakes, practice makes perfect. Continuously experiment, explore, and challenge yourself with real-world scenarios.

If this sparked your curiosity, keep that spark alive and check back frequently. Better yet, be sure not to miss a post by subscribing! With each new post comes an opportunity to learn something new.

Design Meets Data: Crafting Interactive Navigations in Power BI


An essential aspect of any multi-page report is an effective and intuitive way to navigate between the various report pages. A well-designed navigational element in our Power BI reports enhances our users’ experience and guides them to the data and visualizations they require.

This post outlines and explores how Power BI native tools and functionalities can be utilized to create a similar navigational experience that was created in the previous post using Figma and Power BI: Design Meets Data: A Guide to Building Interactive Power BI Report Navigation.

Looking for another approach to building report navigation that uses built-in Power BI tools? Visit Part 3 of this Power BI Navigation series.

Streamlined report navigation with built-in tools to achieve functional, maintainable, and engaging navigation in Power BI reports.

For those interested in implementing the navigation element presented in this post, there are 2-, 3-, 4-, and 5-page templates available for download, with more details at the end of the post.

Revisiting Interactive Navigation in Power BI

Welcome back to another Design Meets Data exploration focused on interactive report navigation in Power BI. In the first part, we dove into using Figma to design and develop a user-friendly report interface.

Now, it is time to shift our focus towards leveraging Power BI’s native arsenal of tools, primarily bookmarks, buttons, and tool tips, to achieve similar, if not enhanced, functionalities.

Why go native? Utilizing Power BI’s built-in tools streamlines support and maintenance and provides a reduction in external complexities and dependencies. Plus, staying within a single platform makes it easier to manage and update our reports.

This post will highlight the nuances of Power BI’s navigation capabilities. It will demonstrate how to replicate the interactive navigation from Design Meets Data: A Guide to Building Interactive Power BI Report Navigation using tools available directly within Power BI. These tools will help simplify our report while maintaining an engaging and interactive navigational element.

Let’s get started!


Setting the Stage with Power BI Navigation

Before diving into the details, let’s step back with a quick refresher on the Power BI tools that we can leverage for crafting our report navigation. Power BI is designed to support complex reporting requirements with ease, thanks to features like bookmarks, buttons, and tooltips that can be intricately configured to guide our users through our data seamlessly.

Bookmarks

Bookmarks in Power BI save various states of a report page, allowing users to switch views or data contexts with a single click. We can use bookmarks to allow our users to toggle between different data filters or visual representations without losing context or having to navigate multiple pages.

For our navigational element, bookmarks will be key to creating the collapsing and expanding functionality. To create a bookmark, we get the report page looking just right, then add a bookmark to save the report state in the bookmark pane.

The new bookmark can now act as a restore point, bringing the user back to this specific view whenever it is selected. To keep our bookmarks organized it is best to rename them with a description name, generally including the report page and an indication of what the bookmark is used for (e.g. Page1-NavExpanded).

Buttons

Buttons take interactivity to the next level. We can use buttons to trigger various events, such as bookmarks, and also serve as navigation aids within the report. Buttons within our Power BI reports can be styled and configured to react dynamically to user interactions.

To create a button, we simply add the button object from the Insert ribbon onto the report canvas. Power BI offers a variety of button styles, such as a blank button for custom designs, or predefined icons for common actions like reset, back, or informational buttons.

Each button can be styled to match our report’s theme, including colors, text, and much more. Another key property to configure is the button action. Using this, we can define whether the button should direct our users to a different report page, switch the report context to a different bookmark, or another one of the many options available.

Tooltips

Tooltips in Power BI can provide simple text hints, but when properly utilized, they can provide additional insights or contextual data relevant to specific visuals without cluttering the canvas. This provides detail when required while keeping our reports clean and simple.

Power BI allows us to customize tooltips to show detailed information, including additional visuals. This can turn each tooltip into a tool to provide context or additional layers of data related to a report visual when a user hovers over the element.

By effectively using tooltips we transform user interaction from just viewing to an engaging, exploratory experience. This boosts the usability of our reports and ensures that users can make informed decisions based on the data view provided.


The Navigation Framework

Now that we have explored some details of the elements used to create our navigation, let’s dive into building the navigational framework. We will craft a minimalistic navigation on the left-hand side of our report, with the functionality to expand when requested by user interaction. This approach to our navigation is focused on making the navigation pane both compact and informative, ensuring that it does not overpower the content of the report.

In the Design Meets Data: A Guide to Building Interactive Power BI Report Navigation blog post the navigational element was built using Figma. Although Figma is a powerful and approachable design tool, in this guide, we will explore creating a similar navigation pane using native Power BI tools and elements. We will use Power BI’s shapes, buttons, and bookmarks to construct the framework and functionality.

The Navigation Pane Base Elements

We will start by creating the navigation pane by adding the base elements. In this compact and expandable design, this includes the background of the navigation pane, which will contain the page navigation and menu icons.

Collapsed Navigation Pane

The base of the navigation consists of three main components that we add to our Power BI report to start building our interactive navigational element.

The collapsed navigation pane starts by adding the shape of the pane itself. The color is set to theme color 1, 50% darker of the Power BI theme. Using the theme color will help our navigation remain dynamic when changing Power BI themes.

The next base element is the menu icon, which expands and collapses our navigation pane. The button is configured to slightly darken when hovered over and darken further when pressed. Additionally, when the button is disabled, the icon color is set to the same color as the navigation pane and is used to contrast the current page indicator bar. This configuration is used for all buttons contained within the navigation pane (both the bookmark and page navigation buttons).

The last base element is the current page indicator. This is a lighter-colored (theme color 1, 60% lighter) rectangle tab that clearly indicates what page in the navigation pane is currently being viewed.

Here is the collapsed navigation pane containing the base elements.

Expanded Navigation Pane

The expanded navigation consists of the same base elements, with the addition of a close icon, and a click shield to prevent the user from interacting with the report visuals when the navigation is expanded.

The additional elements of the expanded menu provide the user with multiple methods to collapse the navigation pane. The close (X) button is added as a flyout from the base navigation pane background, so it is easily identifiable.

When the navigation pane is expanded, we want to prevent users from interacting with the report visuals. To achieve this, we use a partially transparent rectangle to serve as a click shield. If the user clicks anywhere on the report page outside of the navigation pane, the navigation pane will collapse returning the user to the collapsed report view.

Navigation Bookmarks

The last base element required for the interactive navigation is creating the required bookmarks to transition between the collapsed and expanded view. This is done by creating two bookmarks to store each of the required report page views, Page1-Default-NavCollapsed and Page1-NavExpanded.

We can now build on these base elements and bring our navigation to life with Power BI buttons and interactive features.


Navigation Interactive Features

The interactive features in the navigation pane consist of two types of buttons: (1) bookmark buttons and (2) page navigation buttons.

Expanding and Collapsing the Navigation Pane

The previous section added the base elements of the navigation pane which included a menu icon on both the collapsed and expanded navigation panes, and a close button and click shield on the expanded navigation screen.

Building the interactive elements of the navigation starts by assigning actions to each of these bookmark buttons, allowing the user to expand and collapse the navigation pane seamlessly.

The action property for each of these buttons is set to a bookmark type, with the appropriate bookmark selected. For example, for the menu icon button on the collapsed menu, the bookmark selected corresponds to the expanded navigation bookmark. This way, when a user selects this button on the collapsed navigation, it expands, revealing the additional information provided on the expanded navigation pane.

Page Navigation Buttons

The last element to add to the report navigation is the report page navigation buttons.

Each report page button is a blank button configured and formatted to meet the report’s requirements. For this report, each page button contains a circular numbered icon to indicate the report page it navigates to. When the navigation is expanded, an additional text element displays the report page title.

At the end of this post, there are details on obtaining templates that implement this report navigational element. The templates are fully customizable, so they will come with the numbered icons and default page titles, but these can simply be updated to match the aesthetic of any reporting needs.


Wrapping Up: Elevating Your Power BI Reports with Interactive Navigation

As Power BI continues to evolve, integrating more engaging and interactive elements into our reports will become crucial for creating dynamic and user-centric reports. The transition from static to interactive reports empowers our users to explore data in a more meaningful and memorable way. By leveraging bookmarks, buttons, and tooltips, we can transform our reports from a simple presentation of data into engaging, intuitive, and powerful analytical tools.

For those eager to implement the navigational element outlined in this post, there are 2-, 3-, 4-, and 5-page templates available for download. Each template has all the functionality built in, requiring only updating the button icons, if necessary, to better align with your reporting needs.

The template package is available here!

You will get individual template files for a 2-, 3-, 4-, and 5-page report provided in the PBIX, PBIT, and PBIP (12 total files) formats! 


Thank you for reading! Stay curious, and until next time, happy learning.

And, remember, as Albert Einstein once said, “Anyone who has never made a mistake has never tried anything new.” So, don’t be afraid of making mistakes, practice makes perfect. Continuously experiment, explore, and challenge yourself with real-world scenarios.

If this sparked your curiosity, keep that spark alive and check back frequently. Better yet, be sure not to miss a post by subscribing! With each new post comes an opportunity to learn something new.

Dive into DAX: Elevated Insights with Information Functions


In data analytics effectively navigating, understanding, and interpreting our data can be the difference between data-driven insights and being lost in a sea of data. In this post we will focus on a subset of DAX functions that help us explore our data – the Information Functions.

These functions are our key to unlocking deeper insights, ensuring data integrity, and enhancing the interactivity of our reports. Let’s embark on a journey to not only elevate our understanding of Power BI and DAX but also to harness the full potential of our data.

The Role of Information Functions in DAX

Information functions play a crucial role in DAX. They are like the detective of our Power BI data analysis, helping us identify data types, understand relationships, handling errors, and much more. Whether we are checking for blank values, understanding the data type, or handling various data scenarios, information functions are our go-to tools.

Diving deep into information functions goes beyond today’s problems and help prepare for tomorrow’s challenges. Mastering these functions enables us to clean and transform our data efficiently, making our analytics more reliable and our insights more accurate. It empowers us to build robust models that stand the test of time and data volatility.

In our exploration through the world of DAX Information Functions, we will explore how these functions work, why they are important, and how we can use them to unlock the full potential of our data. Ready to dive in?

For those of you eager to start experimenting there is a Power BI report pre-loaded with the same data used in this post ready for you. So don’t just read, follow along and get hands-on with DAX in Power BI. Get a copy of the sample data file here:

This dynamic repository is the perfect place to enhance your learning journey.


The Heartbeat of Our Data: Understanding ISBLANK and ISEMPTY

In data analysis knowing when our data is missing, or blank is key. The ISBLANK and ISEMPTY functions in DAX help us identify missing or incomplete data.

ISBLANK is our go-to tool to identify voids within our data. We can use this function to locate and handle this missing data either to report on it or to manage it, so it does not unintentionally impact our analysis. The syntax is straightforward.

ISBLANK(value)

Here, value is the value or expression we want to check.

Let’s use this function to create a new calculated column to check if our sales amount is blank. This calculated column can then be used to flag and identify these records with missing data, and ensure they are accounted for in our analysis.

MissingSalesAmount = ISBLANK(Sales[Amount])

We can now use this calculated column to provide a bit more context to card visuals showing our total sales for each of our products.

In this example we can see the total sales for each product category. However, if we only show the total sales value it could lead our viewers to believe that the TV category has no sales. By adding a count of TV Sales records that have a blank sales amount we can inform our viewers this is not the case and that there is an underlying data issue.

While ISBLANK focuses on the individual values, ISEMPTY takes a step back to consider the table as a whole. We can use this function to check whether the table we pass to this DAX expression contains data. Its syntax is also simple.

ISEMPTY(table_expression)

The table_expression parameter can be a table reference or a DAX expression that returns a table. ISEMPTY will return a value of true if the table has no rows, otherwise it returns false.

Let’s use ISEMPTY in combination with ISBLANK to check if any of our sales records have a missing or blank employee Id. Then we can use this information, to display the count of records (if any) and the total sales value of these records.

In this example we see that Sales with No EmployeeId returns a value of false, indicating that the table resulting from filtering our sales records to sales with a blank employee Id is not empty and contains records. We can then also make use of this function to help display the total sales of these records and a count.

These two functions are the tools to use when investigating our datasets for potentially incomplete data. ISBLANK can help us identify rows that need attention or consideration in our analysis and ISEMPTY helps validate if a subset of data exists in our datasets. To effectively utilize these two functions, it is important to remember their differences. Remember ISBLANK checks if an individual value is missing data, while ISEMPTY examines if a table contains rows.


The Data Type Detectives: ISTEXT, ISNONTEXT, ISLOGICAL and ISNUMBER

In the world of data, not every value is as it seems and that is where this set of helpful DAX expressions come into play. These functions help identify the data types of the data we use within our analysis.

All of these functions follow the same syntax.

ISTEXT(value)
ISNONTEXT(value)
ISLOGICAL(value)
ISNUMBER(value)

Each function checks the value and returns true or false. We will use a series of values to explore how each of these functions work. To do this we will create a new table which uses each of these functions to check the values: TRUE(), 1.5, “String”, “1.5”, and BLANK(). Here is how we can do it.

TestValues = 
VAR _logical = TRUE()
VAR _number = 1.5
VAR _text = "String"
VAR _stringNumber = "1.5"

VAR _testTable =
{
   ("ISLOGICAL", ISLOGICAL(_logical), ISLOGICAL(_number), ISLOGICAL(_text), ISLOGICAL(_stringNumber), ISLOGICAL(BLANK())),
   ("ISNUMBER", ISNUMBER(_logical), ISNUMBER(_number), ISNUMBER(_text), ISNUMBER(_stringNumber), ISNUMBER(BLANK())),
   ("ISTEXT", ISTEXT(_logical), ISTEXT(_number), ISTEXT(_text), ISTEXT(_stringNumber), ISTEXT(BLANK())),
   ("ISNONTEXT", ISNONTEXT(_logical), ISNONTEXT(_number), ISNONTEXT(_text), ISNONTEXT(_stringNumber), ISNONTEXT(BLANK()))
}

RETURN
SELECTCOLUMNS(
   _testTable,
   "Function", [Value1],
   "Test Value: TRUE", [Value2],
   "Test Value: 1.5", [Value3],
   "Test Value: String", [Value4],
   "Test Value: '1.5'", [Value5],
   "Test Value: BLANK", [Value6]
)

An then we can add a table visual to our report to see the results and better understand how each function treats our test values.

The power of these functions lies in their simplicity, the clarity they can bring to our data preparation process, and their ability to be used in combination with other expressions to handle complex data scenarios.

DAX offers a number of other functions that are similar to the ones explored here such as ISEVEN, ISERROR, and ISAFTER. Visit the DAX reference guide for all the details.

Learn more about: DAX Information Functions

A common issue we can run into in our analysis is assuming data types based on a value’s appearance or context, leading to errors in our analysis. Mistakenly performing a numerical operation on a text field that appear numeric can easily throw our results into disarray. Using these functions early in our process to understand our data paves the way for clean, error-free data processing.


The Art of Data Discovery: CONTAINS & CONTAINSSTRING

When diving into our data, pinpointing the information we need can be a daunting task. This is where DAX steps in and provides us CONTAINS and CONTAINSSTRING. These functions help us uncover the specifics hidden within our data.

CONTAINS can help us identify whether a table contains a row that matches our criteria. Its syntax is as follows.

CONTAINS(table, columnName, value[, columnName, value]...)

The table parameter can be any DAX expression that returns a table of data, columnName is the name of an existing column, and value is any DAX expression that returns a scalar value that we are searching for in columnName.

CONTAINS will return a value of true if each specified value can be found in the corresponding columnName (i.e. columnName contains value), otherwise it will return false.

In our previous ISBLANK example we created a Blank Count measure to help us identify how many sales records for our product categories are missing sales amounts.

Blank Count = 
COUNTROWS(
   FILTER(Sales, Sales[MissingSalesAmount]=true)
)

Now, if we are interested in knowing just if there are missing sales amounts, we could update this expression to return true if COUNTROWS returns a value greater than 0, however this is where we can use CONTAINS to create a more effective measure.

Missing Sales Amount = 
CONTAINS(Sales, Sales[MissingSalesAmount], TRUE())

CONTAINS can be a helpful tool, however, it is essential to distinguish when it is the best tool for the task versus when an alternative might offer a more streamlined approach. Alternatives to consider include functions such as IN, FILTER, or TREATAS depending on the need.

For example, CONTAINS can be used to establish a virtual relationship between our data model tables, but functions such as TREATAS may provide better efficiency and clarity. For details on this function and its use check out this post for an in-depth dive into DAX Table Manipulation Functions.

Discover how to Reshape, Manipulate, and Transform your data into dynamic insights.

For searches based on textual content, CONTAINSTRING is our go to tool. It specializes in revealing rows where text columns contain specific substrings. The syntax is straightforward.

CONTAINSSTRING(within_text, find_text)

The within_text parameter is the text we want to search for the text passed to the find_text parameter. This function will return a value of true if find_text is found, otherwise it will return false.

We can use CONTAINSSTRING to dissect our Product Code and enrich our dataset by adding a calculated column containing a user-friendly color value of the product. Here is how.

Color = SWITCH(
    TRUE(),
    CONTAINSSTRING(Products[Product Code], "BK"), "Black",
    CONTAINSSTRING(Products[Product Code], "rd"), "Red",
    CONTAINSSTRING(Products[Product Code], "GR"), "Gray",
    CONTAINSSTRING(Products[Product Code], "SL"), "Silver",
    CONTAINSSTRING(Products[Product Code], "BL"), "Blue"
)

This new calculated column provides us the color of each product that we can use in a slicer, or we can visualize our totals sales by the product color.

CONSTAINSSTRING is case-insensitive, as shown in the red color statement above. When we require case-sensitivity CONSTAINSSTRINGEXACT provides us this functionality.

CONTAINSSTRING just begins to scratch the surface of the DAX functions available to use for in-depth textual analysis, to continue exploring visit this post that focuses solely on DAX Text Functions.

Stringing Along with DAX: Dive Deep into Text Expressions

By leveraging CONTAINS and CONTAINSTRING – alongside understanding when to employ their alternatives – we are equipped with the tools required for precise data discovery within our data analysis.


Deciphering Data Relationships: ISFILTERED & ISCROSSFILTERED

Understanding the dynamics of data relationships is critical for effective analysis. In the world of DAX, there are two functions we commonly turn to that guide us through the relationship network of our datasets: ISFILTERED and ISCROSSFILTERED. These functions provide insights into how filters are applied within our data model, offering a deeper understanding of the context in which our data operates.

ISFILTERED offers a window into the filtering status of a table or column, allowing us to determine whether a filter has been applied directly to that table or column. This insight is valuable for creating responsive measures that adjust based on user selections or filter context. The syntax is as follows.

ISFILTERED(tableName_or_columnName)

A column or table is filtered directly when a filter is applied to any column of tableName or specifically to columnName.

Let’s create a dynamic measure that leverages ISFILTERED and reacts to user selections. In our data model we have a measure that calculates the product sales percentage of our overall total sales. This measure is defined by the following expression.

Product Percentage of All Sales = 
VAR _filterdSales = [Total Sales]
VAR _allSales = CALCULATE([Total Sales], ALL(Products[Product]))

RETURN
DIVIDE(_filterdSales, _allSales, 0)

We can see this measure displays the percentage of sales for the selected product. However, when no product is selected it displays 100%, although this is expected and correct, we would rather not display the percentage calculation when there is no product selected in our slicer.

This is a perfect case to leverage ISFILTERED to first check if our Products[Product] column is filtered, and if so, we can display the calculation, and if not, we will display “N/A”. We will update the measure’s definition to the following.

Product Percentage of All Sales = 
VAR _filterdSales = [Total Sales]
VAR _allSales = CALCULATE([Total Sales], ALL(Products[Product]))

RETURN
IF(
    ISFILTERED(Products[Product]),
    //The Product column is directly filtered
    DIVIDE(_filterdSales, _allSales, 0),
    //The Product column is not directly filtered
    "N/A"
)

And here are the updated results, we can now see when no product is selected in the slicer the measure displays “N/A”, and when the user selects a product, the measure displays the calculated percentage.

While ISFILTERED focuses on direct filter application, understanding the impact of cross-filtering, or how filters on one table affect another related table, is just as essential in our analysis. ISCROSSFILTERED goes beyond ISFILTERED and helps us identify if a table or column has been filtered directly or indirectly. Here’s its syntax.

ISCROSSFILTERED(tableName_or_columnName)

ISCROSSFILTERED will return a value of true when the specified table or column is cross-filtered. The table or column is cross-filtered when a filter is applied to columnName, any column of tableName, or any column of a related table.

Let’s explore ISCROSSFILTERED and how it differs from ISFILTERED with a new measure similar to the one we just created. We define the new measure as the following.

Product Percentage of All Sales CROSSFILTER = 
VAR _filterdSales = [Total Sales]
VAR _allSales = CALCULATE([Total Sales], ALL(Sales))

RETURN
IF(
    ISCROSSFILTERED(Sales),
    //Sales table is cross-filtered
    DIVIDE(_filterdSales, _allSales, 0),
    //The Sales table is not cross-filterd
    "N/A"
)

In this measure we utilize ISCROSSFILTERED to check if our Sales table is cross-filtered, and if it is we calculate the proportion of filtered sales to all sales, otherwise the expression returns “N/A”. With this measure we can gain a nuanced view of product performance within the broader sales context.

When our Sales table is only filtered by our Product slicer, we see that the ISFILTERED measure and the ISCROSSFILTERED measure return the same value (below on the left). This is because as before the column Products[Product] is directly filtered by our Product slicer, so the ISFILTERED measure carries out the calculation and returns the percentage.

But also, since our data model has a relationship between our Product and Sales table, the selection of a product in the slicer indirectly filters, or cross-filters, our Sales table leading to our CROSSFILTER measure returning the same value.

We start to see the difference in these functions when we start to incorporate other slicers, such as our region slicer. In the middle image, we can see if no product is selected our Product Performance card display “N/A”, because our Products[Product] column is not being directly filtered.

However, the Sales Performance card that uses our CROSSFILTER measure is dynamically updated to now display the percentage of sales associated with the selected region. Again, this is because our data model has a relationship between our Region and Sales table, so the selection of a region is cross-filtering our sales data.

Lastly, we can see both measures in action when a Product and Region are selected (below on the right).

Using ISFILTERED, we can create reports that dynamically adjust to user interactions, offering tailored insights based on specific filters. ISCROSSFILTERED takes this a step further by allowing us to understand and leverage the nuances of cross-filtering impacts within our data, enabling an even more sophisticated analytical approach.

Applying these two functions within our reports allows us to enhance data model interactivity and depth of analysis. This helps us ensure our reports respond intelligently to user interactions and reflect the complex interdependencies within our data.


Wrapping Up

Compared to other function groups DAX Information Functions may be overlooked, however these functions can hold the key to unlocking insights, providing a deeper understanding of our data’s structure, quality, and context. Effectively leveraging these functions can elevate our data analysis and integrating them with other DAX function categories can lead to the creation of dynamic and insightful solutions.

As we continue to explore the synergies between different DAX functions, we pave the way for innovative solutions that can transform raw data into meaningful stories and actionable insights. For more details on Information Functions and other DAX Functions visit the DAX Reference documentation.

Learn more about: DAX Functions


Thank you for reading! Stay curious, and until next time, happy learning.

And, remember, as Albert Einstein once said, “Anyone who has never made a mistake has never tried anything new.” So, don’t be afraid of making mistakes, practice makes perfect. Continuously experiment and explore new DAX functions, and challenge yourself with real-world data scenarios.

If this sparked your curiosity, keep that spark alive and check back frequently. Better yet, be sure not to miss a post by subscribing! With each new post comes an opportunity to learn something new.