Tableau Calculated Fields to DAX: Function Mapping and Conversion Reference
July 27, 2026
Most Tableau calculated fields convert to DAX by substituting function names. Two things break a naive rewrite: DAX has no row context inside a measure, and several DAX functions share a name with a Tableau function while doing something else entirely. This page maps the function library, then covers the traps that do not raise an error.
Tableau to DAX function mapping
Logic and conditionals
| Tableau | DAX | Note |
|---|---|---|
IF … THEN … ELSEIF … END | IF() or SWITCH(TRUE(), …) | SWITCH(TRUE(), …) is far more readable past two branches |
IIF(test, then, else, [unknown]) | IF(test, then, else) | DAX has no fourth "unknown" argument — handle blanks explicitly |
CASE [x] WHEN … THEN … END | SWITCH([x], …) | Direct equivalent |
AND / OR / NOT | && / || / NOT | AND() and OR() exist in DAX but take exactly two arguments |
ISNULL([x]) | ISBLANK([x]) | A DAX blank is not a SQL null — it behaves as zero in arithmetic |
IFNULL(a, b) | COALESCE(a, b) | Direct equivalent |
ZN([x]) | COALESCE([x], 0) | In ratios, prefer DIVIDE(a, b, 0) — see below |
Aggregation
| Tableau | DAX | Note |
|---|---|---|
SUM, MIN, MAX, MEDIAN | Same names | Direct equivalents |
AVG([x]) | AVERAGE([x]) | Renamed |
COUNTD([x]) | DISTINCTCOUNT([x]) | Renamed |
COUNT([x]) | COUNT([x]) or COUNTA([x]) | DAX COUNT handles numbers, dates and text but not TRUE/FALSE — use COUNTA for boolean columns |
ATTR([x]) | SELECTEDVALUE([x]) | Returns blank instead of * when values are not unique |
STDEV / VAR | STDEV.S / VAR.S | VAR is a reserved keyword in DAX — it declares a variable |
SUM([a]) / SUM([b]) | DIVIDE(SUM([a]), SUM([b]), 0) | Third argument replaces the divide-by-zero result |
Dates
| Tableau | DAX | Note |
|---|---|---|
TODAY() / NOW() | TODAY() / NOW() | Direct equivalents |
DATEDIFF('day', [a], [b]) | DATEDIFF([a], [b], DAY) | Argument order differs; the DAX interval is an unquoted keyword |
DATEADD('month', 3, [d]) | EDATE([d], 3) | DAX DATEADD is a different function — see below |
DATEPART('year', [d]) | YEAR([d]) | Also QUARTER, MONTH, DAY, HOUR |
DATETRUNC('month', [d]) | DATE(YEAR([d]), MONTH([d]), 1) | Row-level form; STARTOFMONTH is time intelligence |
DATENAME('month', [d]) | FORMAT([d], "MMMM") | Returns text — sort it with a numeric month column |
MAKEDATE(y, m, d) | DATE(y, m, d) | Renamed |
DATEPARSE(fmt, str) | — | No DAX equivalent; parse in Power Query |
Strings and type conversion
| Tableau | DAX | Note |
|---|---|---|
LEFT, RIGHT, LEN, UPPER, LOWER, TRIM | Same names | Direct equivalents |
MID(s, start) | MID(s, start, num_chars) | DAX requires the third argument; pass LEN(s) to reach the end |
CONTAINS(s, sub) | CONTAINSSTRING(s, sub) | DAX version is case-insensitive; CONTAINSSTRINGEXACT is not |
REPLACE(s, sub, new) | SUBSTITUTE(s, sub, new) | DAX REPLACE is positional: REPLACE(s, start, length, new) |
FIND(s, sub) | SEARCH(sub, s, 1, 0) | Argument order reverses; SEARCH is case-insensitive, while DAX's own FIND is case-sensitive |
REGEXP_EXTRACT, REGEXP_MATCH, REGEXP_REPLACE | — | DAX has no regular expressions; move the logic to Power Query |
INT([x]) | TRUNC([x]) | Not INT() — see below |
STR([x]) / FLOAT([x]) | FORMAT([x], "General Number") / VALUE([x]) | CONVERT([x], STRING) also works |
Measure or calculated column?
Tableau splits calculated fields into row-level and aggregate. Power BI splits them into calculated columns and measures, and the two splits do not line up. Mapping them wrongly is the most common cause of a converted field that returns a plausible but incorrect number.
A calculated column is evaluated once per row on refresh and has row context, so [Price] * [Quantity] works exactly as it did in Tableau. A measure is evaluated at query time against a filter context and has no row context at all — the same expression fails, because there is no current row to read.
| Tableau calculated field | Power BI equivalent | Why |
|---|---|---|
| Row-level, used as a dimension, filter, or slicer | Power Query column (preferred) or calculated column | Computed on refresh, compresses well, filterable |
| Row-level, only ever aggregated in the view | Measure with an iterator, e.g. SUMX(Sales, Sales[Price] * Sales[Qty]) | Avoids materialising a column you never filter on |
Aggregate (contains SUM, AVG, COUNTD) | Measure | Must respond to slicers and to the visual's grain |
The default should be a measure. Calculated columns cost memory on every refresh and cannot respond to a slicer, so reach for one only when the value has to be filterable or appear on an axis.
Traps that silently change results
1. DATEADD is not DATEADD. Both languages have a function with this name and they are unrelated. Tableau's is row-level date arithmetic. The DAX DATEADD is a time-intelligence function that returns a table of shifted dates and errors when the dates in context are not contiguous. For row-level month arithmetic use EDATE; keep DATEADD for shifting a measure across periods.
2. DATEDIFF reverses its arguments. Tableau takes the interval first, DATEDIFF('day', [Start], [End]); DAX takes it last, DATEDIFF([Start], [End], DAY). A converted formula that compiles is not proof of correctness — swapping the two date arguments returns the same magnitude with the opposite sign.
3. INT rounds down instead of truncating. Tableau's INT() truncates toward zero, so INT(-9.7) returns -9. The DAX INT() rounds down: Microsoft's own remark is that TRUNC(-4.3) returns -4 but INT(-4.3) returns -5. The two agree on every positive number, which is why this survives testing and surfaces in production on refunds, adjustments, and variances. Use TRUNC().
A fourth issue is not a trap so much as a habit: DAX blanks. A blank behaves as zero in arithmetic, and a measure that returns blank makes the row disappear from a visual entirely. Tableau's ZN() wrapper has no automatic counterpart, so ratios need explicit handling — which is what the third argument of DIVIDE is for.
What Antares does with calculated fields
Antares reads every calculated field in a workbook before any DAX is written, resolves what each formula depends on, and separates the mechanical conversions from the ones that need a person. Formulas with structural translation blockers (LOD expressions, table calculations, RAWSQL_ passthrough, and R/Python SCRIPT_ functions) are reported as manual actions with the recommended approach rather than guessed at, because a silently wrong rewrite costs more to find in UAT than a flagged one costs to write.
Run the free Analyzer to see the calculated-field breakdown for your own workbooks before committing to an estimate.
Related reading: Tableau LOD expressions to DAX for FIXED, INCLUDE, and EXCLUDE, and Tableau table calculations to DAX for RUNNING_SUM, WINDOW_AVG, and RANK. Primary sources: Tableau's function reference and Microsoft's DAX function reference.
← Back to Complete Migration Guide
Related Migration Resources
Frequently asked questions
Is there a direct Tableau to DAX function mapping?
For most of the library, yes. AVG becomes AVERAGE, COUNTD becomes DISTINCTCOUNT, IFNULL becomes COALESCE, ATTR becomes SELECTEDVALUE, ZN(x) becomes COALESCE(x, 0). The exceptions are DATEADD, DATEDIFF, INT and REPLACE, which exist in both languages with different meanings or argument orders.
Why does DAX DATEADD not work like Tableau DATEADD?
They are unrelated functions with the same name. Tableau DATEADD is row-level date arithmetic; DAX DATEADD is a time-intelligence function that returns a table of shifted dates and errors when the dates in the current context are not contiguous. For row-level month arithmetic in DAX use EDATE(date, months) instead.
Should a Tableau calculated field become a DAX measure or a calculated column?
A measure by default. Use a calculated column only when the value has to be filterable, appear on an axis, or feed a slicer — Tableau row-level fields used as dimensions. Row-level fields that are only ever aggregated are better as a measure with an iterator such as SUMX.
Why does INT() give a different answer after conversion?
Tableau INT truncates toward zero, so INT(-9.7) returns -9. DAX INT rounds down: Microsoft documents that TRUNC(-4.3) returns -4 while INT(-4.3) returns -5. The two agree on every positive number, which is why this survives testing and appears in production on refunds and variances. Use TRUNC().