Forum Discussion
Help on Project - VBA or Macros
Hello there! I have tried several things and I am getting stuck on the best way to get the results that I need.
I have a large spreadsheet, columns A - CR
Each row list out one expense item and has information based on that item filled into columns thru CR.
Finance is wanting a report that if there are 5 expense items (Column BN) for $20 dollars (Column CM) for the Same GL Codes ( GL Codes have 5 sections - Column CC thru Column CG), then I need to condense it to one row with the one amount of $100.
I can sort the rows by Amount, then sort by GLCodes to group them together.
All the information in columns A-BG is the same for each row.
I have attached a pic of a smaller version of the information of what it looks it.
In the pic - it will be column T (Amt), column V-Z (GL Code sections).
I have tried several different things of Grouping, Subtotal to get the total of the same rows when the Amt changes or when the GL Code changes. I having issues getting it work when the Amt and GL Code changes.
And then how to copy/paste all the other information from the row into a total row based on the row above it.
Maybe I am looking at this all wrong and there is a better way of doing this.
Any help would be appreciated.
Thank you!
Dawn
dfuhrman840 I'm not going to bother attempting the conditional formatting, as I have a solution (code is below).
The examples you posted on Nov. 18 were useful. I did not initially see your response, hence the delay. But your text brings up one more question: If the Expense Amt values are different, but the Expense # and GL Codes are identical, should the rows be condensed? My code currently does not include Expense Amt in the decision to condense, only in the accumulation ... but that's easy to change.
In the absence of direct answers to my questions, I derive/assume:
- In the interest of safety, my solution creates a new worksheet.
- Yes, you want the individual rows deleted (after aggregation into one row).
- not applicable
- no longer care
- Amt did refer to a monetary value.
- I still don't have an answer for this. See below.
- If there are data discrepancies in columns B-D between rows that have the same values in columns A and F-L, I will issue a warning, highlight the different data in the lower row, and not condense the row.
To make question 6 clearer, consider the two rows shown in the picture. If the Expense # values for those two rows are the same, should the rows be condensed? (That would be more difficult to do.) Or is that condition so improbable that the code need not be concerned with it?
As I noted before, you are not required to sort the rows before running my procedure.Sub CondenseExpenseItems() ' This procedure: ' 1 - copies a user-specified worksheet to a new worksheet, ' 2 - adds a column of data (the original row number) ' 3 - scans through the rows, condensing "duplicates" as identified ' in columns A and F-L, and summing values in column E (exceptions ' are made if differing data is identified in columns B-D, and ' a warning is given) ' 4 - displays a summary message ' The relevant workbook must be open when this procedure is invoked; it ' does not have to be the active workbook. Const PROCEDURE_NAME = "CondenseExpenseItems" Const in4LAST_HEADER_ROW As Long = 1 'If header rows are added or _ removed, update this value. Const strCOLUMN_FOR_ORIG_ROW_NUM = "M" Dim strActivity As String 'what the code is doing Dim strMessage As String 'message to the user Dim in4UserResponse As VbMsgBoxResult Dim blnCancelProcessing As Boolean Dim strWorkbookName As String Dim objWorkbook As Workbook Dim strOrigWkshtName As String Dim strNewWkshtName As String Dim objWorksheet As Worksheet Dim in4OrigLastDataRow As Long Dim in4LastDataRow As Long 'NOTE: Once row condensing starts, this _ value will decrease. Dim in4Row As Long Dim strRow As String Dim in4AggRow As Long 'row being checked for aggregation Dim blnDiscrepancyFound As Boolean Dim blnAggregationOccurred As Boolean Dim in4RowsWithAccum As Long Dim in4RowsDeleted As Long Dim vntExpenseNum As Variant Dim vntExpenseAmt As Variant Dim vntGLCode1 As Variant Dim vntGLCode2 As Variant Dim vntGLCode3 As Variant Dim vntGLCode4 As Variant Dim vntGLCode5 As Variant Dim vntGLCode6 As Variant Dim vntGLCode7 As Variant '---- On Error GoTo CondenseItems_ErrHndlr '---- PHASE 1: Determine the workbook and worksheet to be edited. strActivity = "Workbook selection" strMessage = "Which Excel workbook should be used?" strWorkbookName = InputBox(strMessage, PROCEDURE_NAME, ActiveWorkbook.Name) If strWorkbookName = "" Then Exit Sub ' On Error Resume Next Set objWorkbook = Application.Workbooks(strWorkbookName) On Error GoTo CondenseItems_ErrHndlr If objWorkbook Is Nothing Then strMessage = "Workbook " & strWorkbookName _ & " is not open/cannot be used." Call MsgBox(strMessage, vbCritical Or vbOKOnly, PROCEDURE_NAME) Exit Sub End If objWorkbook.Activate ' -- strActivity = "Worksheet selection" strMessage = "Which worksheet should be used?" strOrigWkshtName = InputBox(strMessage, PROCEDURE_NAME, ActiveSheet.Name) If strOrigWkshtName = "" Then GoTo CondenseItems_Exit ' On Error Resume Next Set objWorksheet = objWorkbook.Sheets(strOrigWkshtName) On Error GoTo CondenseItems_ErrHndlr If objWorksheet Is Nothing Then strMessage = "Worksheet " & strOrigWkshtName _ & " is not present." Call MsgBox(strMessage, vbCritical Or vbOKOnly, PROCEDURE_NAME) Exit Sub End If ' -- Make a copy of the original worksheet. strActivity = "Copying of worksheet" strNewWkshtName = strOrigWkshtName & "_A" objWorksheet.Copy After:=objWorksheet Set objWorksheet = objWorkbook.Sheets(strOrigWkshtName & " (2)") objWorksheet.Name = strNewWkshtName objWorksheet.Activate DoEvents '---- PHASE 2: Add a column containing row numbers, counting populated ' rows as you go. strActivity = "Storing of original row numbers" Application.Cursor = xlWait Application.ScreenUpdating = False blnCancelProcessing = False With objWorksheet For in4Row = in4LAST_HEADER_ROW + 1 To 1048576# strRow = CStr(in4Row) If .Range("A" & strRow).Value = "" Then '...the end of the data rows has been found. Exit For ElseIf .Range(strCOLUMN_FOR_ORIG_ROW_NUM & strRow).Value <> "" Then strMessage = "Content was found in a cell that was " _ & "expected to be empty: " & strCOLUMN_FOR_ORIG_ROW_NUM _ & strRow Call MsgBox(strMessage, vbCritical Or vbOKOnly, PROCEDURE_NAME) GoTo CondenseItems_Exit End If .Range(strCOLUMN_FOR_ORIG_ROW_NUM & strRow).Value = in4Row in4LastDataRow = in4Row Next in4Row End With in4OrigLastDataRow = in4LastDataRow '---- PHASE 3: Scan the rows, condensing "duplicates" into the ' aggregation row (the first-encountered among the "duplicates"). strActivity = "Scanning rows" in4AggRow = in4LAST_HEADER_ROW + 1 With objWorksheet Do While in4AggRow < in4LastDataRow ' -- Capture values that identify aggregation sets, and ' the amount(s) to be accumulated. strRow = CStr(in4AggRow) blnAggregationOccurred = False vntExpenseNum = .Range("A" & strRow).Value vntGLCode1 = .Range("F" & strRow).Value vntGLCode2 = .Range("G" & strRow).Value vntGLCode3 = .Range("H" & strRow).Value vntGLCode4 = .Range("I" & strRow).Value vntGLCode5 = .Range("J" & strRow).Value vntGLCode6 = .Range("K" & strRow).Value vntGLCode7 = .Range("L" & strRow).Value vntExpenseAmt = .Range("E" & strRow).Value ' -- Scan for "duplicates" (from the bottom up). For in4Row = in4LastDataRow To in4AggRow + 1 Step -1 strRow = CStr(in4Row) strActivity = "Scanning rows" If vntExpenseNum = .Range("A" & strRow).Value _ And vntGLCode1 = .Range("F" & strRow).Value _ And vntGLCode2 = .Range("G" & strRow).Value _ And vntGLCode3 = .Range("H" & strRow).Value _ And vntGLCode4 = .Range("I" & strRow).Value _ And vntGLCode5 = .Range("J" & strRow).Value _ And vntGLCode6 = .Range("K" & strRow).Value _ And vntGLCode7 = .Range("L" & strRow).Value _ Then '...we've found a "duplicate" ' -- Check for a discrepancy (inconsistency) in data ' between this row and the aggregation row. blnDiscrepancyFound = False If .Range("B" & strRow).Value <> .Range("B" & in4AggRow).Value Then blnDiscrepancyFound = True .Range("B" & strRow).Interior.Color = vbYellow End If If .Range("C" & strRow).Value <> .Range("C" & in4AggRow).Value Then blnDiscrepancyFound = True .Range("C" & strRow).Interior.Color = vbYellow End If If .Range("D" & strRow).Value <> .Range("D" & in4AggRow).Value Then blnDiscrepancyFound = True .Range("D" & strRow).Interior.Color = vbYellow End If If blnDiscrepancyFound Then strMessage = "Discrepant data was found in original rows " _ & .Range("M" & CStr(in4AggRow)).Value & " and " _ & .Range("M" & strRow).Value in4UserResponse = MsgBox(strMessage, vbExclamation _ Or vbOKCancel, PROCEDURE_NAME) If in4UserResponse = vbCancel Then blnCancelProcessing = True Exit For End If GoTo CompareNextDataRow End If ' -- Condense this row. strActivity = "Condensing rows" blnAggregationOccurred = True ' Mark the row. .Range(strCOLUMN_FOR_ORIG_ROW_NUM & strRow).Value = _ .Range(strCOLUMN_FOR_ORIG_ROW_NUM & strRow).Value * -1 ' Accumulate, and update the aggregation row. vntExpenseAmt = vntExpenseAmt + .Range("E" & strRow).Value .Range("E" & in4AggRow).Value = vntExpenseAmt ' Delete the row. .Range("A" & strRow).EntireRow.Delete Shift:=xlShiftUp in4LastDataRow = in4LastDataRow - 1 in4RowsDeleted = in4RowsDeleted + 1 End If CompareNextDataRow: Next in4Row ' -- If blnAggregationOccurred Then in4RowsWithAccum = in4RowsWithAccum + 1 End If If blnCancelProcessing Then Exit Do End If in4AggRow = in4AggRow + 1 Loop End With '---- PHASE 4: Display a summary message. strMessage = Format$(in4RowsWithAccum + in4RowsDeleted, "#,###,##0") _ & " rows were condensed into " _ & Format$(in4RowsWithAccum, "#,###,##0") & " rows." Call MsgBox(strMessage, vbInformation Or vbOKOnly, PROCEDURE_NAME) CondenseItems_Exit: Application.ScreenUpdating = True Application.Cursor = xlDefault If Not objWorksheet Is Nothing Then objWorksheet.Activate End If Set objWorksheet = Nothing Set objWorkbook = Nothing Exit Sub CondenseItems_ErrHndlr: Dim in4ErrorCode As Long Dim strErrorDescr As String ' -- Capture info. in4ErrorCode = Err.Number strErrorDescr = Err.Description ' -- strMessage = "Error " & in4ErrorCode & " occurred during " _ & strActivity & ":" & vbCrLf & strErrorDescr & vbCrLf & vbCrLf _ & strWorkbookName & " " & strNewWkshtName & " " & CStr(in4AggRow) Call MsgBox(strMessage, vbCritical Or vbOKOnly, PROCEDURE_NAME) ' -- Application.ScreenUpdating = True Resume CondenseItems_Exit Resume 'inaccessible, but retained for debugging End Sub
I had some trouble getting the new worksheet to appear automatically. I think I got that fixed, but if the new worksheet does not appear, click on an existing worksheet's tab.
11 Replies
- SnowMan55Bronze Contributor
dfuhrman840 Clarification is needed:
- Are you looking to have the results of the worksheet modified, or are you instead looking to have a new worksheet created?
- You wrote "condense it [presumably, that set of expense items]" rather than "summarize it". Do you intend to have the individual rows for a set deleted once the new row is created and populated?
- If the answer to #2 is "no", how are the original rows in a set to be distinguished from the summarized rows? Are the original rows to be modified?
- What is in column BN? Is that an identifier for expense item type? Or is it a quantity? Or something else?
- When you write "Amt", is this the monetary value? Or some other quantity?
- Will any particular set of five GL Code values among the rows always appear in the same left-to-right order? (If "no", the comparisons between rows are significantly more complicated.)
- "All the information in columns A-BG is the same for each row." I presume you mean "...for each row in a set". So if columns BH-BM, BO-CB, and CH-CR may not be the same for each row in a set, how (if at all) are those columns to be populated in the new summarized row?
BTW, while you have sorted the rows to make the related expense items adjacent (and that's natural if you were to do this work manually), sorting is not required to identify and summarize the sets.
- dfuhrman840Copper Contributor
SnowMan55 - sorry for the confusion.
I have added two screenshots. I condensed down my spreadsheet to just the information that I am trying to work on.
The first spreadsheet is how the information comes in.
The second spreadsheet is how I need to send it to finance. I was looking for a way of cutting down alot of the manual process.
Right now, on the first spreadsheet, I do a custom level sort - expense #, then Expense Amt, then GLCode1, then GLCode2, then GLCode 3, then GLCode 4, then GLCode 5, then GLCode 6, then GLCode 6. I then add a row where the GL Code 5 changes. Grouping the same expense item & Amt together that has all the same codes. If there are multiple charges, then I have to add up those amounts and I replace the first amt with the total amount and I delete the duplicate rows.
These expense reports could be 50 items or they could be 900 items. I never know.
The second spreadsheet is what it looks like after I do all the manual changing.
- dfuhrman840Copper Contributor
@SnowMan55 I have even tried to do a conditional formatting to highlight duplicate rows and I can not get it highlight my duplicate rows.