Using ALLSELECTED with no arguments in a remote model later used in a composite model might produce unexpected results. In this article we examine the topic and provide the reasons why ALLSELECTED requires special attention.
When building models that might be extended through composite models, you need to pay special attention to certain details in DAX. The important differences are in the handling of calculation groups and in the behavior of ALLSELECTED. We described the behavior of calculation groups in composite models in the article, Understanding the interactions between composite models and calculation groups. This article describes the unique behavior of ALLSELECTED when used with no arguments in a measure defined in the remote model of a composite model.
We do not describe ALLSELECTED in detail here. Its behavior is rather complex, so the curious reader will find more deails in The definitive guide to ALLSELECTED. For the sake of the current topic, the interesting aspect is that you can invoke ALLSELECTED with no arguments. In that case, ALLSELECTED restores the shadow filter context on any table in the model.
The thing is: in order to restore the shadow filter context on the entire model, the DAX engine needs to know about the model itself. What the engine knows about the model depends on whether the code executed is wholesale or retail. If you are not familiar with wholesale versus retail execution in composite models, you can find a description here: Introducing wholesale and retail execution in composite models.
When the code is wholesale, ALLSELECTED is executed entirely by the remote server and the remote server is not aware of the tables defined in the local model. As such, if there is a filter on a local table, the remote engine cannot restore shadow filter contexts present on the local table, unless it is instructed to do so in the query.
In order to demonstrate the behavior, we create this structure in a composite model.
Date and Sales are tables in the remote model, whereas Local Date is a copy of Date stored in the local model. Both Date and Local Date are linked to Sales using the Order Date column.
We start by analyzing the behavior of ALLSELECTED in a simple scenario, adding complexity one step at a time until we reach the point where ALLSELECTED shows a curious behavior. At that point, the knowledge gained earlier will ensure you are not taken by surprise.
In the remote model we defined a simple measure that uses ALLSELECTED:
Sales Allselected = CALCULATE( [Sales Amount], ALLSELECTED () )
The measure works fine when executed on the remote model. You can see this in the following report that shows both Sales Amount and Sales Allselected in a matrix with a slicer filtering one year only.
The value shown by Sales AllSelected takes into account the filter from the slicer, while ignoring the filter on year and month generated by the rows of the matrix.
This is the query executed by the the visual:
EVALUATE SUMMARIZECOLUMNS ( ROLLUPADDISSUBTOTAL ( 'Date'[Year], "IsGrandTotalRowTotal", ROLLUPGROUP ( 'Date'[Year Month Short], 'Date'[Year Month Number] ), "IsDM1Total" ), TREATAS ( { 2020 }, 'Date'[Year] ), "Sales_Amount", 'Sales'[Sales Amount], "Sales_Allselected", 'Sales'[Sales Allselected] )
Technically, what is happening is that the filter on Year is used as a filter argument in SUMMARIZECOLUMNS. Therefore, it is active against the group-by columns Year, Year Month Short, and Year Month Number. When ALLSELECTED is executed inside SUMMARIZECOLUMNS, it restores the original filter on all the group-by columns, instating again the filter on the entire 2020 year.
The same report produces the very same result when executed in the composite model. Moreover, the query is identical and it is executed wholesale. So far, everything looks rather simple. Things suddenly change if instead of using the Date[Year] column in the slicer, we use ‘Local Date'[Year]. Local Date is a table in the VertiPaq continent of the composite model. The remote model does not even know about its existence. Nonetheless, the report still works – although the query is now much more complex. There are now several queries to analyze: the local query and the DirectQuery queries.
The local query is very similar to the query in the previous example, with the only noticeable difference that the filter is now operating on the Local Date table instead of on the Date table:
EVALUATE SUMMARIZECOLUMNS ( ROLLUPADDISSUBTOTAL ( 'Date'[Year], "IsGrandTotalRowTotal", ROLLUPGROUP ( 'Date'[Year Month Short], 'Date'[Year Month Number] ), "IsDM1Total" ), TREATAS ( { 2020 }, 'Local Date'[Year] ), "Sales_Amount", 'Sales'[Sales Amount], "Sales_Allselected", 'Sales'[Sales Allselected] )
This local query needs to be transformed into a query that can be executed wholesale by the remote server. The remote server cannot accept a filter on Local Date, as it is not aware of the very existence of the Local Date table. There are multiple DirectQuery DAX queries executed, all with a structure similar to the following example:
DEFINE VAR _Var0 = SUMMARIZE ( VALUES ( 'Date' ), 'Date'[Year], 'Date'[Year Month Short], 'Date'[Year Month Number] ) VAR _Var1 = { DT"2020-1-1", DT"2020-1-2", -- -- In total, there are 366 rows: one per day of the year -- DT"2020-12-28", DT"2020-12-29", DT"2020-12-30", DT"2020-12-31" } EVALUATE GROUPCROSSAPPLY ( 'Date'[Year], 'Date'[Year Month Short], 'Date'[Year Month Number], _Var0, TREATAS ( _Var1, 'Sales'[Order Date] ), "__Agg0", [Sales Allselected], "__Agg1", [Sales Amount] )
As you see, the DirectQuery DAX query does not contain any reference to Local Date. Instead, the filter is moved to the Sales[Order Date] column through the _Var1 variable, which contains the visible values of the column used as a key between Local Date and Sales.
The careful reader might notice that the filter is not on the Date table, as it was previously. Indeed, even though the filter is on the Sales[Order Date] column, that does not cross-filter Date. Nonetheless, during the grouping the engine discovers which values of Date[Year], Date[Year Month Short], and Date[Year Month Number] are reachable by the filter on Sales[Order Date]. The presence of the Date columns in the set of group-by columns of GROUPCROSSAPPLY is the key to understanding how ALLSELECTED can work in this scenario.
Now, let us replace the values on the rows. In the previous reports, we were using Date[Year] and Date[Year Month Short] in the matrix rows, with the slicer filtering Local Date[Year]. We now replace the matrix rows with the columns from Local Date. The result is surprisingly wrong.
Now both Sales Amount and Sales Allselected return the very same numbers. In other words, Sales Allselected is reporting an incorrect result. Following the same path we executed previously, let us check the queries executed. The local query shows nothing special, apart from the group-by columns being in Local Date instead of Date:
EVALUATE SUMMARIZECOLUMNS ( ROLLUPADDISSUBTOTAL ( 'Local Date'[Year], "IsGrandTotalRowTotal", ROLLUPGROUP ( 'Local Date'[Year Month Short], 'Local Date'[Year Month Number] ), "IsDM1Total" ), TREATAS ( { 2020 }, 'Local Date'[Year] ), "Sales_Amount", 'Sales'[Sales Amount], "Sales_Allselected", 'Sales'[Sales Allselected] )
The relevant difference is that the query is no longer wholesale, it is retail. The local engine sends to the remote engine the set of values to create the relationship between the unknown Local Date table and Sales. The grouping no longer happens on columns in the remote model, but rather on columns created in variables with no lineage. Here is the code of the DirectQuery DAX query:
The relevant difference is that the query is no longer wholesale, it is retail. The local engine sends to the remote engine the set of values to create the relationship between the unknown Local Date table and Sales. The grouping no longer happens on columns in the remote model, but rather on columns created in variables with no lineage. Here is the code of the DirectQuery DAX query: DEFINE TABLE _T73 = UNION ( SELECTCOLUMNS ( _Var0, "Value", [Value1] ), SELECTCOLUMNS ( _Var1, "Value", [Value1] ) ) TABLE _T78 = UNION ( SELECTCOLUMNS ( _Var0, "Value", [Value2] ), SELECTCOLUMNS ( _Var1, "Value", [Value2] ) ) TABLE _T79 = UNION ( SELECTCOLUMNS ( _Var0, "Value", [Value3] ), SELECTCOLUMNS ( _Var1, "Value", [Value3] ) ) VAR _Var0 = { ( 6, 39, 39 ), ( 6, 40, 40 ), ( 6, 41, 41 ), ( 6, 42, 42 ), ( 6, 43, 43 ), ( 6, 44, 44 ), ( 6, 45, 45 ), ( 6, 46, 46 ), ( 6, 47, 47 ), ( 6, 48, 48 ), ( 6, 49, 49 ), ( 6, 50, 50 ) } VAR _Var1 = { ( 6, 39, 39, DT"2020-1-1" ), ( 6, 39, 39, DT"2020-1-2" ), -- -- In total, there are 366 rows: one per day of the year -- ( 6, 50, 50, DT"2020-12-30" ), ( 6, 50, 50, DT"2020-12-21" ) } EVALUATE GROUPCROSSAPPLYTABLE ( _T73[Value], _T78[Value], _T79[Value], TREATAS ( _Var0, _T73[Value], _T78[Value], _T79[Value] ), "L1", CALCULATETABLE ( GROUPCROSSAPPLY ( TREATAS ( DEPENDON ( _Var1, EARLIER ( _T73[Value], 1 ), EARLIER ( _T78[Value], 1 ), EARLIER ( _T79[Value], 1 ) ), 'Sales'[Order Date] ), "__Agg0", [Sales Allselected], "__Agg1", [Sales Amount] ), ALL ( _T73[Value] ), ALL ( _T78[Value] ), ALL ( _T79[Value] ) )
Apart from the set of variables needed to apply the filter produced by the relationship, GROUPCROSSAPPLY no longer groups by columns in the remote model. It is grouping by columns in the variables, which do not participate in the set of relationships of the remote model.
Despite DEPENDON being used to re-instate the relationship between the variables, ALLSELECTED in the remote server cannot restore the correct filter on the columns. The local engine retrieves the values of both Sales and Sales Allselected by individual date – performing the final aggregation later – but during the calculation, ALLSELECTED cannot remove the filter from any column in the model. Therefore, the result is incorrect.
Things are very different if we define Sales Allselected in the local model. Indeed, now we create a new local measure named Local Sales Allselected with the same code as the remote Sales Allselected measure:
Local Sales Allselected = CALCULATE ( [Sales Amount], ALLSELECTED () )
The Local Sales Allselected measure is aware of the entire model, knowing both the local tables and the remote tables. Indeed, the Local Sales Allselected measure behaves the correct way.
Inspecting local and remote queries reveals no differences in the storage engine or DirectQuery DAX queries. Indeed, there is a difference in the query plan, showing that the calculation of Local Sales Allselected happens in the formula engine once the values per date have been retrieved.
This is an introductory article, with the sole goal to show you the semantic difference between using ALLSELECTED in both a local and a remote model. On the remote model, the results might be surprising. The reason is that the remote model has no clue about the structure of the local model. The local DAX engine sends information about the relationships to the remote model, but in some specific scenarios these are not enough to provide the full picture to the remote engine.
If you plan to build data models that are expected to be extended through composite models, you should make sure to avoid using ALLSELECTED with no arguments in DAX expressions. Otherwise, you will need much more time to debug and double-check any of your composite model reports to make sure that they are actually reporting correct numbers.