Tableau Calculated Fields and LOD Expressions: A Practical Guide
LOD expressions are the feature that separates Tableau dashboards that answer questions from Tableau dashboards that generate more questions. Here's how to actually use them.
Multivak Labs
Engineering Team
Every organisation has that one Tableau dashboard. The one that started as a simple bar chart, got passed between three analysts, acquired a dozen filters nobody fully understands, and now takes forty-five seconds to load. Someone asks "can we see this by region AND by customer?" and the room goes quiet because the last person who tried that broke the numbers for a week.
The root cause, more often than not, is a misunderstanding of how Tableau calculates things at different levels of granularity. Calculated fields and Level of Detail (LOD) expressions are the two tools that solve this problem — but only if you know when to reach for which one, and how they interact with filters, aggregations, and each other.
This guide covers both from the ground up. We'll start with calculated fields (the foundation), move into LOD expressions (the power tool), and then get into the territory most guides skip: nesting, performance, order of operations, and the subtle ways LOD expressions can silently give you wrong numbers if you're not careful.
Calculated Fields: The Foundation
A calculated field in Tableau is any new field you create using a formula. It can be as simple as combining two existing fields or as complex as a multi-condition logical expression. Calculated fields operate at the row level of your data source — they evaluate once for every row before any aggregation happens.
Think of them as adding a new column to your spreadsheet. The data hasn't changed shape; you've just derived a new value from what was already there.
Basic Calculated Field Examples
The most common calculated fields fall into a few categories:
Arithmetic:
// Profit Ratio [Profit] / [Sales]
String manipulation:
// Full customer name [First Name] + " " + [Last Name]
Date calculations:
// Days since last order
DATEDIFF('day', [Last Order Date], TODAY())
Conditional logic:
// Customer segment based on total spend IF [Total Sales] > 10000 THEN "Enterprise" ELSEIF [Total Sales] > 1000 THEN "Mid-Market" ELSE "SMB" END
These are straightforward. They execute at the row level, and Tableau aggregates the results based on whatever dimensions you drag into your view. No surprises here — which is exactly why most Tableau users stop at calculated fields and never venture further.
Aggregated Calculated Fields
You can also write calculated fields that include aggregation functions:
// Percent of total profit SUM([Profit]) / SUM([Sales])
This is where things start to get interesting — and where things start to break. The aggregation in this formula depends entirely on what dimensions are in your view. Drop [Region] on rows and you get the profit percentage per region. Add [Category] and the numbers shift because the SUM is now computed at the Region + Category level.
This is the view-dependent behaviour that calculated fields are built on. For most use cases, it's exactly what you want. But the moment you need a calculation that doesn't change with the view — say, the overall average order value that stays constant no matter how you slice the dashboard — calculated fields can't help you. That's where LOD expressions enter.
What Are LOD Expressions?
Level of Detail expressions let you explicitly control the granularity at which a calculation is performed, independent of what dimensions are in your visualisation. They're Tableau's answer to a very specific problem: "I need this number computed at this level of detail, regardless of what the view is showing."
The syntax is consistent across all three types:
{ [FIXED | INCLUDE | EXCLUDE] <dimension> : <aggregate expression> }
The curly braces are the giveaway. If you see curly braces in a Tableau formula, you're looking at an LOD expression. If you don't, you're not. That's the entire visual distinction, and it's the kind of thing you learn to spot at a glance after your third week of debugging someone else's workbook.
FIXED Expressions: The Most Common LOD
FIXED is the workhorse of LOD expressions. It computes a value at the exact level of detail you specify, completely ignoring any dimensions in the view.
// Total sales per region — stays the same regardless of view dimensions
{ FIXED [Region] : SUM([Sales]) }
Put this on a worksheet that shows data by Sub-Category, by Quarter, by Customer Segment — doesn't matter. The value will always be the total sales for each region, because that's what you told it to compute.
Practical FIXED Examples
Customer's first purchase date:
{ FIXED [Customer ID] : MIN([Order Date]) }
This gives you each customer's first order date regardless of what time period is shown in the view. It's the foundation for cohort analysis in Tableau — you can group customers by when they first bought, then track their behaviour over time.
Number of orders per customer:
{ FIXED [Customer ID] : COUNTD([Order ID]) }
Useful for segmenting customers by purchase frequency. Drag this onto colour and you instantly see which parts of your data are driven by repeat buyers versus one-time purchasers.
Average deal size by sales rep:
{ FIXED [Sales Rep] : AVG([Deal Value]) }
Your sales manager will love this one. It answers "what's the typical deal size for each rep?" without being distorted by whatever filters or dimensions the dashboard user happens to have active.
Multiple dimensions in FIXED:
// Total sales per region per year
{ FIXED [Region], YEAR([Order Date]) : SUM([Sales]) }
You can include multiple dimensions in a FIXED expression. The calculation locks to whatever combination of dimensions you specify. This particular one is useful for year-over-year comparison calculations.
INCLUDE Expressions: Adding Granularity
INCLUDE expressions add dimensions to the view's level of detail. They compute at a finer grain than what the view shows, then Tableau reaggregates the result to the view level.
// Average sales per order, then aggregated to whatever the view shows
{ INCLUDE [Order ID] : SUM([Sales]) }
If your view shows data by [Category], this expression first calculates the total sales for each order within each category, then aggregates those order-level totals to the category level. The default reaggregation is AVG, but you can wrap it in whatever aggregation you want.
When INCLUDE Shines
INCLUDE is your tool when the view is at a coarser grain than the calculation you need. Classic example: you want the average order value, but your view only shows categories.
// Average order value per category (view shows Category)
AVG({ INCLUDE [Order ID] : SUM([Sales]) })
Without the INCLUDE expression, you'd get the average of row-level sales amounts — not the average of order totals. The difference can be dramatic, and getting it wrong is the kind of mistake that makes finance question your entire dashboard. (We've seen it happen. The meeting was not fun.)
Average transaction size per customer segment:
AVG({ INCLUDE [Transaction ID] : SUM([Amount]) })
Median number of items per order:
MEDIAN({ INCLUDE [Order ID] : COUNTD([Product ID]) })
EXCLUDE Expressions: Removing Granularity
EXCLUDE expressions do the opposite of INCLUDE — they remove dimensions from the view's level of detail. The calculation runs at a coarser grain than what the view shows.
// Total sales excluding the sub-category dimension
{ EXCLUDE [Sub-Category] : SUM([Sales]) }
If your view shows [Category] and [Sub-Category], this expression computes the total at the Category level only, giving you the category total on every sub-category row. This is the foundation for percent-of-parent calculations.
Percent-of-Total with EXCLUDE
The most common EXCLUDE use case, and one that comes up in nearly every analytical dashboard:
// What percentage of the category total does each sub-category represent?
SUM([Sales]) / { EXCLUDE [Sub-Category] : SUM([Sales]) }
This divides each sub-category's sales by its parent category's total. No table calculations needed, no secondary data source, no "compute using" configuration that breaks the moment someone adds a filter. It just works.
Deviation from average:
// How far each sub-category deviates from its category average
SUM([Sales]) - { EXCLUDE [Sub-Category] : AVG([Sales]) }
Contribution to regional total:
// Each state's contribution to its region
SUM([Sales]) / { EXCLUDE [State] : SUM([Sales]) }
Table-Scoped LOD Expressions
There's a fourth flavour of LOD expression that most guides skip entirely: the table-scoped expression. It has no keyword — no FIXED, INCLUDE, or EXCLUDE — just curly braces and an aggregation.
// The earliest order date across the entire dataset
{ MIN([Order Date]) }
// The grand total of all sales
{ SUM([Sales]) }
// Total number of distinct customers
{ COUNTD([Customer ID]) }
Table-scoped expressions compute across the entire data source (after data source filters), ignoring every dimension in the view. They're useful for benchmarking — "how does this value compare to the dataset-wide number?"
// Each region's sales as a percentage of the grand total
SUM([Sales]) / { SUM([Sales]) }
Simple, readable, and a lot less fragile than trying to achieve the same thing with table calculations and "compute using" settings that require a PhD in Tableau's addressing/partitioning model to configure correctly.
Choosing Between Calculated Fields, Table Calculations, and LOD Expressions
This is the question that every Tableau user eventually asks, usually after they've built something with table calculations that kind of works but breaks when anyone touches it. Here's the decision framework:
- Calculated fields — use when the computation is row-level or when view-dependent aggregation is exactly what you want. Profit ratios, string formatting, date math, conditional bucketing. They're the default, and you should reach for them first.
- Table calculations — use when you need to compute across the results that Tableau has already returned. Running totals, ranking, moving averages, percent difference from previous. They operate on the visual table, not the data source.
- LOD expressions — use when you need to specify the exact granularity of the calculation, independent of the view. Cohort analysis, fixed benchmarks, percent-of-parent, customer-level metrics in a category-level view.
The real sign of LOD mastery isn't knowing the syntax — it's recognising the moment when a calculated field or table calculation won't give you the right answer, and knowing which LOD type will.
Nesting LOD Expressions
Tableau supports nesting LOD expressions inside other LOD expressions. This is where things get genuinely powerful — and genuinely confusing if you're not methodical about it.
The most common nesting pattern: compute a value at one granularity, then reaggregate it at another.
// Average of monthly sales totals (excluding the month dimension from the view)
{ EXCLUDE [Order Date (Month / Year)] :
AVG({ FIXED [Order Date (Month / Year)] : SUM([Sales]) })
}
The inner expression computes total sales for each month. The outer expression takes the average of those monthly totals, excluding the month dimension from the computation. The result is a stable "average monthly sales" figure that doesn't shift when you add or remove months from the view.
Another Nesting Pattern: Cohort Metrics
// Average lifetime value by acquisition cohort
AVG({ FIXED [Customer ID] : SUM([Sales]) })
// Where cohort is defined by:
{ FIXED [Customer ID] : MIN([Order Date]) }
First, you FIXED the first purchase date per customer (the cohort assignment). Then, in a separate calculation, you FIXED the total sales per customer (their lifetime value). Drop the cohort date on columns and the AVG of lifetime value on rows, and you have a cohort analysis that took three lines of code to build.
Fair warning: nested LOD expressions generate nested subqueries in the underlying SQL. On a live connection to a large database, two levels of nesting is usually fine. Three or more and you should be watching your query times carefully. (More on performance shortly.)
The Order of Operations: Why Your Filters Aren't Working
This is the section that would have saved us collectively hundreds of hours if someone had explained it clearly the first time. Tableau has a strict order of operations for how it processes queries, and LOD expressions sit at different points in that pipeline depending on their type.
The simplified order:
- Data source filters — applied first, at the data connection level.
- Context filters — create a temporary, filtered dataset for all subsequent operations.
- FIXED LOD expressions — computed on the context-filtered dataset.
- Dimension filters — applied to the view.
- INCLUDE / EXCLUDE LOD expressions — computed after dimension filters.
- Measure filters — applied to aggregated results.
- Table calculations — computed on the final visual table.
The critical insight: FIXED expressions are evaluated before dimension filters. This means if you have a dimension filter on [Category] = "Technology", your FIXED expression will still compute across ALL categories unless you promote that filter to a context filter.
This is the single most common source of "my LOD numbers are wrong" complaints. The fix is straightforward:
// This FIXED expression ignores your [Region] dimension filter
{ FIXED [Customer ID] : SUM([Sales]) }
// To make the filter apply, right-click the [Region] filter
// and select "Add to Context"
// Now the FIXED expression only sees data for the filtered region
INCLUDE and EXCLUDE expressions, by contrast, are evaluated after dimension filters, so they respect your filters by default. This is one of the reasons some analysts prefer INCLUDE over FIXED when possible — fewer surprises with filtering.
Performance Considerations
LOD expressions are translated into SQL subqueries or joins by Tableau's query engine. This is fine for small to medium datasets, but on large tables with live connections, poorly written LOD expressions can turn a two-second dashboard into a forty-five-second ordeal. (That dashboard we mentioned in the opening? This was part of the problem.)
Performance Tips
- Minimise the number of LOD expressions per worksheet. Each one potentially adds a subquery. Three or four is usually fine; ten is asking for trouble.
- Use context filters with FIXED expressions. Without them, FIXED computes across the entire dataset. A context filter narrows the data before the FIXED expression runs, reducing the subquery's scope.
- Prefer extracts over live connections for LOD-heavy workbooks. Tableau's extract engine (Hyper) handles LOD expressions much more efficiently than most SQL databases because it can optimise the subquery execution internally.
- Avoid unnecessary nesting. Each nesting level adds a subquery. If you can achieve the same result with a single LOD expression or a combination of LOD + table calculation, do that instead.
- Index your LOD dimensions in the database. If you're using a live connection and your FIXED expression references [Customer ID], make sure Customer ID is indexed. The subquery will join on that field, and without an index, you're looking at a full table scan.
- Use the Performance Recorder. Tableau has a built-in Performance Recorder (Help > Settings and Performance > Start Performance Recording) that shows you the exact SQL generated and how long each query takes. Use it.
Common Pitfalls and How to Avoid Them
After building and debugging more Tableau workbooks than we'd like to admit, here are the mistakes we see most often:
1. Wrapping LOD Results in Unnecessary Aggregations
// This is redundant — the FIXED expression already returns an aggregate
SUM({ FIXED [Region] : SUM([Sales]) })
// This is what you probably want
{ FIXED [Region] : SUM([Sales]) }
// But sometimes you DO need the outer aggregation — when
// the FIXED dimension is finer than the view.
// If the view shows [State] and the FIXED is on [Customer ID],
// you need AVG or SUM to roll up the customer-level values to the state level.
AVG({ FIXED [Customer ID] : SUM([Sales]) })
The rule: if the FIXED dimension is at the same level or coarser than the view, you don't need an outer aggregation. If it's finer, you do. Tableau will usually give you an "aggregation required" warning when you need one.
2. Dimension Name Mismatches
LOD expressions reference dimensions by their exact name in the data source. If your field is called "Customer Name" in one data source and "Customer" in another, the LOD expression won't match. Always drag fields from the data pane into the calculation editor instead of typing them manually.
3. Using ATTR Where You Shouldn't
ATTR is not supported inside LOD expressions. If you find yourself reaching for ATTR in an LOD formula, you're probably approaching the problem from the wrong direction. Step back and think about which dimension you actually need to FIXED on.
4. Ignoring Null Handling
// If [Sales] contains nulls, this might give unexpected results
{ FIXED [Region] : AVG([Sales]) }
// Explicitly handle nulls
{ FIXED [Region] : AVG(IFNULL([Sales], 0)) }
// Or exclude nulls intentionally
{ FIXED [Region] : AVG(IF NOT ISNULL([Sales]) THEN [Sales] END) }
5. Forgetting About Data Blending Limitations
LOD expressions only work within a single data source. If you're using data blending (linking a primary and secondary data source), LOD expressions from the secondary source won't work as expected. The fix is to use a join or a cross-database join instead of blending when you need LOD expressions across multiple data sources.
Real-World Use Cases
Theory is great, but here's where LOD expressions earn their keep in actual dashboards:
Customer Cohort Analysis
// Step 1: Assign each customer to their acquisition month
[Cohort Month] = { FIXED [Customer ID] : MIN(DATETRUNC('month', [Order Date])) }
// Step 2: Calculate months since acquisition
[Months Since Acquisition] = DATEDIFF('month', [Cohort Month], DATETRUNC('month', [Order Date]))
// Step 3: Customer lifetime value
[Customer LTV] = { FIXED [Customer ID] : SUM([Sales]) }
// Now build a cohort grid: Cohort Month on rows, Months Since Acquisition
// on columns, AVG(Customer LTV) on colour
Sales Rep Benchmarking
// Each rep's average deal size
[Rep Avg Deal] = { FIXED [Sales Rep] : AVG([Deal Value]) }
// Company-wide average deal size
[Company Avg Deal] = { AVG([Deal Value]) }
// Performance index: how does each rep compare?
[Performance Index] = [Rep Avg Deal] / [Company Avg Deal]
// A performance index of 1.2 means the rep's average deal is
// 20% above the company average
Year-Over-Year Growth
// Current year sales per region
[CY Sales] = { FIXED [Region] :
SUM(IF YEAR([Order Date]) = YEAR(TODAY()) THEN [Sales] END)
}
// Prior year sales per region
[PY Sales] = { FIXED [Region] :
SUM(IF YEAR([Order Date]) = YEAR(TODAY()) - 1 THEN [Sales] END)
}
// YoY Growth Rate
[YoY Growth] = ([CY Sales] - [PY Sales]) / [PY Sales]
Customer Retention Flag
// Did the customer order in both current and previous year?
[Ordered This Year] = { FIXED [Customer ID] :
MAX(IF YEAR([Order Date]) = YEAR(TODAY()) THEN 1 ELSE 0 END)
}
[Ordered Last Year] = { FIXED [Customer ID] :
MAX(IF YEAR([Order Date]) = YEAR(TODAY()) - 1 THEN 1 ELSE 0 END)
}
[Retention Status] =
IF [Ordered This Year] = 1 AND [Ordered Last Year] = 1 THEN "Retained"
ELSEIF [Ordered This Year] = 1 AND [Ordered Last Year] = 0 THEN "New"
ELSEIF [Ordered This Year] = 0 AND [Ordered Last Year] = 1 THEN "Churned"
ELSE "Inactive"
END
LOD Expressions with Parameters
LOD expressions become even more powerful when combined with Tableau parameters. This is a technique that competitors' guides don't cover, but it's one of the most useful patterns for building flexible, user-driven dashboards.
// Create a parameter called [Metric Selector] with values:
// "Total Sales", "Average Sales", "Order Count"
// Dynamic LOD expression based on user selection
CASE [Metric Selector]
WHEN "Total Sales" THEN { FIXED [Region] : SUM([Sales]) }
WHEN "Average Sales" THEN { FIXED [Region] : AVG([Sales]) }
WHEN "Order Count" THEN { FIXED [Region] : COUNTD([Order ID]) }
END
Drop the parameter control on the dashboard and your users can switch between metrics without you building three separate worksheets. The dashboard stays clean, and your users feel like they have superpowers. (They don't — you do. But let them have this one.)
LOD Expressions in Tableau Prep vs Tableau Desktop
Another topic that most guides skip: LOD-style calculations don't exist in Tableau Prep the same way they do in Desktop. In Prep, you achieve similar results using the "Fixed LOD" option in the aggregate step or by using joins and aggregate steps in combination.
The practical advice: if your LOD calculation is slow in Desktop because of a live connection, consider moving the calculation upstream into Prep. Compute the FIXED values during the data preparation phase, output them as a column in your extract, and Desktop never has to run the subquery at all. This is the single biggest performance improvement you can make for LOD-heavy dashboards, and it's surprisingly underused.
Debugging LOD Expressions
When an LOD expression returns wrong numbers, follow this checklist:
- Check your filters. Are you using dimension filters with a FIXED expression? If so, promote them to context filters and see if the numbers change.
- Isolate the expression. Create a new, blank worksheet. Drop only the LOD expression and its referenced dimensions on the shelf. Does it return the right value? If yes, the problem is in how it interacts with other elements in your original view.
- Inspect the generated SQL. Use the Performance Recorder or Tableau's "View Data" (right-click > View Data > Underlying Data) to see what the expression is actually computing. The SQL will show you exactly which dimensions and filters are in play.
- Check aggregation wrapping. Is Tableau wrapping your LOD result in an aggregation you didn't intend? Look at the pill on the shelf — if it shows SUM, AVG, or another aggregation prefix, that outer aggregation is affecting your result.
- Verify dimension names. Especially after data source changes. A renamed field will silently break an LOD expression, and Tableau's error messages for this are not always helpful.
Frequently Asked Questions
What is the difference between a calculated field and an LOD expression in Tableau?
A calculated field operates at the row level or the aggregation level defined by your current view. An LOD expression lets you explicitly set the granularity of the calculation using FIXED, INCLUDE, or EXCLUDE keywords, independent of what dimensions are in the view. Calculated fields follow the view; LOD expressions override it.
When should I use FIXED vs INCLUDE vs EXCLUDE LOD expressions?
Use FIXED when you need a calculation at a specific dimension regardless of the view — total sales per region, first purchase date per customer. Use INCLUDE when you need finer granularity than the view shows — average order size when the view only shows categories. Use EXCLUDE when you want to remove a dimension from the calculation — category totals while the view shows sub-categories, or percent-of-parent calculations.
Can I nest LOD expressions inside other LOD expressions?
Yes. A common pattern is using an inner FIXED expression to compute a value at one granularity, then wrapping it in an EXCLUDE or INCLUDE expression to reaggregate at a different level. Example: {EXCLUDE [Month]: AVG({FIXED [Month]: SUM([Sales])})} computes the average of monthly totals across a higher dimension. Keep nesting to two levels when possible — beyond that, both readability and performance degrade.
Why is my LOD expression returning unexpected results?
The most common causes: (1) Dimension filters are applied after FIXED LOD expressions execute, so your filter doesn't affect the FIXED result — promote it to a context filter. (2) Tableau is wrapping your LOD expression in an aggregation you didn't intend — check the pill on the shelf. (3) Dimension names in the expression don't match the actual field names in the data source — drag fields into the editor instead of typing them. (4) Null values in the underlying data are affecting the aggregation — add explicit null handling with IFNULL or ZN.
Do LOD expressions hurt Tableau performance?
They can. LOD expressions generate subqueries or joins in the underlying SQL. FIXED expressions are the most common performance concern because they compute across the entire data source unless you use context filters. To optimise: limit LOD expressions per worksheet, use context filters, prefer extracts over live connections for LOD-heavy workbooks, and use Tableau's Performance Recorder to identify slow queries.
Can I use table calculations inside an LOD expression?
No. LOD expressions are computed at the data source level and translated to SQL, while table calculations operate on results already returned to Tableau. The two can't be mixed inside a single expression. If you need both, compute the LOD expression first, then apply the table calculation on top of the LOD result in your view.
How do LOD expressions interact with Tableau's order of operations?
FIXED LOD expressions are evaluated after data source filters and context filters, but before dimension filters. INCLUDE and EXCLUDE are evaluated after dimension filters. This means a standard dimension filter won't affect FIXED results. To filter data before a FIXED expression evaluates, promote the filter to a context filter by right-clicking it and selecting "Add to Context."
What is a table-scoped LOD expression?
A table-scoped LOD expression has no FIXED, INCLUDE, or EXCLUDE keyword — for example, {MIN([Order Date])}. It computes the aggregation across the entire data source (after data source filters), ignoring all dimensions in the view. It's useful for dataset-wide benchmarks like the earliest date, overall average, or grand total that you need to reference in other calculations.
Conclusion
Calculated fields and LOD expressions are not competing features — they're complementary tools that address different problems. Calculated fields handle row-level logic and view-dependent aggregations. LOD expressions handle everything that needs a specific, stable granularity. Table calculations handle post-aggregation computations on the visual result.
The practical path to fluency: start with FIXED (it covers 80% of LOD use cases), understand the order of operations (it prevents 80% of LOD bugs), and use the Performance Recorder (it prevents 80% of LOD performance problems). Three concepts, and you've handled the vast majority of real-world scenarios.
If your dashboards are still held together by a maze of table calculations with "compute using" configurations that only one person understands, LOD expressions are the way out. They're explicit, they're portable, and they don't break the moment someone adds a filter.