Table.AlternateRows Function (Power Query M)

If you want to drop rows from a table at a fixed interval, like stripping out a separator row that repeats every few rows or thinning a table down to every other row, the Table.AlternateRows function is what you reach for. It works by position, keeping and removing rows on a repeating pattern you define. Available in Excel (Power Query), Power BI Desktop, and Power BI Service.

Syntax of Table.AlternateRows Function

Table.AlternateRows(table as table, offset as number, skip as number, take as number) as table

where

  • table (required, table). The source table you want to thin out.
  • offset (required, number). How many rows to keep at the top before the repeating pattern starts.
  • skip (required, number). How many rows to remove in each pass of the cycle.
  • take (required, number). How many rows to keep in each pass of the cycle.

Returns: a new table with rows removed on a repeating pattern. Column order and the order of the surviving rows are left unchanged.

In plain terms, it keeps the first offset rows, then walks down the rest of the table dropping skip rows and keeping take rows, over and over.

Example 1: Keep the first row, then every other row

The pattern runs in two phases. First it keeps the offset rows from the top. Then it repeats a cycle: skip skip rows, then keep take rows.

So Table.AlternateRows(Source,1,1,1) keeps the 1st row, then skips one and keeps one, all the way down.

Here is the starting data:

DayReading
Mon10
Tue20
Wed30
Thu40
Fri50
Sat60
Sun70

Run the pattern with an offset of 1, skipping 1 and taking 1:

let
Source = Excel.CurrentWorkbook(){[Name="Readings"]}[Content],
Result = Table.AlternateRows(Source,1,1,1)
in
Result

It keeps Mon from the offset, then skips Tue and keeps Wed, skips Thu and keeps Fri, skips Sat and keeps Sun.

The result keeps every other row, starting from the first:

DayReading
Mon10
Wed30
Fri50
Sun70

The rows you keep are the take rows. The rows you drop are the skip rows.

Example 2: Keep every other row, starting from the second

Set offset to 0 and the cycle starts straight away, with no rows held back at the top. Because each pass skips before it takes, the very first row gets dropped.

This is the easiest place to trip up, so compare it against Example 1, which used the same skip and take but kept the first row.

Here is the starting data:

DayReading
Mon10
Tue20
Wed30
Thu40
Fri50
Sat60
Sun70

Run it with an offset of 0:

let
Source = Excel.CurrentWorkbook(){[Name="Readings"]}[Content],
Result = Table.AlternateRows(Source,0,1,1)
in
Result

With no offset, it skips Mon and keeps Tue, skips Wed and keeps Thu, skips Fri and keeps Sat, then skips Sun.

The result keeps every other row, this time starting from the second:

DayReading
Tue20
Thu40
Sat60

So to keep the first row and every other one after it, use offset=1, not offset=0.

Example 3: Keep two rows, then skip one

The cycle is not limited to single rows. Here offset is 2, skip is 1, and take is 2, so you keep the first two rows, then repeat a pattern of dropping one and keeping two.

Here is the starting data:

DayReading
Mon10
Tue20
Wed30
Thu40
Fri50
Sat60
Sun70

Keep two at the top, then skip one and take two on each pass:

let
Source = Excel.CurrentWorkbook(){[Name="Readings"]}[Content],
Result = Table.AlternateRows(Source,2,1,2)
in
Result

It keeps Mon and Tue from the offset, skips Wed, keeps Thu and Fri, skips Sat, then keeps Sun.

The result drops only the rows that landed on a skip:

DayReading
Mon10
Tue20
Thu40
Fri50
Sun70

Wed and Sat are gone because they were the skipped rows in each pass.

Example 4: Remove a separator row that repeats

This is where Table.AlternateRows really earns its keep. Pasted or exported reports often have a junk row, like a subtotal or a divider, sitting at a fixed interval.

Here a --- subtotal --- row sits after every data row, and you want it gone.

Here is the starting data:

ItemValue
Apples120
— subtotal —0
Bananas95
— subtotal —0
Cherries60
— subtotal —0

Keep the first row, then skip one and keep one to peel off each separator:

let
Source = Excel.CurrentWorkbook(){[Name="Export"]}[Content],
Result = Table.AlternateRows(Source,1,1,1)
in
Result

It keeps Apples from the offset, skips the separator and keeps Bananas, skips the next separator and keeps Cherries, then skips the trailing separator.

The result is the clean list, separators removed:

ItemValue
Apples120
Bananas95
Cherries60

Because the pattern is position-based, this only works when the junk row sits at a steady interval. If it appears at random spots, filter on its value with Table.SelectRows instead.

Example 5: Drop a repeating two-row block

The block you remove can be more than one row. Flat text exports often repeat a page header and page footer between the real data rows.

Here a page header and page footer pair sits before each data row, so you want to skip two rows and keep one on each pass.

Here is the starting data:

AB
page headerx
page footery
Data 111
page headerx
page footery
Data 222
page headerx
page footery
Data 333

Use an offset of 0, skip 2, take 1:

let
Source = Excel.CurrentWorkbook(){[Name="Log"]}[Content],
Result = Table.AlternateRows(Source,0,2,1)
in
Result

With no offset, each pass skips the header and footer, then keeps the data row that follows.

The result is just the data rows:

AB
Data 111
Data 222
Data 333

The two-row junk block is stripped out on every cycle, leaving only the rows you wanted.

Things to keep in mind with Table.AlternateRows

  • The cycle order is skip first, then take. Each pass removes skip rows before it keeps take rows. Getting this backwards is the usual reason the wrong rows survive.
  • offset only runs once. The first offset rows are kept untouched at the top, and the repeating cycle never reaches back to them.
  • It is position-based, not value-based. It cannot test what is in a row, only where the row sits. For condition-based removal use Table.SelectRows, and to drop duplicate rows use Table.Distinct.
  • A partial final pass keeps whatever is left. If fewer than take rows remain at the end, it keeps them all. If a pass starts with fewer than skip rows left, those rows are simply dropped.
  • The interval has to be steady. If the junk row drifts to an irregular position, the pattern shifts and starts cutting real data. Verify against the source before trusting the output.

Common questions about Table.AlternateRows

How is Table.AlternateRows different from Table.SelectRows?

Table.AlternateRows removes rows by their position on a repeating pattern, while Table.SelectRows removes rows by testing a condition on their values. Use the first to thin a table at a fixed interval, the second to filter on what the rows actually contain.

How do I keep every other row instead of removing alternate rows?

That is exactly what it does, depending on offset. Use Table.AlternateRows(Source,1,1,1) to keep the 1st, 3rd, 5th rows, or Table.AlternateRows(Source,0,1,1) to keep the 2nd, 4th, 6th rows.

Is there a column version of this?

Not with the same pattern logic, but to drop or keep columns by name you use Table.SelectColumns. Table.AlternateRows only ever removes rows, never columns.

List of All Power Query Functions

Related Power Query Functions / Articles:

I am a huge fan of Microsoft Excel and love sharing my knowledge through articles and tutorials. I work as a business analyst and use Microsoft Excel extensively in my daily tasks. My aim is to help you unleash the full potential of Excel and become a data-slaying wizard yourself.