Excel loop condition based concatenation [duplicate] - vba

This question already has answers here:
PowerQuery: How can I concatenate grouped values?
(3 answers)
Closed 4 years ago.
I am very new to excel macros and i need your help to fix one of my condition based concatenation problem.
i will explain the problem with simple scenario in below:
In my sheet , Column A contains customer name and Column B contains country names. Attached excel screenprint for reference ( column C and Column D will be my expected results)
In the column A, single customer name can be repeated as he can have multiple country representations
In the column B, countries placed as shown in the screenprint.
My expected results will be look alike in the column C and D as shown in the image.
I can do the column C using INDEX and i am able to get the unique values from column A
For the column D ,i am expecting the results in such a way that all countries will be concatenated and separated by ' / ' based on the corresponding customer in column A. I tried some vlookups and indexes, but i am unable
to do it.
it would be really helpful if you could provide any suggestions(function/Macros) how it will be achieved.

I am a lower intermediate vba user, so I will admit that I am sure someone can do this better than , however, this works. Add a button and then click on it, or add this to the worksheet and it will occur whenever you choose for it to be fired:
Option Explicit
Sub listout()
'declare your variables
Dim wbk As Workbook
Dim ws1 As Worksheet
Dim cprange As Range
Dim rmrange As Range
Dim bottomRow As Long
Dim row As Range
Dim countname As Variant
Dim copyname As Variant
Dim nametoRow As Long
'speed up process
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
'set what the variables are
Set wbk = ThisWorkbook
Set ws1 = wbk.Worksheets("Names List")
bottomRow = ws1.Range("A1").End(xlDown).row
'get ird of any excisting values
ws1.Range("C1:D100").ClearContents
'Set the range of the names that you want to copy, and put them into column C
Set cprange = ws1.Range(Range("A1"), Range("A1" & bottomRow))
ws1.Range(Range("C1"), Range("C1" & bottomRow)) = cprange.Value
'then remove all the duplicates
Set rmrange = ws1.Range(Range("C1"), Range("C1" & bottomRow))
rmrange.RemoveDuplicates Columns:=1, Header:=xlNo
'redclare the range as it will be shorter because you got rid of load sof duplicates
Set rmrange = ws1.Range(Range("C1"), Range("C1").End(xlDown))
'loop though each name in the 'unique' list and loop through their names in the original data then add the country to their new location in column D
For Each copyname In rmrange
For Each row In cprange
nametoRow = ws1.Application.WorksheetFunction.Match(copyname, rmrange, False)
countname = row.Offset(0, 1)
If row.Value = copyname Then
If Trim(ws1.Range("D" & nametoRow) & vbNullString) = vbNullString Then
ws1.Range("D" & nametoRow) = countname
Else
ws1.Range("D" & nametoRow) = ws1.Range("D" & nametoRow) & "/ " & countname
End If
End If
Next row
Next copyname
'turn these back on otherwise it messes with your computer/excel
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub

Here is a more efficient method.
Advanced Filter to remove duplicates from Col A, paste on Col C
Set necessary ranges
Loop through each unique name
Build String
Paste String
Loop 4 - 6 until complete
Assumptions/Actions: You have headers on Col A, B, C, & D. If you have duplicate countries for a person, the country will show up twice on the string.You will need to change "Sheet1" to your sheet name on the 3rd line.
Usually you would need to check if your value is found using the .Find method, but the below logic does not allow for a cell to not be found as it is looping through values determined by filter. It wouldn't make since for a filtered object to not be found in the range it came from.
Option Explicit
Sub CountryList()
Dim ws As Worksheet: Set ws = ThisWorkbook.Sheets("Sheet1")
Dim FoundCell As Range, SearchRange As Range, Names As Range, SearchCell As Range
Dim MyString As String, i As Long
Set SearchRange = ws.Range("A2:A" & ws.Range("A" & ws.Rows.Count).End(xlUp).Row)
SearchRange.AdvancedFilter Action:=xlFilterCopy, CopyToRange:=ws.Range("C2"), Unique:=True
ws.Range("C2").Delete Shift:=xlShiftUp
Set Names = ws.Range("C2:C" & ws.Range("C" & ws.Rows.Count).End(xlUp).Row)
For Each SearchCell In Names
Set FoundCell = SearchRange.Find(SearchCell)
For i = 1 To Application.WorksheetFunction.CountIf(SearchRange, SearchCell)
MyString = MyString & FoundCell.Offset(, 1) & "/"
Set FoundCell = SearchRange.FindNext(FoundCell)
Next i
SearchCell.Offset(, 1) = Left(MyString, Len(MyString) - 1)
MyString = ""
Next SearchCell
End Sub

