Skip to content

DAX EARLIER Explained: Reference the Previous Row in a Nested Row Context

EARLIER in DAX lets you reach a parent row context from inside a nested iteration. Learn what it does, when you actually need it, and the modern alternatives.

If you have spent more than a week writing DAX, you have hit it: a calculated column that needs the “current” value of a row and a value from the same table one level up. That is exactly what EARLIER is for — and exactly why most beginners find it confusing. This guide explains what EARLIER actually references, shows a worked example, and then shows the patterns that make you not need it at all.

What EARLIER really does

EARLIER returns the value of a column in an outer row context — a row context created by a parent iterator, not the innermost one you are currently in.

A row context is created by:

  • a calculated column (one per row of the table), and
  • iterator functions like SUMX, FILTER, RANKX, ADDCOLUMNS, MAXX.

When an iterator runs inside a calculated column, you get two row contexts stacked: the column’s own row, and the row the iterator is looping over. EARLIER reaches the outer one. EARLIEST reaches two levels up.

The classic example: rank within a category

Say you have a Sales table and want a column that ranks each row by Amount within its own Product. A naive RANKX in a column ranks across the whole table, not per product. EARLIER fixes that:

Rank within Product =
RANKX (
    FILTER (
        Sales,
        Sales[Product] = EARLIER ( Sales[Product] )
    ),
    Sales[Amount],
    ,
    DESC,
    DENSE
)

Here is what happens step by step:

  1. The calculated column creates a row context for each Sales row.
  2. FILTER iterates Sales again, creating a second row context.
  3. Inside FILTER, EARLIER ( Sales[Product] ) pulls the product of the outer (column) row, so the filter keeps only rows of the same product.
  4. RANKX then ranks only within that subset.

Without EARLIER, the inner Sales[Product] would refer to the row FILTER is currently scanning — which is always equal to itself, so the filter would never narrow correctly.

When you should NOT reach for EARLIER

EARLIER only works in a calculated column, because it depends on a row context. Most real reporting is better done with measures, which have no row context and therefore cannot use EARLIER at all. Three modern replacements:

1. Use a measure with RANKX and a visual context

If the ranking is for a visual, do it as a measure:

Rank by Product Measure =
RANKX (
    ALLSELECTED ( Sales[Product] ),
    [Total Amount],
    ,
    DESC,
    DENSE
)

This ranks products in the current visual filter, no column, no EARLIER.

2. Use variables to capture the outer value

When you do need a column, a variable is clearer than EARLIER and easier to read:

Rank within Product (var) =
VAR CurrentProduct = Sales[Product]
RETURN
    RANKX (
        FILTER ( Sales, Sales[Product] = CurrentProduct ),
        Sales[Amount],
        , DESC, DENSE
    )

CurrentProduct captures the outer row context before the inner FILTER shadows it. Same result, no EARLIER keyword, far easier to debug.

3. Use window functions (Power BI / DirectQuery friendly)

Newer Power BI supports RANK and INDEX window functions that handle grouping natively:

Rank within Product (window) =
RANK ( DENSE, Sales, ORDERBY ( Sales[Amount], DESC ), PARTITIONBY ( Sales[Product] ) )

This reads top to bottom and avoids both EARLIER and manual FILTER subsets.

Performance note

EARLIER wrapped in FILTER ( FullTable, ... ) forces a row-by-row scan of the whole table for every row — it is one of the slower patterns in DAX. On a large table, prefer the window function or a pre-grouped summary table. See power-bi-dax-optimization and power-bi-performance-analyzer-guide for measuring it.

FAQ

Q: Is EARLIER deprecated? A: No, it still works. But variables and window functions are preferred because they are easier to read and usually faster.

Q: Why do I get an error “EARLIER has no outer row context”? A: You used it where no parent row context exists — typically inside a measure, which has a filter context but no row context. Capture the value with a variable inside SUMX instead, or rewrite as a measure.

Q: EARLIER vs EARLIEST? A: EARLIER goes one level up; EARLIEST goes to the top level regardless of depth. Use EARLIEST only when you are three or more iterators deep.