Forum Discussion
Context Transition
- 9 years ago
There are 2 contexts in Dax. A ROW context and a FILTER context. Only the FILTER context is applied to the data model to change the foundset of rows against which the the aggregation measure is finally computed.
In your example #1, you had as a calculated COLUMN (not a measure) , Column = SUM ( Orders[Unit Price] ). Since this is a calculated column, the value is computed for each row one at the time. For row #1, there is a row context which is the values of every column in the table for row #1. But the filter context is empty. Since there is a row context but no filter context, DAX sum's up all the values for 'Unit Price' is the Orders table. (Because, again, only Filter context impacts the model foundset, row context's do not). For Row #2, Dax engine again sees a row context which is current values for every column in row #2. But Filter context is again empty, so again DAX sum's up all the values for the 'Unit Price' in the Orders table. In the end, for this new calculated column every row in the table will have the same value.
In your example #2, you wrapped the calculated column in a CALCULATE statement. One of the main features of CALCULATE is to trigger what is called a "context transition". It removes the the values from the row context and puts them into the filter context. So with Column = CALCULATE ( SUM ( Orders[Unit Price] ) ), all the values of the columns of the current row are transitioned into the Filter context. The Filter context is then applied to the model, and the SUM of Orders[Unit Price] is then computed. Note though this may or may not be the value of 'Unit Price' for the current row. It is more precise to say the foundset is all the rows in 'Orders' for which the values of column 1, column 2, ...column N match the respective values of the current row being computed. ok?
#3 Filters automatically flow from the one side to the many side of a relationship. if you put a calculated column in Product table = SUM ( Sales[Line Margin] ), the result would be to sum all the 'Line Margin' values in the entire Sales table because the Filter context is still empty. Do this: CALCULATE ( SUM ( Sales[Line Margin] ) ), and again row is transitioned to filter context, applied to model, and values summed in Sales table is the now filtered foundset. You don't need a RELATEDTABLE (which is is just a synonym for CALCULATETABLE anyway) because of automatic filter propagation from one -> many. There is no automatic from many -> one unless you turn it "on" in the model (make relationships bi-directional - which imo should only be done after careful consideration).
#4 is explained in #2 above. You need to understand how context transition works. Row contexts are created in two ways. For calculated columns, row context exists for each row the value is calculated. In Measures, you can programatically create a row context using an iterator function like SUMX and the the other 'X' functions and others such as ADDCOLUMNS. So for SUMX per your example, multiplication does happen first, then the summation of the values. What Dax does first is evaluate which rows you are iterating over. Which rows are aggregated is at the heart of what makes Dax work - manipulating the filter context under which the aggregation function is computed.
Hope this makes sense...
There are 2 contexts in Dax. A ROW context and a FILTER context. Only the FILTER context is applied to the data model to change the foundset of rows against which the the aggregation measure is finally computed.
In your example #1, you had as a calculated COLUMN (not a measure) , Column = SUM ( Orders[Unit Price] ). Since this is a calculated column, the value is computed for each row one at the time. For row #1, there is a row context which is the values of every column in the table for row #1. But the filter context is empty. Since there is a row context but no filter context, DAX sum's up all the values for 'Unit Price' is the Orders table. (Because, again, only Filter context impacts the model foundset, row context's do not). For Row #2, Dax engine again sees a row context which is current values for every column in row #2. But Filter context is again empty, so again DAX sum's up all the values for the 'Unit Price' in the Orders table. In the end, for this new calculated column every row in the table will have the same value.
In your example #2, you wrapped the calculated column in a CALCULATE statement. One of the main features of CALCULATE is to trigger what is called a "context transition". It removes the the values from the row context and puts them into the filter context. So with Column = CALCULATE ( SUM ( Orders[Unit Price] ) ), all the values of the columns of the current row are transitioned into the Filter context. The Filter context is then applied to the model, and the SUM of Orders[Unit Price] is then computed. Note though this may or may not be the value of 'Unit Price' for the current row. It is more precise to say the foundset is all the rows in 'Orders' for which the values of column 1, column 2, ...column N match the respective values of the current row being computed. ok?
#3 Filters automatically flow from the one side to the many side of a relationship. if you put a calculated column in Product table = SUM ( Sales[Line Margin] ), the result would be to sum all the 'Line Margin' values in the entire Sales table because the Filter context is still empty. Do this: CALCULATE ( SUM ( Sales[Line Margin] ) ), and again row is transitioned to filter context, applied to model, and values summed in Sales table is the now filtered foundset. You don't need a RELATEDTABLE (which is is just a synonym for CALCULATETABLE anyway) because of automatic filter propagation from one -> many. There is no automatic from many -> one unless you turn it "on" in the model (make relationships bi-directional - which imo should only be done after careful consideration).
#4 is explained in #2 above. You need to understand how context transition works. Row contexts are created in two ways. For calculated columns, row context exists for each row the value is calculated. In Measures, you can programatically create a row context using an iterator function like SUMX and the the other 'X' functions and others such as ADDCOLUMNS. So for SUMX per your example, multiplication does happen first, then the summation of the values. What Dax does first is evaluate which rows you are iterating over. Which rows are aggregated is at the heart of what makes Dax work - manipulating the filter context under which the aggregation function is computed.
Hope this makes sense...
- brs09j9 years agoHelper II
"#3 Filters automatically flow from the one side to the many side of a relationship. if you put a calculated column in Product table = SUM ( Sales[Line Margin] ), the result would be to sum all the 'Line Margin' values in the entire Sales table because the Filter context is still empty. Do this: CALCULATE ( SUM ( Sales[Line Margin] ) ), and again row is transitioned to filter context, applied to model, and values summed in Sales table is the now filtered foundset. You don't need a RELATEDTABLE (which is is just a synonym for CALCULATETABLE anyway) because of automatic filter propagation from one -> many. There is no automatic from many -> one unless you turn it "on" in the model (make relationships bi-directional - which imo should only be done after careful consideration). "
Filters automatically flow from the one side to the many side of a relationship. Is this always or is it just when I have a calculated column or use CALCULATE?
- mattbrice9 years agoSolution Sage
It is the filter context that automatically flows from one -> many. CALCULATE's role is to add/change/romove items from the filter context either by context transition or via its filter arguments. So to be doubly clear, CALCULATE in and of itself doesn't cause filters to flow from one -> many; that is built into the Dax engine. CALCULATE just modifies what filters are in filter context.
I copied the below from a great although complex article by Jeffrey Wang @ Microsoft.
Logic-behind-magic-of-dax-cross-table
Calculate function performs the following operations:
- Create a new filter context by cloning the existing one.
- Move current rows in the row context to the new filter context one by one and apply blocking semantics against all previous tables.
- Evaluate each setfilter argument in the old filter context and then add setfilter tables to the new filter context one by one and apply blocking semantics against all tables that exist in the new filter context before the first setfilter table is added.
- Evaluate the first argument in the newly constructed filter context.
Make sense?