If you have a range of data and need to cut just a portion of it (based on one or more criteria), the =AGGREGATE() function in Excel can help.
In this post, we will use a simple database of expenses with category and subcategory attached to every amount. Then we will see how to fetch only the records that match our interest in a particular dimension of data.
Get the file
The file below will be addressed in the paragraphs that follow:
The problem we try to solve
Let’s assume you are a fairly responsible adult who keeps track of his/her expenses. Also, you are addicted to Excel so you use a spreadsheet for the purpose.
You record your charges on sheet All Spendings. Data is entered into an Excel table named Expenses. The Category and Subcategory provide granularity to the amounts:

Before I start — In the first column on sheet Lists the unique categories are populated and will be used for the drop-down lists in the spreadsheet. As we will see later, the user will be able to select a particular category and then the subcategories for it will appear in the second column. Furthermore, there is a post that describes how this is done (Extracting the unique items from a list of entries).
Getting the rows for a single category
On sheet Extract Cat Only we will choose a category and then get all rows of data regardless of the subcategory.

The formula in each column is slightly different but they follow the same mask. I will be explaining the one in the Subcategory column.

From the inside out there are:
- The =AGGREGATE() function that will be examined in detail.
- In a nutshell, this is an =INDEX() function that looks into the Subcategory column and returns the value on the row that is supplied by the =AGGREGATE(). It sounds confusing but later we will see it is actually simple. (This is like an INDEX/MATCH combo but with =AGGREGATE() instead of =MATCH())
- An =IFERROR() error handler.
The =AGGREGATE() itself
The =AGGREGATE() function is going through the rows in the Expenses table and returns only the ones that match our criteria. The four arguments employed are:
- A — 15 is the code of the =AGGREGATE() function to be used. The =AGGREGATE is like a Swiss knife. It is capable of behaving like many other functions but builds on top of them by adding the parameter in the next bullet. In our case, the code (15) means SMALL, therefore, it will behave like the =SMALL() function.
- B — The option to ignore values lays with this argument. Using 6 means all error values will be ignored. Other options are available as well but in a moment we will see that we need this precise behaviour.
- C — This expression constructs the array from which the n‑th smallest row number will be produced.
- D — The position from the array (that we just built in the previous point) that should be returned (1st, 2nd, 3rd, …). In this case, I have decided to use the value in the dedicated column but you can come up with a more sophisticated approach.
Teaching the function to only match our category criteria
Focusing on the formula in the C section we have:

