Calculate(), Filter() And DAX Memory Usage

Following on from my last post on the Query Memory Limit in Power BI, in this post I want to look at one of the most common DAX antipatterns and its effect on query memory usage: filtering on a whole table, rather than an individual column, in the filter parameters of the Calculate() function. A lot has already been written on this particular antipattern from the point of view of query performance – for example see here and here – but it’s only with the recent addition of the Execution Metrics Profiler/Log Analytics event that you can see how bad it is for memory usage too.

Here’s a simple example. Using a semantic model based on the UK Land Registry’s Price Paid data, with two dimension tables (Date and Property Type) and a fact table called Property Transactions with just over a million rows:

…let’s say that you write a measure that counts the number of property transactions for detached properties, ie counting the rows in the Property Transactions table where the Property Type column contains the value “D”. Here’s a first attempt at that:

Detached Count V1 =
CALCULATE (
    COUNTROWS ( 'Property Transactions' ),
    FILTER ( 'Property Transactions', 'Property Transactions'[Property Type] = "D" )
)

Notice that this measure follows the antipattern of filtering on the whole Property Transactions table with the Filter() function just to filter on the Property Type column.

Here’s a DAX query that shows the results of this measure for all Postcodes. It returns around 153000 rows to show the memory impact of the way the measure is written.

EVALUATE
SUMMARIZECOLUMNS(
    'Property Transactions'[Postcode],
    "Detached Count", [Detached Count V1]
)

Here’s what the Execution Metrics event returns for this query:

{
	"timeStart": "2024-06-28T15:41:59.951Z",
	"timeEnd": "2024-06-28T15:42:00.388Z",

	"durationMs": 438,
	"vertipaqJobCpuTimeMs": 47,
	"queryProcessingCpuTimeMs": 391,
	"totalCpuTimeMs": 438,
	"executionDelayMs": 0,

	"approximatePeakMemConsumptionKB": 22246,

	"commandType": 27,
	"queryDialect": 3,
	"queryResultRows": 152583
}

In this case the memory usage of the query, as given by the approximatePeakMemConsumptionKB metrci, is 22246KB or about 23MB.

Now consider this version of the measure:

Detached Count V2 =
CALCULATE (
    COUNTROWS ( 'Property Transactions' ),
    'Property Transactions'[Property Type] = "D"
)

Running the same DAX query as before but with this new measure you get the same results but with a much lower memory usage of 8734KB or 9MB:

{
	"timeStart": "2024-06-28T15:56:09.431Z",
	"timeEnd": "2024-06-28T15:56:09.602Z",

	"durationMs": 172,
	"vertipaqJobCpuTimeMs": 31,
	"queryProcessingCpuTimeMs": 125,
	"totalCpuTimeMs": 172,
	"executionDelayMs": 0,

	"approximatePeakMemConsumptionKB": 8734,

	"commandType": 27,
	"queryDialect": 3,
	"queryResultRows": 152583
}

Query duration and CPU usage is reduced too, but as I said above this highlights how different DAX patterns can result in very different memory footprints. Filtering on just the column you want to filter, rather than the whole table, is a lot more memory efficient! If you’re getting the “This visual has exceeded the available resources error because you’ve hit the Query Memory Limit then it’s worth checking to see if you have used this antipattern in your measures and rewriting accordingly.

Footnote: it’s worth mentioning that two other variations on the rewritten measure, one using the Property Type column on the Property Type dimension table instead of the Property Type column on the fact table:

Detached Count V3 =
CALCULATE (
    COUNTROWS ( 'Property Transactions' ),
    'Property Type'[Property Type] = "D"
)

…and one that added the KeepFilters() function (which is commonly used in this type of measure):

Detached Count V4 =
CALCULATE (
    COUNTROWS ( 'Property Transactions' ),
    KEEPFILTERS ( 'Property Type'[Property Type] = "D" )
)

…both had the same memory usage as the V2 version of the measure, 9MB.

13 thoughts on “Calculate(), Filter() And DAX Memory Usage

  1. Slightly off-topic: Is the “Execution Metrics” event something we should be able to see in Jeffrey’s DAX Debug Output tool or would that need to be updated to include the event?

  2. how does this make sense in the context of:

    CALCULATE(
    [measure],
    Table[Item] = “thing”
    )

    being the exact same xmsql as:

    CALCULATE(
    [measure],
    FILTER(
    ALL(Table),
    [Item] = “thing”
    )
    )

    1. xmsql is just an attempt to represent what the Storage Engine is doing – and I guess this is a case where the representation isn’t perfect. The query plans are different.

    2. CALCULATE(
      [measure],
      Table[Item] = “thing”
      )

      is equivalent to :

      CALCULATE(
      [measure],
      FILTER(
      ALL(Table[Item]),
      [Item] = “thing”
      )
      )

      cf p 120 Definitive Guide to DAX Second edition.

      1. Yes, absolutely. But many people don’t know that and use Filter on a whole table rather than on an individual column, with unfortunate results.

  3. Chris, why does DAX look so much like SQL yet it behaves in very different ways❓🤓🇧🇷

    1. Does it? Looks nothing like SQL to me. SQL is declarative with keywords separated by spaces, DAX is purely functional.

  4. Thank you for clarifying this!

    Do you happen to have suggestions when boolean conditions are not possible and a FILTER statement has to be used, like when a dynamic measure would have to be used in the FILTER context?

    Microsoft even mentions it in one of the links you gave (https://learn.microsoft.com/en-us/dax/best-practices/dax-avoid-avoid-filter-as-filter-argument – cfr. “[…]Consider now a different measure definition.[…]”) but they don’t offer any suggestions on how to fix this.

    Any dynamic dashboard I have ever made uses a structure like for example:

    AmountDue =
    CALCULATE( [AmountTotal],
    FILTER(FactTable,
    [Days Left] >= 0
    &&[SomeOtherFilter]=”Something”
    )
    )

    With [Days Left] being some If-And-Or measure.

    So I feel like there might be a more optimal way of approaching such a model, but I have yet to find it.

    1. The measure name [Days Left] makes me think there’s a dimension that should be filtered here instead of your fact table. Is it an invoice number, order line or something similar?

Leave a Reply to Thierry VanroyCancel reply