Skip to content

Translate Tableau table calculations to SQL windows.

Preserve partitioning, addressing and sort order when rewriting a table calculation. This synthetic monthly example computes a running sum with an explicit SQL window frame.

Updated

Read locally in this browser.

↳sample.csvSynthetic sample

What is the cumulative revenue after each month?

Synthetic monthly revenue is 100, 150 and 200. The cumulative result is 100, 250 and 450 with Month as the addressing order.

“Translate the monthly running sum to a SQL window.”

Worked example · authored synthetic data

  1. Read the original fileUse the existing browser reader
  2. Check the definitionKeep the grouping and filter scope explicit
  3. Compute the resultUse the existing xmetric engine
  4. Review the evidenceRead the exact cells and query

Calculation or query in this example

SELECT Month, SUM(SUM(Revenue)) OVER (ORDER BY Month ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS RunningRevenue FROM input GROUP BY Month ORDER BY Month

Exact sample file · Definitions and expected values

xmetricRunning Revenue

Opening the labelled sample…

Expected values from the labelled sample; the browser recomputes them.

GroupResult
2026-01100
2026-02250
2026-03450
Use your own file

Drop one file anywhere on this page. It is read in your browser; no file is uploaded.

Nothing is uploaded.

Read locallyThe original file stays in the browser.

Share deliberatelyMCP shares supported metadata and requested results with the chat host.

Separate actionsOnline saves and support uploads require their own actions.

How does this workflow work?

What is the direct answer to this workbook question?

Many Tableau table calculations can be rewritten as SQL window functions when partitioning, addressing, sort order and frame bounds match. RUNNING_SUM commonly uses SUM() OVER, LOOKUP uses an offset window, and WINDOW_AVG uses AVG() OVER; missing dates and filter timing can change the result.

How do addressing and partitioning map to SQL?

Tableau's "Edit Table Calculation" dialog uses two terms that confuse SQL writers: Partitioning and Addressing (labeled "Compute Using" / "Specific Dimensions").

Here is the exact mechanical rule for translating any worksheet's table calculation into SQL:

  1. First, aggregate the base measures at the grain of all Dimensions currently on the worksheet (Rows, Columns, Detail, Color) in a CTE.
  2. Unchecked boxes in "Specific Dimensions" (Partitioning) become the columns inside PARTITION BY.
  3. Checked boxes in "Specific Dimensions" (Addressing) become the columns inside ORDER BY.

Which SQL windows correspond to common table calculations?

Assuming your first CTE (view_grain) has already computed SUM(sales) AS total_sales grouped by region (partitioning) and order_month (addressing):

Tableau Table CalculationSQL Window Function EquivalentNotes on Window Frame
INDEX()ROW_NUMBER() OVER (PARTITION BY region ORDER BY order_month)1-based row index within each partition.
FIRST()1 - ROW_NUMBER() OVER (PARTITION BY region ORDER BY order_month)Returns 0 on the first row, -1 on the second, -2 on the third.
LAST() COUNT(*) OVER (PARTITION BY region) - ROW_NUMBER() OVER (PARTITION BY region ORDER BY order_month)Returns 0 on the last row and positive integers counting backward.
SIZE()COUNT(*) OVER (PARTITION BY region)Total number of marks in the partition.
RUNNING_SUM(SUM([Sales]))SUM(total_sales) OVER (PARTITION BY region ORDER BY order_month ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)Always include ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW so duplicate sort values don't merge as RANGE.
LOOKUP(SUM([Sales]), -1)LAG(total_sales, 1) OVER (PARTITION BY region ORDER BY order_month)Negative offset uses LAG(); positive offset (+1) uses LEAD().
WINDOW_AVG(SUM([Sales]), -2, 0)AVG(total_sales) OVER (PARTITION BY region ORDER BY order_month ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)Moving 3-period average (2 preceding rows through current row).
RANK(SUM([Sales]), 'desc')RANK() OVER (PARTITION BY region ORDER BY total_sales DESC)Standard competition rank (1, 2, 2, 4).
RANK_DENSE(SUM([Sales]), 'desc')DENSE_RANK() OVER (PARTITION BY region ORDER BY total_sales DESC)Dense rank without gaps (1, 2, 2, 3).
RANK_UNIQUE(SUM([Sales]), 'desc')ROW_NUMBER() OVER (PARTITION BY region ORDER BY total_sales DESC)Unique rank with no ties (1, 2, 3, 4).
TOTAL(SUM([Sales]))SUM(total_sales) OVER (PARTITION BY region)Unlike WINDOW_SUM, TOTAL() in Tableau re-evaluates the underlying aggregation across the partition; for SUM, SUM(total_sales) OVER (...) matches.

How can I calculate monthly growth and a rolling average?

In a Tableau workbook, you often see two table calculations layered on a monthly revenue sheet partitioned by Region:

// 1. MoM Growth %
(SUM([Sales]) - LOOKUP(SUM([Sales]), -1)) / ABS(LOOKUP(SUM([Sales]), -1))

// 2. 3-Month Moving Average
WINDOW_AVG(SUM([Sales]), -2, 0)

To get the exact same numbers in DuckDB, Postgres, or BigQuery SQL, aggregate to the worksheet grain first, then apply the window functions in the outer query:

WITH view_grain AS (
  SELECT
    region,
    DATE_TRUNC('month', order_date) AS order_month,
    SUM(sales) AS monthly_sales
  FROM orders
  GROUP BY region, DATE_TRUNC('month', order_date)
),
windowed AS (
  SELECT
    region,
    order_month,
    monthly_sales,
    LAG(monthly_sales, 1) OVER (
      PARTITION BY region
      ORDER BY order_month
    ) AS prev_month_sales,
    AVG(monthly_sales) OVER (
      PARTITION BY region
      ORDER BY order_month
      ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
    ) AS rolling_3m_avg
  FROM view_grain
)
SELECT
  region,
  order_month,
  monthly_sales,
  (monthly_sales - prev_month_sales) / NULLIF(ABS(prev_month_sales), 0) AS mom_growth_pct,
  rolling_3m_avg
FROM windowed
ORDER BY region, order_month;

How do missing dates change a table calculation?

One subtle difference between Tableau and SQL: if a region has sales in January and March but zero rows in February, SQL LAG(monthly_sales, 1) on March will grab January's sales, and ROWS BETWEEN 2 PRECEDING AND CURRENT ROW will average 3 non-consecutive months.

If Tableau has "Show Missing Values" turned on for the date pill, your SQL query must CROSS JOIN a calendar spine of all months with the distinct regions before running LAG() or AVG() OVER().

How do I see which dimensions a table calculation uses in a .twbx file without Tableau Desktop?

Drop the .twbx or .twb file into the xmetric workbook inspector to list its data sources, sheets, and table calculation formulas for free, or test the table calculation on sample data in the formula playground, where you set Compute Using with addressing and partitioning. You can also open the workbook in the xmetric app and work with its sheets on shelves in the browser.

Why can't I write SUM(sales) OVER (...) in a single SQL query without a CTE?

You can write SUM(SUM(sales)) OVER (PARTITION BY region ORDER BY DATE_TRUNC('month', order_date)) directly inside a GROUP BY query in standard SQL. However, separating the GROUP BY step into a view_grain CTE makes complex formulas like LOOKUP and nested window calculations much easier to read and debug.

What is the SQL equivalent of a Table Calculation Filter?

Because Tableau evaluates Table Calculation filters last in its order of operations (after window functions are computed), a table calculation filter translates to a QUALIFY clause in DuckDB/BigQuery/Snowflake, or an outer WHERE clause wrapping the windowed CTE.

Test a table calculation on sample data in the formula playground, then build the full view on shelves in the xmetric app.

What data does this worked example use?

The initial view uses authored synthetic data, not customer data or a captured agent session.

The example record lists its file, calculation or SQL and expected values. The live browser result is computed by xmetric’s existing engine or workbook inspector. Samples are excluded from own-data activation reporting.