Picture a developer who needs a margin figure in a report. She adds a new column to the Product table and types a ratio. Every row looks right. Then she drops the column into a matrix, and the Grand Total shows a number nobody can explain.
Now picture the opposite problem. A model has grown heavy and slow to refresh because every new idea became another column. Nobody remembers why half of them exist.
Both stories come from the same gap: not knowing what calculated columns and calculated tables really do, and when a measure is the better tool. This guide closes that gap. You will learn how each object works, why you sometimes cannot avoid them, how to create them in several tools, and how to review them with 20 worked examples.
Meet the Sample Data Model
Every example uses one model: Customer, Product and Date dimensions, plus two fact tables, Sales (one row per order) and Sales Order Details (one row per order line). Customer and Date filter Sales, which filters Sales Order Details. Product filters Sales Order Details only. The inactive Date relationship on Sales[ShipDate] is activated with USERELATIONSHIP.
One warning up front: header-level amounts such as Sales[Freight] and Sales[TaxAmount] are not filtered by Product, so never slice them by Product columns. The examples below do not do this. These base measures are used throughout:
Total Sales = SUM ( 'Sales Order Details'[SalesAmount] )
Total Cost = SUM ( 'Sales Order Details'[TotalCost] )
Total Profit = [Total Sales] - [Total Cost]
Profit Margin % = DIVIDE ( [Total Profit], [Total Sales] )
Total Quantity = SUM ( 'Sales Order Details'[Quantity] )
Order Count = DISTINCTCOUNT ( Sales[SalesOrderID] )
The Concepts, Explained Simply
What Is a Calculated Column?
A DAX calculated column adds a new column to a table that already exists. Think of adding a column to a spreadsheet and filling the formula down: each row gets its own answer. DAX evaluates the formula row by row, using row context, when data is processed or refreshed. The results are stored in the model like any other column.
What Is a Calculated Table?
A DAX calculated table is a DAX expression that returns a whole table. Think of printing a helper sheet from your workbook and keeping it. The expression runs when data is processed or refreshed, and the resulting table is stored in the model. After that it behaves like a normal table.
The Third Option: Measures
A measure is evaluated at query time, in filter context, and nothing is stored except the formula. It is a calculator that works on whatever the user has selected. (I will cover explicit measures in other articles, so I will stay brief here.)
Refresh Time vs Query Time
Calculated columns and tables pay their cost at refresh: they add refresh work and memory, but their values are ready when a report opens. Measures pay at query time: they add almost no storage, but the engine computes them whenever a visual asks. Neither is automatically faster. The trade-off is storage versus calculation at query time, and the outcome depends on your model.
Row Context vs Filter Context
Row context means "the current row". It exists inside a calculated column and inside iterators such as SUMX. Filter context means "the filters currently applied", from slicers, visuals and CALCULATE. A calculated column has no filter context from the report, because it is computed at refresh.
That raises a question: how can a column use a measure? The answer is context transition. When a measure, or CALCULATE, is evaluated inside row context, DAX turns the current row's values into filters. The measure then sees only that row's slice of the data. Examples 9 and 10 use this.
Calculated Objects vs Power Query and the Source
Power Query (M) transformations run during refresh, before data lands in the model. They cannot use model relationships or DAX measures. Columns created in the source system, such as a SQL view, are calculated once and shared with every consumer of that source. Calculated objects run after loading, so they can use relationships, other tables and measures. The price is that the logic lives inside the model.
Why Calculated Columns and Tables Are Necessary
A measure returns a value in a cell. It cannot become a category. That is the dividing line, and it explains most legitimate uses:
- Slicers, axes, headers, grouping and bands. A field like age group must exist as a column to appear on an axis or in a slicer.
- Relationships. Relationships connect columns, so a composite key or a cleaned key must be a real column.
- Sort by column. A text column such as a band name can be sorted by another column in the same table, which must also exist physically.
- Tables with no source. A date table, a disconnected slicer table, a parameter table, or a role-playing copy of a dimension can all be built with DAX.
- Helper or summary tables. Sometimes a pre-calculated table is worth its cost (see Example 18).
Support for calculated columns and tables in DirectQuery and composite models, and in particular SSAS compatibility levels, has restrictions that change over time. If you work in those modes, check the current documentation before you design around them.
Comparison: Calculated Column vs Calculated Table vs Measure
| Aspect | Calculated column | Calculated table | Measure |
|---|---|---|---|
| What it returns | One value per row of an existing table | A whole table (rows and columns) | A single value for the current filter context |
| When evaluated | At data refresh or processing | At data refresh or processing | At query time |
| Where stored | Values stored in the model, inside its table | Result stored in the model as a new table | Only the formula is stored, not the result |
| Context | Row context (filter context only via context transition) | No report filter context; row context inside iterators | Filter context from visuals, slicers and filters |
| Memory impact | Grows with row count and column cardinality | Grows with the rows and columns returned | Negligible storage; cost is computing at query time |
| Usable in slicers or axes | Yes | Yes (its columns) | No, not as a category (it appears as a value) |
| Usable in relationships | Yes, as a key column | Yes, its columns can take part | No |
| Responds to slicer selections | No, fixed at refresh (though you can slice by it) | No, static until refresh | Yes |
| Best use | Row-level attributes: groups, bands, keys, sort columns | Date tables, parameter tables, helper and role-playing tables | Aggregations, ratios, and anything that must react to selections |
When to Use Which: A Decision Guide
A simple rule of thumb, in order of preference:
- Source system or Power Query for row-level data shaping.
- A measure for aggregations and ratios.
- A calculated column or table only when the model itself must hold the result.
Need a value that reacts to slicers and visuals?
YES -> Use a measure.
NO -> Need it as a slicer, axis, band, key or sort column?
YES -> Can the source or Power Query create it cleanly?
YES -> Create it there.
NO -> Calculated column (needs relationships/measures,
or the source is not under your control).
NO -> Need a whole table (date, parameter, helper)?
YES -> Calculated table (or a source/M table).
NO -> Probably a measure.
There are exceptions. If you cannot touch the source, or the logic needs model relationships, a calculated column is a sensible choice. In SSAS Tabular, a calculated column may be the simplest option when no one owns the upstream ETL.
Why Ratios Belong in Measures
A ratio is a relationship between two totals, not a property of a row. If you store a ratio per row and then sum it, the result is meaningless. If you average it, every row counts equally regardless of its sales volume. A measure like [Profit Margin %] divides the totals at whatever level the visual shows, so it is correct in every cell, including the Grand Total. Example 12 proves this.
Different Ways to Create Calculated Columns and Tables
1. Power BI Desktop
Select a table in the Fields pane, then use the table tools in the ribbon: New column or New table. Type the DAX in the formula bar. Pros: it is fast, and you get immediate feedback. Cons: the logic lives inside the file, so it is harder to review, version and manage in bulk.
2. Visual Studio with Analysis Services Projects (SSAS Tabular)
In a Tabular model project, the model designer lets you enter a DAX formula for a new calculated column in a table, and create calculated tables from the table-related menus. Exact labels differ between versions, so check the current documentation. Pros: projects fit source control and deployment pipelines. Cons: the tooling is heavier, and you must deploy and process to see the result.
3. Tabular Editor
Tabular Editor is an external tool for editing tabular models. In general terms, you right-click a table, add a calculated column or calculated table, and edit the DAX in an expression editor. Pros: bulk edits, scripting and a clearer view of dependencies. Cons: it is a separate tool, and editions and licensing differ, so check the current details.
4. Scripting and Code-Based Definitions
Models can also be defined as text. TMSL describes SSAS Tabular models as JSON, and TMDL is a text-based format for semantic models. The DAX query view in Power BI Desktop can also help you write and test DAX. Availability and syntax change between releases, so confirm them in the current documentation before relying on any of these. Pros: repeatable, reviewable, and easy to diff. Cons: a steeper learning curve.
5. Power Query or the Source as the Alternative
In Power Query, use the Add Column options, or write M directly. In the source, create the column in a SQL view or ETL step. Pros: the logic runs before the model, can be shared by other consumers, and keeps DAX lean. Cons: you need access to the source, and neither option can use DAX measures or model relationships.
Performance, Refresh and Governance Notes
- Calculated objects add to refresh time and to memory. How much depends on your model.
- High-cardinality calculated columns (many distinct values) are costly because they compress poorly.
- Calculated objects can depend on each other. The engine works out the order, but long chains are hard to debug, and a circular dependency causes an error.
- Keep logic in one place. If the same rule appears in the source, in M and in DAX, one of them will drift.
- Document every calculated object: purpose, owner, and what it depends on.
20 Examples Based on the Sample Data Model
Each example adds a new object to the sample model. I name the table before every example.
Beginner: Calculated Columns (1-6)
Example 1: Customer Age Group
Scenario: Marketing wants to slice sales by age band. New calculated column in the Customer table.
Age Group =
VAR ReferenceDate = DATE ( 2026, 12, 31 )
VAR AgeInYears = INT ( YEARFRAC ( Customer[BirthDate], ReferenceDate, 1 ) )
RETURN
IF (
ISBLANK ( Customer[BirthDate] ),
"Unknown",
SWITCH (
TRUE (),
AgeInYears < 25, "Under 25",
AgeInYears < 35, "25-34",
AgeInYears < 50, "35-49",
AgeInYears < 65, "50-64",
"65+"
)
)
Explanation: SWITCH ( TRUE (), ... ) tests each condition in order and returns the first match. I used a fixed reference date because TODAY () in a stored column is evaluated only at refresh, so ages silently go stale between refreshes. A fixed date, or a date taken from your Date table, makes the result predictable. I avoided DATEDIFF with YEAR because it counts calendar-year boundaries, not full years. Use this pattern whenever bands must appear on a slicer or axis.
Example 2: Shipping Days
Scenario: Operations wants to see how long orders take to ship. New calculated column in the Sales table.
Shipping Days =
IF (
ISBLANK ( Sales[ShipDate] ),
BLANK (),
DATEDIFF ( Sales[OrderDate], Sales[ShipDate], DAY )
)
Explanation: DATEDIFF returns the number of day boundaries between the two dates. The ISBLANK check keeps unshipped orders from producing a misleading number. Because it is a column, you can group orders by shipping days or use it in a filter. If you only need the average, a measure over this column is enough.
Example 3: Is Delayed Shipment
Scenario: A manager wants a quick True/False filter for late deliveries. New calculated column in the Sales table, built on Example 2.
Is Delayed Shipment =
IF (
ISBLANK ( Sales[Shipping Days] ),
BLANK (),
Sales[Shipping Days] > 7
)
Explanation: The column returns TRUE, FALSE or blank, and the seven-day threshold is only an example. Flags like this work well as slicers and filters. Note that this column depends on Sales[Shipping Days]. Keep the threshold in a documented place if it may change.
Example 4: Margin Per Unit
Scenario: The pricing team wants the list-price profit per unit for each product. New calculated column in the Product table.
Margin Per Unit = Product[ListPrice] - Product[UnitCost]
Explanation: This is a row-level subtraction on one table, which is a legitimate use of a column. It describes the product itself, not any sales activity. Summing it across products would not mean much, so treat it as an attribute rather than something to total. Ideally this would be computed upstream if the source offers it.
Example 5: Price Band
Scenario: Merchandisers want to compare products grouped by price range. New calculated column in the Product table.
Price Band =
SWITCH (
TRUE (),
Product[ListPrice] < 25, "Budget",
Product[ListPrice] < 100, "Standard",
Product[ListPrice] < 500, "Premium",
"Luxury"
)
Explanation: Products are bucketed by list price, and the band edges are illustrative. The column has only four distinct values, so it is cheap and compresses well. It can go straight onto a slicer or an axis. To keep the bands in a sensible order, add a numeric sort column and use Sort by column.
Example 6: Customer Label
Scenario: A report needs a readable label to tell apart customers who share a name. New calculated column in the Customer table.
Customer Label = Customer[CustomerName] & " (" & Customer[City] & ")"
Explanation: The & operator joins text, so "Maria Lopez (Madrid)" becomes one string. A blank city would leave empty parentheses, so handle that if your data has gaps. This column has high cardinality because it is close to unique per customer. Use it for display, and hide it if it is only a helper.
Intermediate: Relationships and Context Transition (7-11)
Example 7: Category on Order Lines
Scenario: An analyst wants the product category available directly on each order line. New calculated column in the 'Sales Order Details' table.
Category = RELATED ( Product[Category] )
Explanation: RELATED follows a relationship from the many side to the one side. Each order line has exactly one product, so DAX knows which Product row to read. It would not work the other way around, because one product has many lines. Use it sparingly: you can already slice by Product[Category], so this copy mostly costs memory.
Example 8: Gross Amount
Scenario: Finance wants to see the value of each line before discount. New calculated column in the 'Sales Order Details' table.
Gross Amount =
'Sales Order Details'[Quantity] * 'Sales Order Details'[UnitPrice]
Explanation: The stored SalesAmount applies DiscountPct, so Gross Amount minus SalesAmount equals the discount given on that line. The column is easy to understand, but the fact table is the largest in the model. A SUMX measure over Quantity * UnitPrice can give the same total without storing a value per row. Choose the column only if you need the value for slicing or row-level filtering.
Example 9: Order Sales
Scenario: Sales managers want the total value of each order on the order header. New calculated column in the Sales table.
Order Sales = CALCULATE ( [Total Sales] )
Explanation: Inside a calculated column, CALCULATE triggers context transition: the current Sales row becomes a filter. That filter flows from Sales to Sales Order Details through the relationship, so [Total Sales] sums only that order's lines. This works because SalesOrderID is unique per Sales row. The value is fixed at refresh and does not change with slicers.
Example 10: Customer Lifetime Sales
Scenario: CRM wants each customer's all-time sales stored on the customer row. New calculated column in the Customer table.
Lifetime Sales = CALCULATE ( [Total Sales] )
Explanation: Context transition turns the customer row into a filter, which flows Customer to Sales to Sales Order Details. The result is every sale for that customer. It is fixed at refresh and does not respond to a Date slicer, so it is a snapshot, not a live figure. If you need sales for the selected period, use [Total Sales] directly.
Example 11: Customer Tier
Scenario: The loyalty team wants to group customers into tiers. New calculated column in the Customer table, based on Example 10.
Customer Tier =
IF (
ISBLANK ( Customer[Lifetime Sales] ),
"No Sales",
SWITCH (
TRUE (),
Customer[Lifetime Sales] >= 50000, "Platinum",
Customer[Lifetime Sales] >= 20000, "Gold",
Customer[Lifetime Sales] >= 5000, "Silver",
"Bronze"
)
)
Explanation: The thresholds are illustrative. This column depends on Customer[Lifetime Sales], which depends on [Total Sales], which depends on Sales Order Details columns. That is a dependency chain. It is fine here, but if you create a loop (for example, making Lifetime Sales depend on Customer Tier), the model will report a circular dependency. Hide Lifetime Sales if users only need the tier.
The Trap (12)
Example 12: Margin % as a Column vs the Measure
Scenario: A developer stores a margin ratio per product, then averages it in a visual. New calculated column in the Product table (plus a demonstration measure that exists only for this example).
Margin % = DIVIDE ( Product[ListPrice] - Product[UnitCost], Product[ListPrice] )
Avg of Margin % Column = AVERAGE ( Product[Margin %] )
Explanation: Take a hypothetical example with no discounts. Product A has a 20% margin and sold 1,000 units for 100,000 in sales and 80,000 in cost. Product B has a 50% margin and sold 10 units for 10,000 in sales and 5,000 in cost. AVERAGE of the column gives 35%, and SUM gives 70%, which is not a margin at all. The explicit [Profit Margin %] measure divides total profit (25,000) by total sales (110,000) and returns about 22.7%, which reflects what actually sold. Always use the measure for ratios.
Calculated Tables (13-18)
Example 13: Date Table
Scenario: A model has no date dimension, and time intelligence needs one. New calculated table named 'Date'. (The sample model already has one. This shows how you would build it if it did not.)
Date =
ADDCOLUMNS (
CALENDAR ( DATE ( 2020, 1, 1 ), DATE ( 2026, 12, 31 ) ),
"Year", YEAR ( [Date] ),
"MonthNumber", MONTH ( [Date] ),
"MonthName", FORMAT ( [Date], "mmmm" ),
"MonthYearSort", YEAR ( [Date] ) * 100 + MONTH ( [Date] ),
"FiscalYear", YEAR ( EDATE ( [Date], 6 ) )
)
Explanation: CALENDAR returns one row per day between two dates, and ADDCOLUMNS adds attributes. The fiscal year here assumes a June 30 year-end, matching the Sales Fiscal YTD measure. After creating it, mark it as a date table on [Date] and set sort-by-column properties. This is a short version, and my Date table article covers the full one.
Example 14: Role-Playing Ship Date Table
Scenario: Analysts want to slice by order month and ship month side by side. New calculated table named 'Ship Date'.
'Ship Date' = 'Date'
Explanation: This creates a copy of the Date table. It needs its own new relationship, 'Ship Date'[Date] to Sales[ShipDate], which is an addition to the sample model. Properties such as sort-by-column and date-table marking may need to be set again on the copy, so check it. Choose this over USERELATIONSHIP when both dates must appear in the same visual or slicers. Choose USERELATIONSHIP when you want a smaller model and need one date at a time.
Example 15: Metric Selector Table
Scenario: Users want a slicer to switch one chart between Sales, Profit and Quantity. New calculated table named 'Metric Selector', plus one new measure.
'Metric Selector' =
DATATABLE (
"Metric", STRING,
"MetricOrder", INTEGER,
{
{ "Sales", 1 },
{ "Profit", 2 },
{ "Quantity", 3 }
}
)
Selected Metric Value =
SWITCH (
SELECTEDVALUE ( 'Metric Selector'[Metric], "Sales" ),
"Sales", [Total Sales],
"Profit", [Total Profit],
"Quantity", [Total Quantity],
BLANK ()
)
Explanation: The table is disconnected: it has no relationships, so selecting a value filters nothing in the model. The measure reads the selection with SELECTEDVALUE and returns the matching base measure. Sort Metric by MetricOrder so the slicer keeps its order. One number format applies to the measure, so mixing currency and quantity can look odd. Power BI also offers field parameters for this purpose, so check the current documentation.
Example 16: Discount What-If Table
Scenario: Sales planners want to test "what if every line had this discount?". New calculated table named 'Discount Scenario', plus one new measure.
'Discount Scenario' =
SELECTCOLUMNS (
GENERATESERIES ( 0, 30, 5 ),
"Discount %", DIVIDE ( [Value], 100 )
)
Sales at Scenario Discount =
VAR SelectedDiscount = SELECTEDVALUE ( 'Discount Scenario'[Discount %], 0 )
RETURN
SUMX (
'Sales Order Details',
'Sales Order Details'[Quantity]
* 'Sales Order Details'[UnitPrice]
* ( 1 - SelectedDiscount )
)
Explanation: GENERATESERIES returns a column called Value with 0, 5, 10 and so on up to 30. I generate whole numbers and divide by 100 to avoid decimal drift. The measure replaces each line's discount with the selected one, and returns the undiscounted total if nothing is selected. Like Example 15, the table is disconnected, and it is a good fit for scenario slicers.
Example 17: Top 10 Customers Table
Scenario: An executive wants a fixed list of the ten biggest customers. New calculated table named 'Top 10 Customers'.
'Top 10 Customers' =
SELECTCOLUMNS (
TOPN ( 10, Customer, [Total Sales], DESC ),
"CustomerKey", Customer[CustomerKey],
"CustomerName", Customer[CustomerName],
"Lifetime Sales", [Total Sales]
)
Explanation: TOPN ranks customers by [Total Sales] and keeps the top ten, and ties can return more than ten rows. The ranking is computed at refresh with no report filters, so it is static until the next refresh. It will not change when a user picks a year. If you need a ranking that responds to slicers, build it with a measure instead.
Example 18: Sales Summary Table
Scenario: A heavy report repeatedly shows sales by category and year. New calculated table named 'Sales Summary'.
'Sales Summary' =
SUMMARIZECOLUMNS (
Product[Category],
'Date'[Year],
"Sales", [Total Sales],
"Profit", [Total Profit]
)
Explanation: SUMMARIZECOLUMNS groups by Category and Year and calculates both measures for each pair. It leaves out combinations where every measure is blank. A summary table is worth it only when it removes a proven bottleneck or feeds a specific need, because it adds refresh work and memory and is static between refreshes. Test the report without it first.
Advanced: Decisions and Governance (19-20)
Example 19: Order Size Band Three Ways
Scenario: The business wants to classify orders as Small, Medium or Large. The thresholds are illustrative.
Option A: calculated column in the Sales table:
Order Size Band =
VAR OrderTotal = CALCULATE ( [Total Sales] )
RETURN
SWITCH (
TRUE (),
ISBLANK ( OrderTotal ), "No Lines",
OrderTotal < 100, "Small",
OrderTotal < 500, "Medium",
"Large"
)
Option B: Power Query (M), which assumes queries named Sales and Sales Order Details:
let
OrderTotals =
Table.Group (
#"Sales Order Details",
{ "SalesOrderID" },
{ { "OrderTotal", each List.Sum ( [SalesAmount] ), type number } }
),
Merged =
Table.NestedJoin ( Sales, { "SalesOrderID" }, OrderTotals, { "SalesOrderID" }, "Totals", JoinKind.LeftOuter ),
Expanded =
Table.ExpandTableColumn ( Merged, "Totals", { "OrderTotal" } ),
Banded =
Table.AddColumn (
Expanded,
"Order Size Band",
each
if [OrderTotal] = null then "No Lines"
else if [OrderTotal] < 100 then "Small"
else if [OrderTotal] < 500 then "Medium"
else "Large",
type text
),
Result = Table.RemoveColumns ( Banded, { "OrderTotal" } )
in
Result
Option C: measure, counting orders in one band:
Small Orders =
CALCULATE (
[Order Count],
FILTER ( Sales, [Total Sales] < 100 )
)
Explanation: Choose the measure when you only need counts or totals by band, because it reacts to slicers and stores nothing. Choose a stored column when the band must be a slicer or axis. When you have a choice, build that column in Power Query or the source, since the logic runs before the model. Use the calculated column when the rule needs model relationships or measures, or when you cannot change the pipeline.
Example 20: Model Clean-Up Checklist
Scenario: You inherit a model with dozens of calculated columns and tables. Review each one with this list.
For every calculated column or table, ask:
[ ] Could this be a measure?
[ ] Could this move to the source or Power Query?
[ ] Is it high-cardinality (many distinct values)?
[ ] Is it used in any visual, relationship, sort or measure?
[ ] Is it a ratio or average stored per row?
[ ] Does it use TODAY () or NOW () and assume live updates?
[ ] Does it depend on a long chain of other calculated objects?
[ ] Should it be hidden from report authors?
[ ] Is it documented (purpose, owner, dependencies)?
[ ] Is the same logic duplicated elsewhere?
Explanation: Work through the list column by column. Any "yes" to the first two questions is a candidate for removal or relocation. Check usage before deleting, because reports and other tools may depend on a column. Run this review regularly, and tools such as Tabular Editor can help with dependencies.
Common Mistakes and Best Practices
- Building ratios as columns. Summing or averaging a stored ratio gives wrong totals. Use a measure.
- Creating a column when a measure would do. If the result should react to slicers, it belongs in a measure.
- Ignoring high cardinality. Columns with almost unique values compress poorly and add memory.
- Using TODAY () in a stored column. The value is fixed at refresh, not live.
- Creating circular dependencies. Two objects that depend on each other cause an error. Keep dependency chains short.
- Building calculated tables that could be source tables. If a table can come from the source or Power Query, load it from there.
- Forgetting to hide helper columns. Hide anything report authors should not use.
- Not documenting calculated objects. Record the purpose, owner and dependencies for each one.
Frequently Asked Questions
Are calculated columns slower than measures?
Not necessarily. A calculated column does its work at refresh and stores the result, while a measure does its work at query time. Which is better depends on your model, so test rather than assume.
Do calculated columns update when I change a slicer?
No. Their values are fixed at refresh. You can slice by a calculated column, but its stored values do not change with your selection. Use a measure for anything that must react.
Can a calculated table have relationships?
Yes. Its columns can take part in relationships like those of any other table. Examples 14 and 17 show cases where you might add one. The table is still static until refresh.
Should I use Power Query instead?
Often, yes, for row-level shaping. Power Query and the source run before the model and keep DAX lean. Use a calculated column or table when the logic needs model relationships, measures, or DAX-only constructs.
Conclusion: Key Takeaways
- A calculated column is stored row by row; a calculated table is a stored table; a measure is calculated at query time.
- Use calculated objects for slicers, axes, bands, keys, sort columns and tables with no source.
- Prefer, in order: the source or Power Query, then a measure, then a calculated column or table.
- Never store ratios per row and expect correct totals.
- Cost and speed depend on your model, so review calculated objects regularly.
Related reading: explicit measures, the Date table, CALCULATE, and row context vs filter context. Find them on exceltobi.com.
Ready to build cleaner Power BI and SSAS Tabular models? Explore more guides on exceltobi.com and put these patterns to work in your own model.