Skip to content

Coming from DAX or Tableau

SquareX deliberately borrows the semantics you already know — measures, filter context, level-of-detail — and spells them in a smaller, plainer syntax. If you can write DAX or LOD expressions, you already think in SquareX; this page maps the vocabulary.

You know it as In DAX In Tableau In SquareX
Named measures Measures (calculated fields) Name := expr
Measure references [Measure] [Measure] — same brackets
Modify filter context CALCULATE context filters Calculate(expr, mods…)
Remove filters ALL / REMOVEFILTERS / ALLEXCEPT (FIXED, implicitly) RemoveFilters(dims…)
Additive filter filter args / KEEPFILTERS context filters bare predicates in Calculate
Fixed-grain LOD CALCULATE + ALLEXCEPT patterns {FIXED …} Fixed(dims…, expr)
View-relative LOD (visual calculations) {INCLUDE …} / {EXCLUDE …} Include / Exclude
Same period last year SAMEPERIODLASTYEAR, DATEADD date calcs DateShift(col, -1, "year")
To-date totals TOTALYTD / QTD / MTD RUNNING_SUM table calc YTD / QTD / MTD
Rolling window DATESINPERIOD + CALCULATE WINDOW_SUM table calc RollingSum(expr, n, grain)
Iterators SUMX / AVERAGEX / … (row-level calcs) SumX / AvgX / MinX / MaxX
Variables VAR … RETURN Let(name, expr, body)
Value match SWITCH CASE Switch(expr, v, r, …)
Searched conditions SWITCH(TRUE(), …) idiom CASE WHEN Case(cond, r, … [, default])
Semi-additive (balances) LASTNONBLANK patterns LastNonEmpty / PeriodEndSnapshot
Safe division DIVIDE(a, b) (opt-in) ZN/manual Divide(a, b) — safe by default
Row context / context transition implicit, EARLIER, … doesn’t exist — deliberately

Searched branching — DAX’s most famous workaround, gone:

DAX
Revenue Tier =
SWITCH(TRUE(),
[Revenue] >= 1000000, "Tier 1",
[Revenue] >= 100000, "Tier 2",
"Tier 3")
SquareX
RevenueTier := Case(
[Revenue] >= 1000000, "Tier 1",
[Revenue] >= 100000, "Tier 2",
"Tier 3")

The %-of-total denominator:

DAX
Pct of Total = DIVIDE([Revenue], CALCULATE([Revenue], ALL('Product')))
SquareX
PctOfTotal := Divide([Revenue], Calculate([Revenue], RemoveFilters(Product)))

Tableau’s fixed LOD:

Tableau
// Avg revenue per customer (needs the measure wrapped at the sheet level)
AVG({ FIXED [Customer Id] : SUM([Amount]) })
SquareX
CustomerRevenue := Fixed(Customer.Id, [Revenue])
// …then set the chart's field-well aggregation to avg

Variables:

DAX
Margin % =
VAR rev = [Revenue]
RETURN DIVIDE(rev - SUM(Sales[Cost]), rev)
SquareX
MarginPct := Let(rev, [Revenue],
Divide(rev - Sum(Sales.Cost), rev))

Each difference is deliberate — chosen to be easier to read, write, and review:

Choice SquareX DAX / Tableau
Definition Name := expr DAX Name = expr (vs = comparisons); Tableau calc dialog
Column reference Sales.Amount — plain dotted path 'Sales'[Amount] / [Table].[Field]
Measure reference [Measure Name] [Measure] — the one thing kept identical
Date grain Date.Date:month inline DATETRUNC / bucketing boilerplate
Logic operators and · or · not — words && · || · NOT
Case sensitivity case-insensitive, stored canonically DAX case-insensitive-ish; Tableau case-sensitive fields
Division by zero Divide safe by default DAX / gives infinity; DIVIDE is opt-in
Comments // anywhere, survive save // and /* */
Function count 39, curated ~250+ in DAX
  • Row context and context transition. There is no implicit row context, no EARLIER, no wrapping a measure to force context transition. Measures are aggregate-level, full stop; per-row arithmetic happens only inside the iterators, which do not transition context. Row-level logic (per-row labels, flags) lives in calculated fields — a simpler tool that compiles straight into the row SQL.
  • BLANK() arithmetic. SquareX keeps SQL semantics: NULL + 5 is NULL — not DAX’s BLANK() + 5 = 5. When you want zeros, say so: Coalesce([Measure], 0). See NULL & type semantics.
  • Tableau’s FIXED-vs-filters surprise. Tableau evaluates FIXED before dimension filters, which is why filters “don’t work” on LODs until promoted to context filters. SquareX Fixed evaluates inside the active filter context — filters just work. The filter-independent variant is an explicit opt-in flag: Fixed(dims…, expr, IgnoreFilters).
  • Guessing what a filter argument does. In SquareX a bare predicate in Calculate always narrows (intersects) and never overrides; to override you visibly remove then add: Calculate([Revenue], Region = "West", RemoveFilters(Region)). Modifier order is by category (removals first), so the written order can’t bite you.
  • Table-calc positioning. There are no WINDOW_SUM-style calcs that depend on the pane layout. YTD, RollingSum, and DateShift name their date column explicitly and compile into the query — rearranging the visual can’t change the number.

A few DAX/Tableau capabilities have no SquareX equivalent on purpose:

  • A 250-function catalog. The surface is 39 functions. For exotic SQL, use calculated fields (row-level) or the SQL editor — the escape hatch is SQL, not more functions.
  • Ranking/window functions (RANKX, INDEX/OFFSET) — visual-level quick calcs cover ranking today.
  • Visual calculations — a separate visual-scoped runtime is exactly the kind of second engine SquareX avoids; quick calcs handle per-visual math.
  • USERELATIONSHIP — the signature UseRelationship(fromCol, toCol) is reserved for role-playing-date models; it activates when the first model needs it.

If a measure needs something the language refuses, it fails loudly with a diagnostic at the exact position — never a silently different number.