LEFT(), RIGHT() & MID()

Learn how DAX's LEFT, RIGHT, and MID extract a substring by position, why MID counts starting positions from 1 (not 0), and why that's the opposite of Power Query's Text.Middle.

LEFT(), RIGHT() & MID()

These three functions extract a portion of a text value based on position and length — the DAX equivalents of Power Query's Text.Start(), Text.End() & Text.Middle(), with one important difference in how positions are counted.

LEFT(text, [num_chars])
RIGHT(text, [num_chars])
MID(text, start_num, num_chars)

LEFT() and RIGHT()

LEFT("INV-2026-0042", 3)
RIGHT("INV-2026-0042", 4)
LEFT(..., 3)  -> "INV"
RIGHT(..., 4) -> "0042"

Both count from the respective end of the string — LEFT from the beginning, RIGHT from the end — and, like their Power Query counterparts, simply return fewer characters than requested if the text is shorter than num_chars, rather than erroring.


MID(): One-Indexed Starting Position

MID("INV-2026-0042", 5, 4)
Position:  123456789...
Text:      INV-2026-0042
               ^^^^
MID(..., 5, 4) -> "2026"

The start_num argument is 1-indexed — position 5 is the 5th character, matching how Excel's MID() works and how most people naturally count. This is the opposite convention from Power Query's Text.Middle(), which is zero-indexed.

Try it live

FieldValue
Text
Start (1-indexed)
Num Chars
I1
N2
V3
-4
25
06
27
68
-9
010
011
412
213

Numbers below the ruler are 1-indexed, the way MID() counts positions.

MID("INV-2026-0042", 5, 4)
Result:"2026"

The identical substring, extracted in Power Query M instead — note the start argument:

Text.Middle("INV-2026-0042", 4, 4)

Same result, different start number: MID() start 5 means the same character as Text.Middle() start 4 — MID() counts the first character as 1, Text.Middle() counts it as 0.

Try the same extraction in both functions: MID(text, 5, 4) in DAX and Text.Middle(text, 4, 4) in Power Query return the identical substring — the start number just needs to shift by one to account for the different counting convention.


The Real Trap: Moving Between DAX and Power Query

A Power BI report almost always uses both languages — Power Query for the load/transform layer, DAX for measures and calculated columns. Someone comfortable with one language's substring function can carry the wrong indexing assumption straight into the other:

Power Query: Text.Middle([Code], 4, 4)   <- start counts from 0
DAX:         MID([Code], 4, 4)           <- would start one character too early

The same start value of 4 extracts a different substring in each language — Text.Middle treats it as the 5th character, MID treats it as the 4th. Neither function errors when this happens; it just silently returns a substring shifted by one character from what was intended.


Common Mistakes

Assuming MID() Is Zero-Indexed Like Text.Middle()

Porting a Text.Middle([Code], 4, 4) expression into a DAX calculated column as MID([Code], 4, 4) produces a result shifted one character early — MID([Code], 5, 4) is the actual equivalent.

Assuming Text.Middle() Is One-Indexed Like MID()

The same mistake in the opposite direction: writing Text.Middle([Code], 5, 4) while thinking in DAX/Excel terms starts one character too late in Power Query.

Assuming a Fixed Length That Doesn't Hold for Every Row

LEFT([Code], 3) assumes every value in [Code] has at least a 3-character meaningful prefix — a shorter value doesn't error, it just returns less than expected, which can silently produce wrong-looking results rather than an obvious failure.


Best Practices

  • When porting a substring expression between Power Query and DAX, explicitly adjust the start number by one rather than copying it directly — don't assume either language's convention.
  • Use LEN([Code]) (DAX) or Text.Length([Code]) (Power Query) to compute a variable start or length instead of hardcoding a position that only holds for some rows.
  • Prefer doing substring extraction in whichever layer the value is first available in, rather than duplicating the same extraction logic in both languages.

Next Steps

Need the rest of DAX's text functions in one place — CONCATENATEX, SUBSTITUTE, FORMAT, EXACT? See the DAX Text Functions Cheat Sheet.