This page is for analysts and BI developers facing a Tableau round, from a first reporting job to a senior dashboard role. Most rounds start with dimensions, measures, blue and green pills, then move to live versus extract and how tables are combined. The middle is calculations: aggregate calcs, LOD expressions and table calculations, and why a filter did or didn't change a number. Senior rounds add performance, publishing, row-level security and a dashboard design case. Each question shows what the interviewer is checking, the shape of a strong answer and a sample you can say out loud. Build each example in a workbook as you practise.
Search all questions by round, difficulty and level, or save the ones you want to practise.
Dimensions: qualitative fields like names, categories and dates that slice the data.
Measures: numbers Tableau aggregates, SUM by default.
In the view: dimensions split the marks; measures are aggregated once per mark.
"A dimension is a field I slice by, like Region, Product or Order Date. A measure is a number I want to add up or average, like Sales or Quantity. When I drop a dimension on Rows, Tableau splits the view into one header per value, so it sets the level of detail. When I drop a measure, Tableau aggregates it, SUM by default, once for every combination of the dimensions in the view. So Region on Rows and Sales on Columns gives one bar per region with total sales. Tableau guesses the role from the data type, which isn't always right. A numeric Customer ID or postal code lands as a measure, and summing IDs is meaningless, so I convert those to dimensions."
Saying blue pills are dimensions and green pills are measures, which mixes up two separate ideas.
Discrete (blue): separate values; creates headers and a categorical colour palette.
Continuous (green): an unbroken range; creates an axis and a colour gradient.
Independent idea: a dimension can be continuous and a measure can be discrete.
"Blue means discrete and green means continuous. A discrete field has separate, countable values, so on Rows or Columns it draws headers, and on Colour it gives each value its own colour. A continuous field is a range, so it draws an axis, and on Colour it gives a gradient. That's a different question from dimension versus measure. Dimension or measure decides whether a field slices the data or gets aggregated. Discrete or continuous decides how it's drawn. So I can have a continuous dimension, like Order Date as a continuous month on a timeline, or a discrete measure, like SUM of Sales switched to discrete so it shows as text headers. When a chart looks wrong, like a bar chart that won't give me an axis, the pill colour is the first thing I check."
Treating blue as dimension and green as measure, or not knowing a date can be either.
Date part: the upper list; discrete by default; Month gives twelve values across all years.
Date value: the lower list; continuous by default; Month means each month of each year.
Use: parts for seasonality, values for a timeline.
"The upper list gives date parts, and the lower list gives date values. Month as a date part is just the month number or name, so January of every year falls into one January bucket. It's discrete by default, so I get twelve headers. Month as a date value truncates each date to the start of its month but keeps the year, so January 2024 and January 2025 stay separate. It's continuous by default, so I get a timeline. I pick based on the question. For seasonality, I use the month date part on Columns and the year on Colour, so each year becomes its own line across twelve months. For a trend over time, I use the month date value so the line runs straight through the years. Either one can be switched between discrete and continuous if the chart needs it."
Building a monthly trend with the month date part and not noticing that all the years have been added together.
Live: queries the source on every interaction; always current; speed and load depend on the database.
Extract: a .hyper snapshot; usually faster; refreshed on a schedule, fully or incrementally.
Decide on: how fresh the data must be, database speed, users, and security.
"A live connection sends a query to the database every time someone clicks, so the numbers are always current, but the speed depends on that database, and a popular dashboard puts real load on it. An extract is a snapshot saved in a .hyper file. It's columnar and built for this kind of query, so it's usually faster, and I can shrink it by filtering rows, hiding unused fields or aggregating. The catch is that it's only as fresh as its last refresh. So I ask how fresh the data really needs to be. A daily sales review is fine on an extract refreshed each morning. An operations screen watching orders minute by minute may need live, ideally against a fast warehouse rather than a transactional system. If the data is huge and only grows, an incremental refresh keeps the extract current without rebuilding it."
Saying extracts are always better, or never mentioning that an extract goes stale between refreshes.
Relationships: logical layer; tables stay separate; Tableau builds the right query per view, measures keep their own level.
Joins: physical layer; tables merged into one before analysis; can repeat rows.
Blending: two separate data sources linked per sheet; secondary aggregated first, like a left join.
Default: relationships first, joins when you need one flat table, blending for sources that can't be related.
"Relationships are the default in newer versions. The tables stay separate in the logical layer, and when I build a view Tableau queries only the tables it needs and aggregates each measure at its own level, so a one-to-many link doesn't inflate my totals. A join happens in the physical layer. The tables are merged into one flat table before I analyse anything, so if an order matches three shipment rows, the order's values appear three times and a SUM overcounts. Blending combines two separate data sources on one sheet. Tableau queries each one, aggregates the secondary source to the linking fields, and brings those numbers in, a lot like a left join from the primary side. I reach for relationships first. I use a join when I truly need row-level fields from both tables together, and blending mainly when the two sources can't be combined any other way."
Describing all three as 'ways to join' without mentioning that joins can duplicate rows and change totals.
Cause: one order matches several shipment rows, so its order-level cost repeats.
Confirm: compare row count with COUNTD of Order ID.
Fix: use a relationship, or take one value per order with a FIXED LOD, or aggregate before joining.
"That's a fan-out from a one-to-many join. Shipping cost sits on the order row, but an order split into two shipments now has two rows after the join, so its cost is counted twice. To confirm, I compare the number of rows with COUNTD of Order ID. If rows are higher, orders are repeating. The cleanest fix is to use a relationship instead of a join, because then Tableau aggregates the cost at the order level before bringing shipments in. If I have to keep the join, I take one value per order with a FIXED expression, MIN of shipping cost for each Order ID, and sum that instead. The other option is to aggregate shipments to one row per order before joining. Whatever I pick, I check the new total against the source system."
// Shipping Cost (per order): one value per order, then summed
SUM({FIXED [Order ID] : MIN([Shipping Cost])})
Dividing the total by two, or switching to AVG, without understanding why the rows repeated.
Real need: how fresh must it be, and what decision needs that freshness.
Risk: analytical queries from many viewers can slow the system taking orders.
Options: a read replica or warehouse, frequent or incremental extract refreshes.
Agree: a freshness target, and check it with the database owners.
"I'd start by asking what 'up to date' needs to mean. If they decide things once a day, a morning extract is fine. If they watch orders during a sale, maybe every fifteen minutes is enough, and that's very different from truly live. Then I'd explain the risk plainly. Dashboard queries are heavy, and with dozens of people clicking filters they'd be hitting the same database that takes customer orders, which could slow checkout. I'd bring the database team in, because they own that risk. The usual options are a live connection to a read replica or the warehouse instead of production, or an extract with frequent incremental refreshes. I'd propose one, agree a freshness target with the manager, and put the as-of time on the dashboard so everyone knows how current the numbers are."
Connecting live to production because a manager asked, without talking to the people who run that database.
Right: SUM([Profit]) / SUM([Sales]), a ratio of totals.
Wrong: [Profit] / [Sales] per row, then summed or averaged.
Why: an aggregate calc recomputes correctly at whatever level the view shows.
"I'd write it as SUM of Profit divided by SUM of Sales. That's an aggregate calculation, so Tableau totals profit and sales for whatever the mark is, a region, a month, the grand total, and then divides. If I write Profit divided by Sales without the SUMs, it's a row-level calculation: it computes a ratio on every order row, and then Tableau aggregates those ratios. Summed, three orders with a ratio of 0.2 each would show 0.6, which is nonsense. Averaged, a tiny order counts as much as a huge one, so the result is skewed. The ratio of totals is weighted correctly and still works when someone adds a new dimension to the view. The general rule is that ratios, averages of rates and margins almost always want the aggregation inside the calculation."
// Profit Ratio: correct at every level of detail
SUM([Profit]) / SUM([Sales])
Computing the ratio per row and averaging it, then defending the result.
Cause: one part is evaluated per row, another per aggregated mark.
Row-level fix: move the condition inside the aggregate.
Aggregate fix: wrap the dimension in ATTR, MIN or MAX, or use an LOD.
"It means part of my formula works per row of data and another part works on aggregated values, and Tableau can't combine the two. A typical case is IF Region equals West THEN SUM of Sales. Region is checked row by row, but SUM of Sales is a total. Usually the fix I want is to push the condition inside the aggregation: SUM of IF Region is West THEN Sales END. That checks every row and adds up only the West rows, and it works in any view. The other direction is to make everything aggregate, for example wrap Region in ATTR, which returns the value when a mark has only one region and an asterisk when it has several. That only gives a useful answer when each mark has one region, like when Region is in the view. So I choose based on the question: filter rows first, or test the aggregated mark."
// Error: IF [Region] = 'West' THEN SUM([Sales]) END
// Row-level condition, then aggregate
SUM(IF [Region] = 'West' THEN [Sales] END)
// Everything aggregate
IF ATTR([Region]) = 'West' THEN SUM([Sales]) END
Wrapping fields in random aggregations until the error goes away without checking the numbers.
Purpose: compute an aggregate at a level different from the view.
FIXED: uses only the dimensions you name, whatever is in the view.
INCLUDE and EXCLUDE: start from the view's dimensions and add or remove some.
Results: INCLUDE and EXCLUDE return measures; FIXED can act as a dimension.
"An LOD expression lets me compute an aggregate at a different grain from the view. Say my view shows sales by region, but I need each customer's total. FIXED computes at exactly the dimensions I list and ignores the view, so FIXED Customer ID, SUM of Sales gives every customer's lifetime total no matter what's on the sheet. INCLUDE takes the view's dimensions and adds some, so it goes finer. Then the result is aggregated back up to the view, which is how I get things like average sales per customer by region. EXCLUDE takes the view's dimensions and removes some, so it goes coarser, handy for comparing a row with its parent total. One practical difference: a FIXED result can be used as a dimension, for example to bucket customers, while INCLUDE and EXCLUDE always return measures. FIXED also behaves differently with filters, which is worth knowing before using it."
{FIXED [Customer ID] : SUM([Sales])}
{INCLUDE [Customer ID] : SUM([Sales])}
{EXCLUDE [Sub-Category] : SUM([Sales])}
{MAX([Order Date])} // no dimension: one value for the whole table
Describing all three as the same thing with different syntax, or not knowing which one ignores the view.
First purchase: FIXED on Customer ID with MIN of Order Date.
Cohort: truncate that date to the quarter and use it as a dimension.
Measure: COUNTD of Customer ID by cohort and order year, optionally as a share of cohort size.
"First I need each customer's first order date on every one of their rows, which is exactly what FIXED does: FIXED Customer ID, MIN of Order Date. Because it's FIXED, it doesn't care what's in the view, so a customer's first date stays the same even when I'm looking at a later year. Then I truncate that to the quarter with DATETRUNC, and that becomes my cohort dimension. On the sheet I put the cohort on Rows, the year of Order Date on Columns and COUNTD of Customer ID as the value. Each row shows how many customers from that cohort bought in each year. To compare cohorts of different sizes, I divide by the cohort size, another FIXED on the cohort with COUNTD of customers. One thing to watch is filters: if someone filters the date range with a normal filter, FIXED still sees all dates, which is what I want here because the first purchase must stay the true first one."
// First Order Date
{FIXED [Customer ID] : MIN([Order Date])}
// Cohort Quarter
DATETRUNC('quarter', [First Order Date])
// Cohort Size
{FIXED [Cohort Quarter] : COUNTD([Customer ID])}
// Retention share, with Cohort Quarter and YEAR([Order Date]) in the view
COUNTD([Customer ID]) / MIN([Cohort Size])
Using MIN of Order Date without FIXED, so the first purchase changes with whatever year is in the view.
Problem: AVG([Sales]) averages rows, not customers.
INCLUDE: total per customer inside the view, then average up to region.
Check: SUM of Sales divided by COUNTD of Customer ID should match.
"AVG of Sales averages the data rows, so a customer with twenty order lines counts twenty times and I get the average line value, not the average customer. What I need is each customer's total first, then the average of those totals within the region. INCLUDE does that: AVG of INCLUDE Customer ID, SUM of Sales. With Region in the view, Tableau sums sales for each region and customer pair, then averages those customer totals up to the region. I'd sanity-check it against SUM of Sales divided by COUNTD of Customer ID, which should give the same answer here. The INCLUDE version earns its place when the manager asks the next question, like the median customer or the biggest customer per region, because I just swap AVG for MEDIAN or MAX on the outside."
// Average sales per customer, at whatever level the view shows
AVG({INCLUDE [Customer ID] : SUM([Sales])})
// Largest single customer per region
MAX({INCLUDE [Customer ID] : SUM([Sales])})
Presenting AVG of Sales as 'average per customer' without noticing it averages order lines.
EXCLUDE: remove Sub-Category from the view's level to get the category total.
Table calc option: percent of total computed along Sub-Category, restarting per category.
Filters: both shrink the denominator when sub-categories are filtered out; FIXED on Category does not.
"With Category and Sub-Category in the view, I need the category total on every sub-category row. EXCLUDE does it: EXCLUDE Sub-Category, SUM of Sales computes at the category level and hands that total down to each row, so I divide SUM of Sales by it. I could also use a percent of total quick table calculation, computed along Sub-Category so it restarts for each category. Both give the same numbers until someone filters. EXCLUDE runs after normal dimension filters, and so does a table calc, so if a user hides two sub-categories the rest are divided by a smaller total, so their shares still add up to the whole. If the business wants the share of the full category even when some are hidden, I use FIXED Category instead, because FIXED is computed before dimension filters. Asking which behaviour they want is part of the answer."
// Share of category, filtered sub-categories leave the denominator
SUM([Sales]) / SUM({EXCLUDE [Sub-Category] : SUM([Sales])})
// Share of the full category, even when sub-categories are filtered out
SUM([Sales]) / MIN({FIXED [Category] : SUM([Sales])})
Dividing by the grand total, or not realising that filtering changes the denominator.
What: a calculation on the aggregated results in the view, after the query returns.
Addressing: the dimensions it moves along.
Partitioning: the dimensions it restarts on.
Example: a running total of monthly sales that restarts each year.
"A table calculation runs on the aggregated results that are already in the view, not on the underlying rows. Running totals, percent of total, rank, difference from the previous value and moving averages are all table calcs. Compute Using tells Tableau how to walk through those results. The dimensions it moves along are addressing, and the dimensions it restarts on are partitioning. Say I have Year on Rows, Month on Columns and a running total of sales. If I compute using Month, Month is addressing and Year is partitioning, so the total builds across the months and starts again each January. If I compute using both Year and Month, the total runs through the whole timeline without resetting. Most confusing table calc results come from the wrong Compute Using, so I set it explicitly with Specific Dimensions rather than trusting Table Across, which breaks the moment someone swaps the layout."
Thinking a table calculation runs on the database rows, or leaving Compute Using on the default without checking.
Formula: current minus LOOKUP of the previous mark, over the previous mark.
Compute along: the year of Order Date.
Traps: first year is null, a missing year compares the wrong years, filtering the view removes the previous year.
"With the year of Order Date in the view, I take SUM of Sales minus LOOKUP of SUM of Sales at offset minus one, and divide by that previous value. I compute it along the year, and the quick table calc called Percent Difference builds almost the same thing. The traps come from LOOKUP working on marks in the view, not on the calendar. The first year has nothing before it, so it's null. If a year has no data at all, LOOKUP compares with whichever year is the previous mark, which may be two years back. And if a user filters to show only 2025, the 2024 mark is gone, so growth becomes null. For that last one I'd use a table calculation filter so 2024 is hidden but still there for the calculation, or build the growth with conditional sums that don't depend on what's shown."
// YoY Growth, compute using YEAR([Order Date])
(SUM([Sales]) - LOOKUP(SUM([Sales]), -1))
/ ABS(LOOKUP(SUM([Sales]), -1))
Writing the formula but not knowing that filtering the view to one year makes it null.
Cause: the dimension filter removes rows before the table calc computes the total.
Table calc filter: filter on LOOKUP(MIN([Category]), 0), which hides marks after the calculation.
LOD option: divide by a FIXED grand total, which ignores dimension filters.
"A normal category filter removes those rows before the percent of total is calculated, so the remaining categories get divided by a smaller total and always add back up to the whole. To keep the original shares, I need the filter to act after the calculation. Table calculation filters are the last filters Tableau applies, so I make a calc that is just LOOKUP of MIN Category at offset zero. It returns the category name, but because it's a table calc, filtering on it only hides marks after everything is computed. I show that as the user's filter instead of the real Category field. The other way is to change the denominator: divide SUM of Sales by a FIXED grand total with no dimensions, which is computed before dimension filters. I'd pick the LOD version if other sheets also need the true total."
// Category (hide only): use this as the filter
LOOKUP(MIN([Category]), 0)
// Or: share of the unfiltered grand total
SUM([Sales]) / MIN({FIXED : SUM([Sales])})
Hard-coding the total into the calculation, or telling users to just not use the filter.
Early: extract filters, data source filters, then context filters.
Middle: FIXED LODs plus sets, conditional and Top N filters at one stage, then normal dimension filters.
Later: INCLUDE and EXCLUDE, measure filters, table calculations, then table calc filters.
Why: it explains when a filter changes a result and when it only hides marks.
"Tableau applies things in a fixed order. First come extract filters and data source filters, which limit the data before anything else. Next are context filters, which create the working set for everything that follows. At the next stage, FIXED LOD expressions are computed and sets, Top N and conditional filters are applied, all before the normal filters. After that come the normal dimension filters most people use. Then INCLUDE and EXCLUDE expressions, then measure filters. Table calculations are computed on what's left in the view, and table calculation filters are applied at the very end. It matters because it explains the questions I get every week. Why did my FIXED number not change when I filtered? It was computed before the filter. Why does my top ten show seven rows? Top N ran before the region filter. Why did my shares change? The filter ran before the table calc. Once you know the order, the fix is usually moving a filter up or down it."
Believing all filters apply at the same moment, so every surprising number looks like a bug.
Cause: the top ten are picked across all regions, then the region filter removes some.
One region: add the region filter to context so the top ten are picked within it.
Every region: RANK or INDEX computed along Product, partitioned by Region, filtered to ten or fewer.
"The Top 10 filter runs before the normal region filter. So Tableau finds the ten best products across the whole company first, then the region filter drops the ones with no sales in that region, so I end up with six, and they may not even be that region's best sellers. For a single selected region, I add the region filter to context. Context filters run before Top N, so the top ten are chosen inside that region. Showing the top ten in every region at once needs a different tool, because a Top N filter picks one list for the whole view. I'd make a rank calc, RANK of SUM of Sales, computed along Product so it restarts for each region, and put it on Filters, keeping values from one to ten. Because it's a table calc filter, it applies last, after the ranking, so every region keeps its own ten."
// Sales Rank: compute using Product, so it restarts per Region
RANK(SUM([Sales]))
// On Filters: keep 1 to 10
Blaming missing data, or not knowing that Top N is evaluated before normal dimension filters.
Parameter: a single workbook-level value the user sets; not tied to one data source.
Needs a reference: does nothing until a calc, filter, reference line or Top N uses it.
Filter: removes data directly and can take many values.
"A parameter is a single value the user controls, like a number, a date or a pick from a list. It lives at the workbook level, not inside one data source, so one parameter can drive sheets built on different sources. On its own it does nothing. It only has an effect when I reference it somewhere, like in a calculated field, a Top N filter, a reference line or a bin size. A filter works directly on the data. It removes rows or marks, it belongs to a field, and it can take many values at once. So if I want the user to pick several regions, that's a filter. If I want them to type a sales target and see which stores beat it, or choose the top five or top twenty, that's a parameter feeding a calculation. Newer versions can also refresh a parameter's list from a field when the workbook opens."
Creating a parameter, showing its control, and expecting it to filter the view without referencing it anywhere.
Parameter: a string list with the three measure names.
Calc: CASE on the parameter returning the matching aggregate.
Polish: put the parameter in the title, handle number formats, or drive it with a parameter action.
"I'd create a string parameter with a list of three values, Sales, Profit and Quantity, and show its control. Then a calculated field with CASE on the parameter: when it's Sales, return SUM of Sales, when Profit, SUM of Profit, otherwise SUM of Quantity. That calc goes on the chart instead of any single measure. The details are what make it usable. The axis title would show the calc's name, so I hide it and put the parameter in the sheet title, so it reads 'Profit by Region' when Profit is picked. Sales and Profit are money and Quantity is a count, so one number format won't fit all three. I either use a neutral format or build a separate label calc. If users would rather click than use a drop-down, a parameter action from a small table of measure names sets it."
// Selected Measure
CASE [Choose Measure]
WHEN 'Sales' THEN SUM([Sales])
WHEN 'Profit' THEN SUM([Profit])
ELSE SUM([Quantity])
END
Building three separate sheets and hiding them, without knowing the parameter approach.
Types: filter, highlight, go to sheet, go to URL, change parameter, change set values.
Triggers: hover, select or menu.
On clear (filter actions): keep filtered values, show all values or exclude all values.
"There are six. A filter action uses a mark I click to filter other sheets, like clicking a region on the map to filter the product chart. A highlight action lights up matching marks elsewhere without removing anything, good for spotting patterns. Go to sheet jumps to another dashboard, useful for a summary page that drills into a detail page. Go to URL opens a link, for example to a record in another system with the ID passed in. A parameter action sets a parameter from a click, and a set action changes a set's members, which is how I build things like click-to-expand or proportional brushing. Each can run on hover, select or menu. The setting people forget on a filter action is what happens when the selection is cleared. For a detail table, I often choose exclude all values, so it stays empty until someone clicks."
Knowing only filter actions, or not knowing that clearing a selection can be set to show all, keep or exclude.
Ask first: who uses it, which decisions, how often, on what screen, which targets.
Top: a few KPIs with comparison to target and last period.
Middle: trend over time and a breakdown by product and rep.
Detail on demand: actions or a drill page, few filters, tested with real managers.
"I'd start with questions, not charts. What does a regional manager decide after looking at it, maybe where to push the team or which products to discount? How often do they look, on a laptop or a phone, and which targets are they measured on? Then the layout follows how people read, top-left first. Across the top I'd put four or five headline numbers, like sales, margin and orders, each against target and last month, coloured only when something is off track. Under that, a monthly trend with the target as a reference line, and a bar chart by product category sorted by gap to target. At the bottom, a rep or store table that fills when a manager clicks a bar. I'd keep filters to region and month, use a fixed size, and pick colour for meaning, not decoration. Then I'd sit with two managers, watch them use a draft and cut whatever they ignore."
Jumping straight to a list of charts without asking who uses it or what they need to decide.
Understand: what question each new chart answers and who needs it.
Offer structure: keep the summary page, move detail to a linked page or tab.
Show, then agree: a quick mock-up and a clear trade-off on speed and readability.
"I wouldn't say no, and I wouldn't just cram them in. I'd ask for ten minutes to understand what each chart is for. Often several of the twelve answer the same question, or a couple are for a different audience. Then I'd propose a structure: the current page stays the summary, with the key numbers on top, and the extra charts go on a detail page reached by clicking a number or a navigation button. I'd sketch it quickly, even as a rough draft in the workbook, so they're reacting to something real. I'd also be honest about the trade-off: twelve more sheets on one page would make it slower to load and harder to read in a meeting. For tomorrow, I'd commit to the few charts that matter most and schedule the rest."
Adding all twelve without a question, or refusing outright because it breaks design rules.
Measure: Performance Recorder to split query time from rendering and layout.
Data: extract, hide unused fields, filter or aggregate early, simplify joins.
Design: fewer sheets and marks, fewer quick filters showing only relevant values.
Calcs: cheaper types, fewer string operations, heavy logic moved upstream.
"I don't guess. I run the Performance Recorder, reload the dashboard and look at what takes the time: executing queries, computing layouts or rendering. If it's queries, I look at the data. Is it live against a slow source when an extract would do? Can I hide unused fields, filter out old years or aggregate the extract to the level the dashboard actually uses? Are there extra joins nobody needs? If it's layout or rendering, it's usually too many marks or sheets. A big crosstab with thousands of rows is a common culprit. Every quick filter runs its own query to fill its list, and ones set to show only relevant values run again whenever another filter changes, so I switch them to all values or replace them with actions. For calculations, I avoid heavy string logic where a number or a boolean works, and I move complex logic into the database or the extract. Then I record again to prove it helped."
Listing tips from memory without measuring first, or blaming the server without checking the workbook.
Data source: embedded in the workbook or published separately and shared.
Credentials and refresh: embed or prompt; extract refresh schedule; a bridge for private network data on Cloud.
Access: projects, groups and permissions; subscriptions and alerts for users.
"The first choice is the data source. I can embed it in the workbook, which is quick for a one-off, or publish it separately so many workbooks share one source, one set of calculations and one refresh. For anything used by more than one team, I publish it separately, so everyone uses the same definitions. Next is credentials: embed them so viewers don't need database access, or prompt each user. If it's an extract, I set a refresh schedule that lands before people start work. On Tableau Cloud, data inside a private network needs Tableau Bridge to reach it. Then access. I publish into a project whose permissions are set on groups, not individual people, so access is managed in one place. Finally I tell users what they can do: subscribe to a daily email or set a data-driven alert on a number they care about."
Publishing everything with embedded data sources and individual permissions, with no thought about refresh or ownership.
Entitlements: a table mapping each username to the regions they may see.
User calc: match that table against USERNAME() or ISMEMBEROF() for groups.
Enforce: a data source filter on the published data source, not a sheet filter.
Test: preview as several users and lock down edit and download rights.
"I'd keep an entitlement table that maps each user to the regions they're allowed to see, rather than hard-coding names in a calculation. I join that table to the data on Region. Then a calc checks whether the entitlement username equals USERNAME(), the signed-in user, and returns true or false. For rules by team, ISMEMBEROF checks group membership. The key is where the filter lives. I put it as a data source filter on the published data source, so every workbook built on it inherits the rule. A filter on a worksheet can be removed by anyone who can edit the workbook. Before release I test as several users, including one with no entry, who should see nothing. I also stop viewers from downloading the workbook or editing it on the web, so nobody can take the data out from under the rule."
// User Allowed: data source filter, keep True
[Entitled User] = USERNAME()
// Group-based variant
ISMEMBEROF('Leadership') OR [Entitled User] = USERNAME()
Using a normal sheet filter or hiding sheets as the security, or hard-coding usernames into the calculation.
Tell people: warn the meeting owner at once, with the data's as-of time.
Diagnose fast: read the job error; credentials, source outage or timeout.
Recover: run a manual refresh if it fits in time; otherwise present with a clear note.
Prevent: failure alerts, an as-of label and a fix for the root cause.
"First, I'd message the person running the meeting straight away: the data is from yesterday morning, I'm on it, and I'll update them in twenty minutes. Surprises in a leadership meeting are worse than bad news beforehand. Then I'd open the failed job on the server and read the error. Most failures are expired or changed database credentials, the source being down, or a timeout. If it's credentials, I update them and run the refresh by hand, and if it finishes in time, great. If it can't finish, the dashboard is still usable with yesterday's data, as long as everyone knows, so I'd make sure the as-of date is visible. Afterwards I'd fix the cause properly, make sure failure notifications reach me, and add a 'data as of' label so viewers can always see how fresh it is."
Saying nothing and hoping nobody notices, or promising a fix you can't deliver in the hour.
Situation: which numbers disagreed and who noticed.
Method: narrow by period and dimension until one small set of rows differs.
Cause and fix: the definition, filter or join behind it, and what you changed.
Prevention: agreed definitions, a check or a note on the dashboard.
"At my last company, finance said my revenue dashboard was higher than their monthly report. I didn't argue about who was right. I asked for their extract for one month and started narrowing it down. The totals differed, so I split by region, and only two regions were off. Then by week, then by order, until I had a short list of orders on my side but not theirs. Every one had been cancelled and refunded. Finance removed refunded orders by invoice date, and my data source only dropped cancellations. Once we saw that, the fix was to agree on one definition of net revenue, which I built into the shared data source, so every workbook used it. I added a line on the dashboard saying what revenue included, and we set up a small monthly check comparing the two totals."
Adjusting a filter until the totals matched without finding the real cause, or blaming the other team's report.
Need: who asked, and the decision they were stuck on.
Discovery: how you learned what they really needed.
Build: the few views that answered it.
Result: the decision it changed, in plain terms.
"At my last company, the support lead asked for 'a dashboard of all tickets'. Instead of building that, I asked what she decided each week, and it turned out to be how many people to put on each shift. So the real question was when tickets arrive and how long they wait. I built a heat map of tickets by hour and weekday, a line of average wait time by hour, and a filter by channel, and that was nearly all. In the first review she noticed that Monday mornings and one evening slot had long waits while mid-afternoon was overstaffed. She moved two people's shifts, and within a month wait times at those peaks had clearly dropped. What I took from it is that the first request usually describes data, and my job is to find the decision behind it."
Describing a dashboard only by its charts and features, with no user, decision or outcome.
Signal: how you knew usage was low.
Why: what users told you or what you saw them do.
Change: simplifications, defaults, speed or training.
Result: what usage looked like after.
"At my last company, I'd built an inventory dashboard for store managers, and the server's usage views showed only a handful of them had opened it in a month. I called four managers and asked them to open it while I watched. Three problems came up fast. It took almost a minute to load on store laptops, it opened on all stores so they had to filter to their own every time, and it had eleven filters they didn't understand. So I switched it to an extract with only the fields it used, set the default view to each manager's own store using their login, and cut the filters to three. I also added a short 'how to read this' note at the top and ran a fifteen-minute call. Over the next two months, most managers were opening it weekly, and the store stock-outs they reported came down."
Saying users need more training without ever watching them use the dashboard.
ClapAssist is an AI interview assistant for Mac and Windows. It listens to the interview on your computer and shows you what to say, in short lines you can read while you talk. Your resume and notes are never stored on our servers. It stays out of screen share on every plan; only you can see it.