by Charley Kyd
Many Excel users have a similar challenge. So I'm going to make my answer as general as possible.
After you read this, you might wonder if I also made it as long as possible. That certainly wasn't my intention. This will be a long response because there will be a lot to explain.
It certainly would be possible to write a macro to do what you ask. There are at least two problems with that approach. First, most Excel users should not become programmers; they have enough to do in their real jobs.
Second, user-written macros are difficult to maintain, particularly after the people who wrote them leave. So the following solution does not use macros.
Instead, I'll show you how to use dynamic range names and conditional formatting to cause Excel tables to change automatically in response to changing data.
(Our article Use Conditional Formatting To Manage Customer Tasks provides additional information about conditional formatting in Excel 2003 and Excel 2007.)
Depending on how you look at it, this could be good news or bad news. It's good news if you want the opportunity to learn more about some of Excel's most powerful and flexible features. It's bad news if you're looking for a one-minute solution.
Introducing the Sales Summary Report
The following figure shows a simplified version of the tracking log you sent me. I set it up in a new workbook in a worksheet I named Log.
The following two figures show where we're headed. Excel uses the data from the log to automatically generate a Sales Summary Report for any sales person whose name is entered in cell D1 of the report.
To be clear, when I entered "peter" in cell D1, Excel displayed the table shown in the range A3:D6 for Peter. And when I entered "alyson" in cell D1, Excel displayed the table shown in the range A3:D8 for Alyson.
Let's see how Excel performs this magic without using macros.
Create the Log
The first step is to create the log in a new workbook. The first figure above shows the result.
For tables like this I always include the shaded border rows shown in the figure. And I anchor all range names for the table in those empty border rows. Then, when I insert new rows of data I always do so between the shaded borders. By using this approach I always ensure that my formulas adapt correctly when we insert or delete rows in the database.
There aren't any formulas in the Log figure above. The Row numbers count downward from the value 2 in cell A4.
The text in row 2 of the Log table represents the range names that I wanted to assign. To assign that text as range names, select the range A2:E10 in the Log worksheet, and choose Formulas, Defined names, Create from Selection, or choose Ctrl+Shift+F3. In the Create Names from Selection dialog, make sure that only Top Row is checked, then choose OK.
Begin the Report
To begin the Report table, enter and format rows 1 through 4 in the Report table shown here:
Cell C1 contains the right-aligned text "CurSalesPerson". Assign this text as the name of cell D1. To do so, select the range C1:D1; choose Formulas, Defined names, Create from Selection, or choose Ctrl+Shift+F3; make sure that only Left Column is checked; and then choose OK.
Replace the text in cell D3 with the formula shown here for that cell:
Here, the PROPER function displays the sales person's name in upper-lower case. It makes the report better-looking, but provides no other value.
Enter the First Row Formula
We rely on Excel's MATCH function to return the row numbers that we need. This function takes the general form:
=MATCH(lookup_value, lookup_array, match_type)
Therefore, enter this formula for the cell shown:
This formula returns the index number for the first occurrence of "peter" within the SalesPerson range in the Log table shown first in this article. The formula returns the value 2, which corresponds with the Row number shown in the Log table. (The gray border row counts as Row 1.)
(In Excel, "row number" typically refers to a spreadsheet row. Excel uses "index" to refer to the rows within a range. In reports, however, people often refer to table rows without reference to the spreadsheet row that it might be on. Here, when I refer to Rows within the table -- which are the same as index rows -- I'll use "Row". Therefore, in the preceding figure, the 2 in cell A5 refers to Row 2 of the log table.)
The other formulas in row 5 use this index number (or "Row number") to return data from the Log table.
In cell A5, the zero value for the match_type argument tells Excel to look for an exact match. (This setting ignores the case of the text, however.) If MATCH finds the sales person's name in the SalesPerson range, it returns the Row number of the first instance of that name; otherwise it returns #N/A.
The #N/A result looks ugly in a report, and gives the false impression that we've experienced an unexpected problem. Therefore, we must change the formula to hide this result. Through Excel 2003, the way to do that is with an IF function. But Excel 2007 offers a more efficient approach.
In the old approach we would have:
=IF(ISERROR(my formula),"", my formula)
That is, if my formula generates an error, return a blank; otherwise, return the results of my formula.
But in Excel 2007 we can use the IFERROR function, which uses this form:
If there is no error, Excel returns the value. If there is an error, Excel returns the value_if_error.
Using this approach, you should change the formula to:
Add the Remaining Row Formulas
The formula in cell A6 is longer than the formula in cell A5. This is because the formula in cell A6 can't search the entire SalesPerson column. Instead, it must search the section of SalesPerson column that's below the instance we've already found in cell A5. To do so, the formula relies on the OFFSET function, which takes this general form:
=OFFSET(reference, rows, cols, height, width)
Here, rows and cols refer to the number of rows and columns that the new reference is offset from the specified reference. You'll see how this works in the next few paragraphs. In all cases in this example, cols will have a value of 0 (meaning that the reference doesn't shift left or right) and width will have a value of 1 (meaning that the final reference is one column wide).
To build the formula for cell A6, I started with the OFFSET function then wrapped the other parts of the formula around it. Let's do the same thing here. Enter this formula for the cell shown:
This formula will return a #VALUE! error, but that's okay. We'll fix it in a minute.
The reason we get this error is that the formula returns a reference to more than one cell and is trying to force that result into a one-cell space. That's like trying to force five gallons of water into a one-gallon can. It doesn't work.
Even so, let's see what this formula does. The formula starts with the SalesPerson range and shifts it downwards by the number of rows in cell A5. This will allow the MATCH function to begin its search in the cell immediately below the cell where the first sales person was found.
However, by shifting the reference to the SalesPerson range downwards, we have a reference that extends beyond the bottom of our source table. We therefore must reduce the height of the range that the OFFSET returns. The new height needs to equal the number of rows in SalesPerson less the number of rows we shifted downwards, which is the value in cell A5.
To check that the OFFSET function works correctly in cell A6, select the formula in your formula bar, press Ctrl+C to copy it, then press Esc to return to the Ready mode. Now press your F5 function key, or press Ctrl+G, or choose Home, Editing, Find & Select, Go to...all of which will launch the Go To dialog. Press Ctrl+V to paste the formula into the Reference edit box; then choose OK.
After you do so, Excel will select the range returned by the OFFSET function, which should be the range shown here:
The next step is to wrap the MATCH function around the OFFSET function.
Here, I've shown the formula in three lines, with the first and last line showing the additional parts that you add to the formula. Again, enter it as one line with no extra spaces.
When you enter this formula, you should see the value 4, as shown in the figure below.
Finally, for this cell, we need to wrap the formula with the IFERROR test to protect it from the #N/A problem.
Again, I displayed this formula in multiple lines so you could more easily see how its sections fit together. After you enter it in one line with no spaces, you should have the result for cell A6, shown here:
Copy this formula from cell A6 to the range A7:A15.
Enter the Other Formulas in the Report Table
The other formulas in the table are easy. Enter them for the cells shown:
As you can see, all of the heavy lifting was in column A. Once we know the index number for each row of data, we simply use the INDEX function to return the data we need to our report. Of course, if the cell in column A contains a null string ("") these formulas also return a null string.
Copy the formulas in the range B5:D5 to the range B6:B15. As a general rule, it's a good idea to copy the formulas to enough rows that will allow for future growth in your log. I chose to copy to row 15, but you could copy the rows even further.
Format the Table
We use a combination of standard and conditional formatting with the table.
For the standard format, we assign borders to the entire table. To do so, select the range A5:D15. Choose Home, Cells, Format, Format Cells, or choose Ctrl+1. In the Border tab, choose the border pattern shown here. Then choose OK.
To assign the conditional formatting, make sure that the range A5:D15 is still selected, and that cell A5 is the active cell. Then choose Home, Styles, Conditional Formatting, New Rule. In the New Formatting Rule dialog, choose "Use a formula to determine which cells to format".
We want the first conditional formatting to occur whenever we have blank cells in column A. That is we want it to occur whenever column A has a null string. Therefore, in the text box labeled "Format values where this formula is true" enter the formula:
Why do we use $A5 in this formula? Because the active cell is in row 5, and because there's no "$" sign in front of the 5, this reference causes each cell in the selected range to look in column A of its own row.
We now need to specify the format to use. To do so, choose Format in the New Formatting Rule dialog. In the Border tab, remove all borders so that the Border setup area looks like this. Then choose OK. And then choose OK again.
With range A5:D15 still selected, and with cell A5 as the active cell, return to the New Formatting Rule dialog to add a second condition. Again, choose "Use a formula to determine which cells to format", and then enter this formula:
Because the active cell is in row 5, this formula says to apply the conditional formatting if the cell in column A of the current row contains actual data, and if the next row down in column A contains no data. That is, apply this format if the current row is the last row of the table.
Again, choose Format and then define the border setting shown here.
After you choose OK, and then OK again. You can review your conditional formats, To do so, choose Home, Styles, Conditional Formatting, Manage Rules. When you do so, you'll see a dialog like this:
Optional Error Test
This solution relies on our having enough formulas in the Report sheet to display all instances of the specified sales person. Therefore, it's a good idea to be alerted when we run out of room and need to copy some more formulas to display a larger Report table.
The easiest way to do so is to replace the formula in cell D3 with one that provides an error message if there aren't enough rows for the table. The error message would appear in cell D3, as shown here:
Here's the logic of the test: If the count of the sales person's name in the Log equals the count of the row numbers in the Report, return the sales person's name; otherwise return an error message.
To get the count of the sales person's name we use the COUNTIF function, which has this general form:
Specifically, to count the number of times that the current sales person's name appears in the SalesPerson column of the log, we would use this formula:
The rows of data in the Report table should have exactly this number of rows. Therefore, we can construct an OFFSET formula that returns a reference to the cells in column A that should contain row numbers:
That is, beginning in cell A4, move down one row and return a reference that returns the number of rows we need. As before, the formula at this stage will return a #VALUE! error.
We can find the number of rows of data in the Report table with the COUNT function:
This value should be equal to the COUNTIF result. If it is, we return the current salesperson's name; if it isn't, we return an error. So here's our final formula:
As always, enter the formula in one line without extra spaces.