Related

How To Have VBA Insert Formula Result as a Value

I got help last week getting my syntax and ranges correct, and thought I could just do a vlookup to finish it but apparently I was mistaken. It just seems like when I try to research how to accomplish this, I find various examples but I don't have the background to translate it to my code.
The macro runs and does almost everything its supposed to do. But in addition to inserting the arrays, there are 3 other cells that need values when there are blank cells in my ‘sourcerng’.
This is the logic for the cells that need values (the values are already in my worksheet, I just need to get them to these blank cells). I tried to do an IIF statement for these but I still have no idea what I'm doing. Would that be the best way? Should it just be another IF THEN statement?
rngBE - IF Column Z = 0 Then copy value from corresponding row in column O. Otherwise copy value from column Z
rngBG - IF Column AA = "Unknown" Then copy value from corresponding row in column I. Otherwise copy value from column AA.
rngBK - IF Column AB = "Unknown" Then copy value from corresponding row in column N. Otherwise copy value from column AB.
Sub AutomateAllTheThings6()
Dim arr3() As String
Dim arr11() As String
'Dim resBE As String
Dim rng3 As Range
Dim rng11 As Range
Dim rngBE As Range
Dim rngBG As Range
Dim rngBK As Range
Dim sourcerng As Range
'Dim firstRow As Long
Dim lastRow As Long
'Dim i As Long
Call OptimizeCode_Begin
'firstRow = 2
lastRow = ActiveSheet.Range("D1").End(xlDown).Row
Set rng3 = ActiveSheet.Range("BH2:BJ" & lastRow)
Set rng11 = ActiveSheet.Range("BL2:BV" & lastRow)
Set rngBE = ActiveSheet.Range("BE2:BE" & lastRow)
Set rngBG = ActiveSheet.Range("BG2:BG" & lastRow)
Set rngBK = ActiveSheet.Range("BK2:BK" & lastRow)
Set sourcerng = ActiveSheet.Range("BE2:BE" & lastRow)
arr3() = Split("UNKNOWN,UNKNOWN,UNKNOWN", ",")
arr11() = Split("UNKNOWN,UNKNOWN,UNKNOWN,UNKNOWN,UNKNOWN,UNKNOWN,00/00/0000, _
00/00/0000,00/00/0000,00/00/0000,NEEDS REVIEW", ",")
For Each cell In sourcerng
If IsEmpty(cell) Then
Intersect(rng3, ActiveSheet.Rows(cell.Row)).Value = arr3
Intersect(rng11, ActiveSheet.Rows(cell.Row)).Value = arr11
'***PLS HELP***
Intersect(rngBE, ActiveSheet.Rows(cell.Row)).Value = "WEEEEE"
Intersect(rngBG, ActiveSheet.Rows(cell.Row)).Value = "WOOOOO"
Intersect(rngBK, ActiveSheet.Rows(cell.Row)).Value = "WAAAAA"
End If
Next
Range("BR2:BU2").Select
Range(Selection, Selection.End(xlDown)).Select
Selection.NumberFormat = "mm/dd/yyyy"
Columns("BF:BF").Select
Selection.Delete Shift:=xlToLeft
Call OptimizeCode_End
End Sub
'*********TESTING***********
'resBE = IIf(Cells(13,Z).Value = 0, Cells(13,BE).Value = Cells(13,Z), Cells(13,BE).Value = Cells(13,O))
'***************************************
'For i = firstRow To lastRow
' valZ = Range("Z" & i)
' valOh = Range("O" & i)
'
' If valZ = 0 Then
' rngBE.Value = valOh
' Else rngBE.Value = valZ
' End If
There are several ways to do your task. If you're more of an "Excel" person than VBA you might consider this approach: You can inject the syntax of any "regular" formula in R1C1 Format.
So the formula mentioned above =if($Z2=0,$O2,$Z2) is .FORMULA format for any value in row 2.
But in .FORMULAR1C1 it can be inserted in ANY cell as: =IF(RC26=0,RC15,RC26) (basically no rows up or down, but always columns O (15) and Z(26).
So, your modified code would have something like this:
Intersect(rngBE, ActiveSheet.Rows(cell.Row)).FormulaR1C1 = "=IF(RC26=0,RC15,RC26)"
Intersect(rngBE, ActiveSheet.Rows(cell.Row)).Value = _
Intersect(rngBE, ActiveSheet.Rows(cell.Row)).Value
Again, this is NOT the most efficient way to accomplish your task, but if you're dealing with thousandsof rows, versus tens to hundreds of thousands, I wouldn't worry about it and it gives you a new tool to use.

Find last row and sum more than one column

I am creating a new macro to enable a custom report from a download we use bi-weekly.
I recorded the macro using one of the downloads, This has given me everything I need, except for my last piece.
I need to find the last row, go one row down, and sum columns J, K, L, and M. Then in column "I" the word "Total" should be on that same row.
To be clear, I want to sum column J from J2:Jxxx, where xxx is the last row.
Each time we download this report, the number of lines will vary, so I cannot use static row numbers as part of the formula.
I need to know how to write this, I have searched several forums and Excel sites to get this, but nothing has worked. Also, can this be done in such a way that one set of code will cover all the columns, or will it have to be repeated for each column?
Here is the code I have (keep in mind this is now a hodge-podge from trying out various helps I found on-line throughout the day):
EndRowI = Range(I65536).End(xlUp).Row
.Sheets(x).Range("I" & EndRowI + 1).Formula = "=SUM(I2:I" & EndRowI & ")"
EndRowH = Range("H" & Rows.Count).End(xlUp).Row
Range("H" & LR + 1).FormulaR1C1 = "Total"
For what it's worth, the name of my sheet is "combined" which is an earlier step in my macro.
Thank you!
*Please correct your worksheet name as my example refers to Sheet1 in ThisWorkbook
This code finds the biggest row number of J:M columns range and then sums each column and shows them at that biggest row number with their TOTAL title in H column. (As your question was not so clear I tried to figure out this based on your comments.)
Option Explicit
Sub SubUntilLastRow()
Dim CurCal As XlCalculation
Dim wb As Workbook, ws As Worksheet, colsLastRow As Long
Dim cols As Variant, SumCols As Long, colsArray As Variant
Dim biggestRow As Long
Application.ScreenUpdating = False
CurCal = Application.Calculation
Application.Calculation = xlCalculationManual
biggestRow = 1
Set wb = ThisWorkbook
Set ws = wb.Sheets("Sheet1")
colsArray = Array("J", "K", "L", "M")
For Each cols In colsArray
colsLastRow = ws.Cells(Rows.Count, cols).End(xlUp).Row
If colsLastRow > biggestRow Then
biggestRow = colsLastRow + 1
End If
Next cols
For Each cols In colsArray
colsLastRow = ws.Cells(Rows.Count, cols).End(xlUp).Row
ws.Cells(biggestRow, cols).Formula = "=SUM(" & cols & "2:" & cols & colsLastRow & ")"
Next cols
ws.Range("H" & biggestRow).Value = "TOTAL"
Application.ScreenUpdating = True
Application.Calculation = CurCal
End Sub

Create a VBA macro that Find and Copy?

I need a little bit help with a macro of Excel.
I need to create a macro that automatically find users and copy the values that i have in an other Sheet:
I have one sheet with values that contains the Users and their Kills and Deaths, I create 3 sheets more (3 different groups of users), and I need that the macro copy values automatically finding the users and copying values.
Images to describe it better:
----(Copy this values on)----->
You don't need a macro for this, using the worksheetfunction VLOOKUP is sufficient.
As an example, if you have your headers in row 1 and users in column A, what you'd put into cell B2 (the number of kills for the first user) would be =VLOOKUP($A2;Values!$A$2:$C$9;2;FALSE) and C2 would be =VLOOKUP($A2;Values!$A$2:$C$9;3;FALSE).
The arguments for the function (which you can also find in the linked document) is:
First, the value you're looking for, in your case whatever is in A2
Next the array of values which you want to return a result from - vlookup will only look through the first column, but since you want to return results from the other columns we include columns A:C in the formula.
What column in the range you search to return the result from for kills it is column 2, for deaths column 3.
Finally whether you want to have an exact match (false) or if an approximate one is ok (true).
If I understand what you're after, you should be able to do this with VLOOKUPs
(No VBA necessary)
The following source code solve your problem.
Option Explicit
Dim MyResultWorkbook As Workbook
Dim ValuesWorksheet As Worksheet
Dim SniperWorksheet As Worksheet
Dim ARsWorksheet As Worksheet
Sub CopyResult()
Set MyResultWorkbook = ActiveWorkbook
Set ValuesWorksheet = MyResultWorkbook.Sheets("Values")
Set SniperWorksheet = MyResultWorkbook.Sheets("Sniper")
Set ARsWorksheet = MyResultWorkbook.Sheets("Ars")
Dim SniperLastRow As Long
Dim ARLastRow As Long
Dim RowPointer As Long
Dim ValuePointer As Long
ValuePointer = 2
'Update the Sniper worksheets
SniperLastRow = SniperWorksheet.Cells(SniperWorksheet.Rows.Count, "A").End(xlUp).Row
For RowPointer = 2 To SniperLastRow
Do While (SniperWorksheet.Range("A" & RowPointer).Value <> ValuesWorksheet.Range("A" & ValuePointer).Value)
ValuePointer = ValuePointer + 1
Loop
SniperWorksheet.Range("A" & RowPointer).Offset(0, 1).Value = ValuesWorksheet.Range("A" & ValuePointer).Offset(0, 1).Value 'copy kill
SniperWorksheet.Range("A" & RowPointer).Offset(0, 2).Value = ValuesWorksheet.Range("A" & ValuePointer).Offset(0, 2).Value 'copy death
ValuePointer = 2
Next
'Update the Ars worksheets
ARLastRow = ARsWorksheet.Cells(ARsWorksheet.Rows.Count, "A").End(xlUp).Row
For RowPointer = 2 To ARLastRow
Do While (ARsWorksheet.Range("A" & RowPointer).Value <> ValuesWorksheet.Range("A" & ValuePointer).Value)
ValuePointer = ValuePointer + 1
Loop
ARsWorksheet.Range("A" & RowPointer).Offset(0, 1).Value = ValuesWorksheet.Range("A" & ValuePointer).Offset(0, 1).Value 'copy kill
ARsWorksheet.Range("A" & RowPointer).Offset(0, 2).Value = ValuesWorksheet.Range("A" & ValuePointer).Offset(0, 2).Value 'copy death
ValuePointer = 2
Next
End Sub

How to compare two columns in different sheets

I have one excel file with multiple sheets.
I need to compare two sheets (1) TotalList and (2) cList with more than 25 columns, in these two sheets columns are same.
On cList the starting row is 3
On TotalList the starting row is 5
Now, I have to compare the E & F columns from cList, with TotalList E & F columns, if it is not found then add the entire row at the end of TotalList sheet and highlight with Yellow.
Public Function compare()
Dim LoopRang As Range
Dim FoundRang As Range
Dim ColNam
Dim TotRows As Long
LeaData = "Shhet2"
ConsolData = "Sheet1"
TotRows = Worksheets(LeaData).Range("D65536").End(xlUp).Row
TotRows1 = Worksheets(ConsolData).Range("D65536").End(xlUp).Row
'TotRows = ThisWorkbook.Sheets(LeaData).UsedRange.Rows.Count
ColNam = "$F$3:$F" & TotRows
ColNam1 = "$F$5:$F" & TotRows1
For Each LoopRang In Sheets(LeaData).Range(ColNam)
Set FoundRang = Sheets(ConsolData).Range(ColNam1).Find(LoopRang, lookat:=xlWhole)
For Each FoundRang In Sheets(ConsolData).Range(ColNam1)
If FoundRang & FoundRang.Offset(0, -1) <> LoopRang & LoopRang.Offset(0, -1) Then
TotRows = Worksheets(ConsolData).Range("D65536").End(xlUp).Row
ThisWorkbook.Worksheets(LeaData).Rows(LoopRang.Row).Copy ThisWorkbook.Worksheets(ConsolData).Rows(TotRows + 1)
ThisWorkbook.Worksheets(ConsolData).Rows(TotRows + 1).Interior.Color = vbYellow
GoTo NextLine
End If
Next FoundRang
NextLine:
Next LoopRang
End Function
Please help with the VBA code.
Thanks in advance...
First I am going to give some general coding hints:
set Option Explicit ON. This is done through Tools > Options >
Editor (tab) > Require Variable Declaration . Now you HAVE to
declare all variables before you use them.
always declare a variables type when you declare it. If you are unsure about what to sue or if it can take different types (not advisable!!) use Variable.
Use a standard naming convention for all your variables. Mine is a string starts with str and a double with dbl a range with r, etc.. So strTest, dblProfit and rOriginal. Also give your variables MEANINGFUL names!
Give your Excel spreadsheets meanigful names or captions (caption is what you see in excel, name is the name you can directly refer to in VBA). Avoid using the caption, but refer to the name instead, as users can change the caption easily but the name only if they open the VBA window.
Ok so here is how a comparison between two tables can be done with your code as starting point:
Option Explicit
Public Function Compare()
Dim rOriginal As Range 'row records in the lookup sheet (cList = Sheet2)
Dim rFind As Range 'row record in the target sheet (TotalList = Sheet1)
Dim rTableOriginal As Range 'row records in the lookup sheet (cList = Sheet2)
Dim rTableFind As Range 'row record in the target sheet (TotalList = Sheet1)
Dim shOriginal As Worksheet
Dim shFind As Worksheet
Dim booFound As Boolean
'Initiate all used objects and variables
Set shOriginal = ThisWorkbook.Sheets("Sheet2")
Set shFind = ThisWorkbook.Sheets("Sheet1")
Set rTableOriginal = shOriginal.Range(shOriginal.Rows(3), shOriginal.Rows(shOriginal.Rows.Count).End(xlUp))
Set rTableFind = shFind.Range(shFind.Rows(5), shFind.Rows(shFind.Rows.Count).End(xlUp))
booFound = False
For Each rOriginal In rTableOriginal.Rows
booFound = False
For Each rFind In rTableFind.Rows
'Check if the E and F column contain the same information
If rOriginal.Cells(1, 5) = rFind.Cells(1, 5) And rOriginal.Cells(1, 6) = rFind.Cells(1, 6) Then
'The record is found so we can search for the next one
booFound = True
GoTo FindNextOriginal 'Alternatively use Exit For
End If
Next rFind
'In case the code is extended I always use a boolean and an If statement to make sure we cannot
'by accident end up in this copy-paste-apply_yellow part!!
If Not booFound Then
'If not found then copy form the Original sheet ...
rOriginal.Copy
'... paste on the Find sheet and apply the Yellow interior color
With rTableFind.Rows(rTableFind.Rows.Count + 1)
.PasteSpecial
.Interior.Color = vbYellow
End With
'Extend the range so we add another record at the bottom again
Set rTableFind = shFind.Range(rTableFind, rTableFind.Rows(rTableFind.Rows.Count + 1))
End If
FindNextOriginal:
Next rOriginal
End Function

Copying multiple cells in same row based on multiple criteria

Background: I have an Excel file used for tracking credit card payables. There are 18 columns of data (A through R). Out of these 18 columns, I want to use a macro to filter for specific statement date and then for a specific company code.
Each company code will be assigned a new worksheet. In each of these worksheets, I want to bring over specific cells from the master worksheet based on the criteria. For instance, the macro should first sort for statement date (7/31/2012) and then company code (ABC). Then, I need to run a loop to bring over details. For instance, in the master worksheet, the GL code in column P needs to be copied to the "ABC" worksheet in column H.
Here's a summary of what needs to happen:
1. Clear any filters in filter range (A2:R2)
2. Filter for date in cell A1 on "Master" worksheet beginning in cell A3 (date column)
3. Filter for company code (ABC) in column O
That should give a data set for particular company's statement activity. Here's what needs to happen next:
4. Copy Column P cell values in "master" worksheet to Column C in "ABC" worksheet
5. Copy Column N cell values in "master" worksheet to Column D in "ABC" worksheet
6. Copy Column R cell values in "master" worksheet to Column H in "ABC" worksheet
7. Copy Column F cell values in "master" worksheet to Column G in "ABC" worksheet, but max of 30 characters
8. If Column G value in "master" worksheet is >=0, then copy that value to Column E in "ABC" worksheet (otherwise needs to be zero)
9. If column G value in "master" worksheet is <0, then copy that value to Column F in "ABC" worksheet (otherwise needs to be zero)
Is this possible?
Here's a sub that should get you started. I did not implement all your steps, but I believe this is enough to take and finish on your own. If you find this answer is helpful to get you where you need to go, please accept this answer. If you have problems with anything here, please add a comment to this answer asking for clarification.
I have only tested on dummy data, but what I did work with was successful.
Option Explicit
Sub TransferData()
Dim Master As Worksheet
Dim NewSheet As Worksheet
Dim CompanyList As Object
Dim lRow As Long, lMaxRow As Long, lNewRow As Long
Dim vDictItem As Variant
Set CompanyList = CreateObject("Scripting.Dictionary")
Set Master = ThisWorkbook.Sheets("Master")
If Master.FilterMode Then
Master.ShowAllData
End If
Master.Range("A:R").Sort Master.Range("A2"), xlAscending, Master.Range("O2"), , xlAscending, , , xlYes
lMaxRow = Master.Range("A" & Master.Rows.Count).End(xlUp).Row
For lRow = 3 To lMaxRow
If Not CompanyList.Exists(Master.Range("A" & lRow).Value) Then
CompanyList.Add Master.Range("A" & lRow).Value, Master.Range("A" & lRow).Value
End If
Next lRow
For Each vDictItem In CompanyList.Keys
Master.Range("A3:R" & lMaxRow).AutoFilter 1, vDictItem
If Master.Cells.SpecialCells(xlCellTypeVisible).Count > 0 Then
Set NewSheet = ThisWorkbook.Worksheets.Add
NewSheet.Name = vDictItem
lNewRow = 1
For lRow = 3 To lMaxRow
If Master.Rows(lRow).Hidden = False Then
lNewRow = lNewRow + 1
NewSheet.Range("C1").Value = Master.Range("P1").Value
NewSheet.Range("C" & lNewRow).Value = Master.Range("P" & lRow).Value
NewSheet.Range("G1").Value = Master.Range("F1").Value
NewSheet.Range("G" & lNewRow).Value = Left(Master.Range("F" & lRow).Value, 30)
NewSheet.Range("E1").Value = Master.Range("G1").Value & " (POS)"
NewSheet.Range("F1").Value = Master.Range("G1").Value & " (NEG)"
If Master.Range("G" & lRow).Value >= 0 Then
NewSheet.Range("E" & lNewRow).Value = Left(Master.Range("G" & lRow).Value, 30)
Else
NewSheet.Range("F" & lNewRow).Value = Left(Master.Range("G" & lRow).Value, 30)
End If
End If
Next lRow
End If
Next vDictItem
End Sub