- First, we take the row numbers of the Date column. This can be any column from the Expenses table and to be honest, I picked this one at random. Nevertheless, it will produce precisely the same result.
- Then the row number of the table header is subtracted. The result is the position (in the Expenses table) for every row of it (1,2,3,…).
This seems like being over-engineered way to get the sequence but it is the best way to do so without a separate column. The reason is that it works perfectly regardless of the location of the table on the sheet — you can move it 1000 rows down and the resulting numbers will start from 1 and go up nonetheless. - Then the values in the Category column are tested against the one for which we want to extract rows (indicated by the named range CatOnly_Category_Choice).
Expenses[Category]=CatOnly_Category_Choice
The comparison happens for every row of the table. It produces an array of TRUE/FALSE values. - The row number is divided by the TRUE/FALSE. In Excel division by FALSE is the same as division by zero. Needless to say, this would result in an error.
At this point:
- We have a total of 20 rows of expenses in the table.
- The formula would first generate the numbers from 1–20 to represent them.
- Then it will perform 20 tests of the values in the Category column. If the value in the cells is the one we have chosen on sheet Extract Cat Only, the result will be TRUE. Otherwise, the evaluation will be FALSE. 20 boolean outcomes will be available.
- Dividing the numbers from the 2nd point with the booleans from the 3rd will return:
- The row number generated in (2) — as division by TRUE is like dividing by 1;
- Or DIV/0 error because division by FALSE is attempted.
- A total of 20 number or error figures will be produced and they will be an array used by the =AGGREGATE() function.
Confusing? See it in action!
Open the section below to see what values are generated for each row if the Groceries category is selected.
Click to see how it all unfolds[1] Date | [2] Category | [3] Subcategory | [4] Amount | [5] Row # of the cell in Date | [6] Row # of the Category column header | [7] Position array = [5]-[6] | [8] Category is the one chosen | [9] Result array = [7]/[8] |
---|---|---|---|---|---|---|---|---|
16.6.2020 | Groceries | Bakery | 1,89 | 3 | 2 | 1 | TRUE | 1 |
16.6.2020 | Groceries | Dairy | 3,59 | 4 | 2 | 2 | TRUE | 2 |
16.6.2020 | Groceries | Meat | 6,99 | 5 | 2 | 3 | TRUE | 3 |
16.6.2020 | Groceries | Condiments | 1,99 | 6 | 2 | 4 | TRUE | 4 |
16.6.2020 | Groceries | Spirits | 10,99 | 7 | 2 | 5 | TRUE | 5 |
16.6.2020 | Restaurants | Lunch | 7,40 | 8 | 2 | 6 | FALSE | #DIV/0! |
16.6.2020 | Car | Fuel | 83,00 | 9 | 2 | 7 | FALSE | #DIV/0! |
17.6.2020 | Utilities | Electricity | 26,78 | 10 | 2 | 8 | FALSE | #DIV/0! |
17.6.2020 | Restaurants | Lunch | 9,00 | 11 | 2 | 9 | FALSE | #DIV/0! |
17.6.2020 | Clothing | All | 49,99 | 12 | 2 | 10 | FALSE | #DIV/0! |
17.6.2020 | Car | Accessories | 10,99 | 13 | 2 | 11 | FALSE | #DIV/0! |
17.6.2020 | Gifts | All | 31,70 | 14 | 2 | 12 | FALSE | #DIV/0! |
17.6.2020 | Restaurants | Dinner | 26,00 | 15 | 2 | 13 | FALSE | #DIV/0! |
18.6.2020 | Medical | Dentist | 140,00 | 16 | 2 | 14 | FALSE | #DIV/0! |
18.6.2020 | Groceries | Fish | 14,99 | 17 | 2 | 15 | TRUE | 15 |
18.6.2020 | Groceries | Bakery | 1,89 | 18 | 2 | 16 | TRUE | 16 |
18.6.2020 | Groceries | Spirits | 12,99 | 19 | 2 | 17 | TRUE | 17 |
18.6.2020 | Groceries | Dairy | 3,59 | 20 | 2 | 18 | TRUE | 18 |
18.6.2020 | Restaurants | Lunch | 6,80 | 21 | 2 | 19 | FALSE | #DIV/0! |
The =AGGREGATE() handles the errors
The =AGGREGATE() now has everything needed to do its magic. It has been instructed to:
- Take the newly constructed array of numbers and errors;
- Ignore the #DIV/0 because there is no useful information (matching the desired category) available on the respective row in the Expenses table.
- Then look through the leftover number values and find the smallest n‑th among them. If n=1 then this would be the lowest numerical — the very first row from the Expenses table that has our category of choice. N=2 would be the second and so on.
Combining with the =INDEX() function
Knowing which row from the Expenses table we need means that the =INDEX() function can be used to get its value. In the INDEX/MATCH post, there is detail how this happens.
When we are looking for the Groceries category the first row of data that will be fetched will be the first one from the Expenses table. However, switching to the Car category means that the first row for which the =AGGREGATE() will pass value to the =INDEX() function is the 7th.

Adding a second criteria
On sheet Extract Cat & Subcat we take it all one step further by introducing second condition — the subcategory of the spendings. The formula remains the same with one small change:

When testing every line of the Expenses table for the appropriate category (resulting in TRUE/FALSE) we multiply the result by another boolean test — this time for the subcategory of choice. If any test fails and produces FALSE as a result, the multiplication will return FALSE overall. This will cause the =AGGREGATE() function to drop the row as demonstrated above.
Why not use another solution? Pivots? Power Query? Plain filters?
Solving the task as described has one advantage — it employs the real-time calculations made by Excel.
- Pivots need to be refreshed to get the new data entered.
- Power Query calls for refresh due to the same reason.
- Filters will visualize the result at least equally fast but then the data won’t be so well prepared for further manipulation.
However, using =AGGREGATE() is only one way to extract the rows needed and it is not necessarily always the best. For once, it is taxing on the CPU and once your database grows, this might be a problem. Furthermore, building it would take more time and it will be wasted if your project is not intended for an extended period of use. Also, if you happen to enjoy pivot slicers, your visual preferences might not be satisfied.