I need to do a Vlookup of an ID on the source sheet to a table in the data sheet. When the Vlookup is done, it needs to return the cell values from 6 different columns.
Here I have a function to get the range:
Function find_Col(header As String) As Range
Dim aCell As Range, rng As Range, def_Header As Range
Dim col As Long, lRow As Long, defCol As Long
Dim colName As String, defColName As String
Dim y As Workbook
Dim ws1 As Worksheet
Set y = Workbooks("Template.xlsm")
Set ws1 = y.Sheets("Results")
With ws1
Set def_Header = Cells.Find(what:="ID", LookIn:=xlValues, lookat:=xlWhole, MatchCase:=False, SearchFormat:=False)
Set aCell = .Range("B2:Z2").Find(what:=header, LookIn:=xlValues, lookat:=xlWhole, MatchCase:=False, SearchFormat:=False)
If Not aCell Is Nothing Then
defCol = def_Header.Column
defColName = Split(.Cells(, defCol).Address, "$")(1)
col = aCell.Column
colName = Split(.Cells(, col).Address, "$")(1)
lRow = Range(defColName & .Rows.count).End(xlUp).Row - 1
Set myCol = Range(colName & "2")
'This is your range
Set find_Col = Range(myCol.Address & ":" & colName & lRow).Offset(1, 0)
'If not found
Else
MsgBox "Column Not Found"
End If
End With
End Function
Then in my sub, I select the range and do a Vlookup which fills this range:
Selection.FormulaR1C1 = "=VLOOKUP(RC[-4],myTable,2,FALSE)"
And this works great.
Then I needed to return more than just one column, so I ended up with the formula:
Selection.FormulaArray = "=VLOOKUP($C3,myTable,{2,3,4,5,6},FALSE)"
Source Sheet:
Data Sheet:
So, my function returns only the range for one column, which I think I can use in terms of getting a row count then using something like this:
Set myRng = find_Col("Product")
For currentRow = myRng.Rows.count To 1 Step -1
Selection.FormulaArray = "=VLOOKUP($C3,myTable,{2,3,4,5,6},FALSE)"
Next currentRow
Then perhaps instead of C3 it could look something like this:
C & currentRow --> Selection.FormulaArray = "=VLOOKUP($C & currentRow,myTable,{2,3,4,5,6},FALSE)"
But then I have the issue that only one cell is selected (G3) and from H-L is not. And I have no idea whether this is even a plausible effort.
Ideally of course, I would have cells G3:L3 selected and fill the formula down to the last row.
My brain is just fried from all the thinking and attempts.
So this should do the trick... I've explained every instance but if you need help understanding just ask:
Option Explicit
Sub FillData1()
Dim ws As Worksheet, wsData As Worksheet, arr As Variant, arrData As Variant
Dim DictHeaders As Scripting.Dictionary, DictIds As Scripting.Dictionary, DictDataHeaders As Scripting.Dictionary, _
DictDataIds As Scripting.Dictionary
Dim LastRow As Long, LastCol As Integer, i As Long, j As Integer
With Application
.ScreenUpdating = False
.EnableEvents = False
End With
With ThisWorkbook
Set ws = .Sheets("Results")
Set wsData = .Sheets("List")
End With
'Lets suppose your data always starts on row 2 in both sheets and column B will always have the max amount of rows filled
With ws 'filling the first array
LastRow = .Cells(.Rows.Count, 2).End(xlUp).Row
LastCol = .Cells(2, .Columns.Count).End(xlToLeft).Column
arr = .Range("B2", .Cells(LastRow, LastCol)).Value
End With
With wsData 'filling the data array
LastRow = .Cells(.Rows.Count, 4).End(xlUp).Row
LastCol = .Cells(2, .Columns.Count).End(xlToLeft).Column
arrData = .Range("A2", .Cells(LastRow, LastCol)).Value
End With
'Now lets put everything into Dictionaries so if the data moves columns or rows won't matter
Set DictHeaders = New Scripting.Dictionary
Set DictIds = New Scripting.Dictionary
For i = 1 To UBound(arr, 2) 'this will fill the headers positions on the main sheet
If Not DictHeaders.Exists(arr(1, i)) Then DictHeaders.Add arr(1, i), i
Next i
For i = 2 To UBound(arr, 1) 'this will fill the IDs positions on the main sheet
If Not DictIds.Exists(arr(i, DictHeaders("KW ID"))) Then DictIds.Add arr(i, 1), i
Next i
Set DictDataHeaders = New Scripting.Dictionary
Set DictDataIds = New Scripting.Dictionary
For i = 1 To UBound(arrData, 2) 'this will fill the headers positions on the data sheet
If Not DictDataHeaders.Exists(arrData(1, i)) Then DictDataHeaders.Add arrData(1, i), i
Next i
For i = 2 To UBound(arrData, 1) 'this will fill the IDs positions on the data sheet
If Not DictDataIds.Exists(arrData(i, DictDataHeaders("KW ID"))) Then DictDataIds.Add arrData(i, DictDataHeaders("KW ID")), i
Next i
'Finally will loop through the main array to fill it with the data from the data array
On Error Resume Next
For i = 2 To UBound(arr)
For j = 6 To UBound(arr, 2) 'I'm assuming you want to avoid the first columns which are hidden
arr(i, j) = arrData(DictDataIds(arr(i, 1)), DictDataHeaders(arr(1, j)))
Next j
Next i
On Error GoTo 0
With ws 'filling the first array
LastRow = .Cells(.Rows.Count, 2).End(xlUp).Row
LastCol = .Cells(2, .Columns.Count).End(xlToLeft).Column
.Range("B2", .Cells(LastRow, LastCol)).Value = arr
End With
With Application
.ScreenUpdating = True
.EnableEvents = True
End With
End Sub
I don't know if I got the true issue of your goal. However, since your Selection parts in your code should be avoid, why don't make something like the following?
Set myRng = find_Col("Product")
For currentRow = myRng.Rows.count To 1 Step -1
Range(Cells(currentRow, 5), Cells(currentRow, 9)).FormulaArray = "=VLOOKUP(RC3,myTable,{2,3,4,5,6},FALSE)"
Next currentRow
Related
I have a workbook includes 2 sheets.
In each sheet, it has couple columns like Name(column A), State(column B) and ID (column C). But the rows' sort sequence of two sheets are both random.
According to IDs, I need to use VBA to compare the value of Name and State.
If they don't match, then highlight both of 2 cells in 2 sheets.
The result should be like this:
But my code below can only run for Column A if IDs have the same order sequence.
I understand that it can be much easier if I use conditional formatting to create a new rule or use vlookup or index and match function to compare. But I am asked to use VBA
Thank you!
Sub Test_Sheet()
Dim sheetOne As Worksheet
Dim sheetTwo As Worksheet
Dim lastRow As Long
Dim lastRow2 As Long
Dim thisRow As Long
Dim thisRow2 As Long
Dim lastCol As Long
Dim lastCol2 As Long
Dim thisCol As Long
Dim thisCol2 As Long
Dim foundRow As Range
Dim foundRow2 As Range
Dim lastFoundRow As Long
Dim lastFoundRow2 As Long
Dim searchRange As Range
Dim searchRange2 As Range
Dim isMatch As Boolean
Dim isMatch2 As Boolean
' Set up the sheets
Set sheetOne = Sheets("Sheet1")
Set sheetTwo = Sheets("Sheet2")
' Find the last row of the active sheet
lastRow = sheetOne.Cells(sheetOne.Rows.Count, "A").End(xlUp).Row
lastRow2 = sheetOne.Cells(sheetOne.Rows.Count, "B").End(xlUp).Row
' Set the search range on the other sheet
Set searchRange = sheetTwo.Range("A2:A" & sheetTwo.Cells(sheetTwo.Rows.Count, "A").End(xlUp).Row)
Set searchRange2 = sheetTwo.Range("B2:B" & sheetTwo.Cells(sheetTwo.Rows.Count, "B").End(xlUp).Row)
' Look at all rows
For thisRow = 1 To lastRow
' Find the last column on this row
lastCol = sheetOne.Cells(thisRow, sheetOne.Columns.Count).End(xlToLeft).Column
' Find the first match
Set foundRow = searchRange.Find(sheetOne.Cells(thisRow, "A").Value, searchRange(searchRange.Count), xlValues, xlWhole)
' Must find something to continue
Do While Not foundRow Is Nothing
' Remember the row we found it on
lastFoundRow = foundRow.Row
' Check the found row has the same number of columns
If sheetTwo.Cells(lastFoundRow, sheetTwo.Columns.Count).End(xlToLeft).Column = lastCol Then
' Assume it's a match
isMatch = True
' Look at all the column values
For thisCol = 1 To lastCol
' Compare the column values
If sheetTwo.Cells(lastFoundRow, thisCol).Value <> sheetOne.Cells(thisRow, thisCol).Value Then
' No match
isMatch = False
Exit For
End If
Next thisCol
' If it's still a match then highlight the row
If isMatch Then sheetOne.Range(sheetOne.Cells(thisRow, "A"), sheetOne.Cells(thisRow, lastCol)).Interior.ColorIndex = 3
End If
' Find the next match
Set foundRow = searchRange.Find(sheetOne.Cells(thisRow, "A").Value, foundRow, xlValues, xlWhole)
' Quit out when we wrap around
If foundRow.Row <= lastFoundRow Then Exit Do
Loop
Next thisRow
For thisRow2 = 1 To lastRow2
lastCol2 = sheetOne.Cells(thisRow2, sheetOne.Columns.Count).End(xlToLeft).Column
Set foundRow2 = searchRange2.Find(sheetOne.Cells(thisRow2, "B").Value, searchRange2(searchRange2.Count), xlValues, xlWhole)
Do While Not foundRow2 Is Nothing
lastFoundRow2 = foundRow2.Row
If sheetTwo.Cells(lastFoundRow2, sheetTwo.Columns.Count).End(xlToLeft).Column = lastCol2 Then
isMatch2 = True
For thisCol2 = 1 To lastCol2
If sheetTwo.Cells(lastFoundRow2, thisCol2).Value <> sheetOne.Cells(thisRow2, thisCol2).Value Then
isMatch2 = False
Exit For
End If
Next thisCol2
If isMatch2 Then sheetOne.Range(sheetOne.Cells(thisRow2, "B"), sheetOne.Cells(thisRow2, lastCol2)).Interior.ColorIndex = 5
End If
Set foundRow2 = searchRange2.Find(sheetOne.Cells(thisRow2, "B").Value, foundRow2, xlValues, xlWhole)
If foundRow2.Row <= lastFoundRow2 Then Exit Do
Loop
Next thisRow2
End Sub
Please, try the next code. It uses arrays, for faster iteration, processing the matching in memory and Union ranges, coloring the cells interior at once, at the end. Modifying the interior of each cell consumes Excel resources and takes time:
Sub testCompareIDs()
Dim sheetOne As Worksheet, sheetTwo As Worksheet, lastRow1 As Long, lastRow2 As Long, i As Long, j As Long
Dim rng1 As Range, rng2 As Range, arr1, arr2, rngColA1 As Range, rngColA2 As Range, rngColB1 As Range, rngColB2 As Range
Set sheetOne = Sheets("Sheet1")
Set sheetTwo = Sheets("Sheet2")
lastRow1 = sheetOne.cells(sheetOne.rows.count, "C").End(xlUp).row
lastRow2 = sheetTwo.cells(sheetOne.rows.count, "C").End(xlUp).row
Set rng1 = sheetOne.Range("A2:C" & lastRow1)
Set rng2 = sheetTwo.Range("A2:C" & lastRow2)
arr1 = rng1.value: arr2 = rng2.value 'place ranges to be processed in arrays, for faster iteration
For i = 1 To UBound(arr1)
For j = 1 To UBound(arr2)
If arr1(i, 3) = arr2(j, 3) Then
If arr1(i, 1) <> arr2(j, 1) Then
If rngColA1 Is Nothing Then
Set rngColA1 = rng1.cells(i, 1)
Set rngColA2 = rng2.cells(j, 1)
Else
Set rngColA1 = Union(rngColA1, rng1.cells(i, 1))
Set rngColA2 = Union(rngColA2, rng2.cells(j, 1))
End If
End If
If arr1(i, 2) <> arr2(j, 2) Then
If rngColB1 Is Nothing Then
Set rngColB1 = rng1.cells(i, 2)
Set rngColB2 = rng2.cells(j, 2)
Else
Set rngColB1 = Union(rngColB1, rng1.cells(i, 2))
Set rngColB2 = Union(rngColB2, rng2.cells(j, 2))
End If
End If
Exit For 'exit iteration since the ID has been found
End If
Next j
Next i
If Not rngColA1 Is Nothing Then
rngColA1.Interior.ColorIndex = 3
rngColA2.Interior.ColorIndex = 3
End If
If Not rngColB1 Is Nothing Then
rngColB1.Interior.ColorIndex = 3
rngColB2.Interior.ColorIndex = 3
End If
End Sub
The strings compare is case sensitive. The code can be adapted to not be case sensitive (using Ucase for each compare line)
Please, send some feedback after testing it.
I am trying to make something that would look like this:
In the table on the right there will be all the unique records which will be stored in a certain area. However some record may be existing in more areas, and this information can be taken from the list in column A and B. The macro should take each unique record in column D and search for it in Column A, every time it finds it, should copy the location/area in column B and pasted next to the unique record in the table. I think I could do this with a loop, but what I created in the code below does not really works.
The second challenge is to make it understand that in a location has been copy into the table, the new found location needs to be pasted in the next free cell of that same unique record.
I am aware my code is a little scare but I would appreciate even just advice on which direction I should be looking... Thanks in advance!
Sub searcharea()
Dim UC As Variant, UCrng As Range, ra As Range
Set UCrng = Range("F2:F6")
For Each UC In UCrng
Set ra = Cells.Find(What:=UC, LookIn:=xlFormulas, _
LookAt:=xlPart, SearchOrder:=xlByColumns, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False).Activate
ra.Offset(0, 1).Copy Destination:=Range("E2")
Next
End Sub
I would suggest looping through all Rows (Columns A + B), e.g.:
For i = 1 to Rows.Count
'DoStuff
Next i
For each row, you copy the value of A into D, if it is not there already.
You can access the values like this:
Cells(i, "A").Value
Cells(i, "B").Value
For finding values in a column, see here. If you found a duplicate, use another loop to check which column (E, F, G,..) in your specific row is the first empty one, and past the value of column B there.
Take a try:
Option Explicit
Sub test()
Dim LastRowA As Long, LastRowD As Long, i As Long, rngColumn As Long
Dim rng As Range
With ThisWorkbook.Worksheets("Sheet1")
LastRowD = .Cells(.Rows.Count, "D").End(xlUp).Row
.Range("D2:J" & LastRowD).ClearContents
LastRowA = .Cells(.Rows.Count, "A").End(xlUp).Row
For i = 2 To LastRowA
LastRowD = .Cells(.Rows.Count, "D").End(xlUp).Row
Set rng = .Range("D1:D" & LastRowD).Find(.Range("A" & i).Value, LookIn:=xlValues, lookat:=xlWhole)
If Not rng Is Nothing Then
rngColumn = .Cells(rng.Row, .Columns.Count).End(xlToLeft).Column
Cells(rng.Row, rngColumn + 1).Value = .Range("B" & i).Value
Else
.Range("D" & LastRowD + 1).Value = .Range("A" & i).Value
.Range("E" & LastRowD + 1).Value = .Range("B" & i).Value
End If
Next i
End With
End Sub
I think this code will do what you want. Please try it.
Option Explicit
Sub SortToColumns()
' Variatus #STO 30 Jan 2020
Dim WsS As Worksheet ' Source
Dim WsT As Worksheet ' Target
Dim Rng As Range
Dim Fn As String, An As String ' File name, Area name
Dim Rls As Long
Dim Rs As Long
Dim Rt As Long, Ct As Long
With ThisWorkbook ' change as required
Set WsS = .Worksheets("Sheet1") ' change as required
Set WsT = .Worksheets("Sheet2") ' change as required
End With
With WsT
' delete all but the caption row
.Range(.Cells(2, 1), .Cells(.Rows.Count, "A").End(xlUp)).EntireRow.ClearContents
End With
Application.ScreenUpdating = False
With WsS
' find last row of source data
Rls = .Cells(.Rows.Count, "A").End(xlUp).Row
For Rs = 2 To Rls ' start from row 2 (row 1 is caption)
Fn = .Cells(Rs, "A").Value
An = .Cells(Rs, "B").Value
If FileNameRow(Fn, WsT, Rt) Then
' add to existing item
With WsT
Ct = .Cells(Rt, .Columns.Count).End(xlToLeft).Column
Set Rng = .Range(.Cells(Rt, "B"), .Cells(Rt, Ct))
End With
With Rng
Set Rng = .Find(An, .Cells(.Cells.Count), xlValues, xlWhole, xlByRows, xlNext)
End With
' skip if Area exists
If Rng Is Nothing Then WsT.Cells(Rt, Ct + 1).Value = An
Else
' is new item
WsT.Cells(Rt, "A").Value = Fn
WsT.Cells(Rt, "B").Value = An
End If
Next Rs
End With
Application.ScreenUpdating = True
End Sub
Private Function FileNameRow(Fn As String, _
WsT As Worksheet, _
Rt As Long) As Boolean
' Rt is a return Long
' return True if item exists (found)
Dim Fnd As Range
Dim Rng As Range
Dim R As Long
With WsT
R = .Cells(.Rows.Count, "A").End(xlUp).Row
Set Rng = .Range(.Cells(2, "A"), .Cells(R, "A"))
Set Fnd = Rng.Find(Fn, Rng.Cells(Rng.Cells.Count), xlValues, xlWhole, xlByRows, xlNext)
If Fnd Is Nothing Then
Rt = Application.Max(.Cells(.Rows.Count, "A").End(xlUp).Row + 1, 2)
Else
Rt = Fnd.Row
FileNameRow = True
End If
End With
End Function
Initially, I started off with CSV data which I cleaned/filtered out. It's a very large data set. Here's what I am hoping to accomplish:
What I have tried:
My approach was to first copy the column with names to a new sheet, next remove all duplicates, and then use match and index to create new columns. Unfortunately, due to large data size excel crashes.
Are there any excel commands I can use? Or perhaps VBA? I appreciate any help.
Assuming that source data is in columns A:C and output is in E:H columns:
Sub TransposeTable()
Dim lastRow&, r&, x&, j&
x = 1: r = 2
While Len(Cells(r, "A")) > 0
x = x + 1
lastRow = Columns("A:A").Find(Cells(r, "A"), LookAt:=xlWhole, SearchDirection:=xlPrevious).Row
Cells(x, "E") = Cells(r, "A")
For j = r To lastRow
Cells(x, GetColumn(Cells(j, "C"))) = Cells(j, "B")
Next
r = lastRow + 1
Wend
End Sub
Private Function GetColumn&(strAttribute)
Select Case strAttribute
Case "Weight": GetColumn = 6
Case "Age": GetColumn = 7
Case "Height": GetColumn = 8
End Select
End Function
Add a helper column as shown in the image below in cell D2 ...
... then as you can see on the right hand side, I have the transformed table.
In cell G2, this is the formula ...
=IFERROR(INDEX($B:$B,MATCH($F2 & "_" & G$1,$D:$D,0)),"")
... now fill that down and across the rest of the grid.
If that doesn't work for you then you can always use a macro. Depends on the size of your data and how painful it is to maintain that matrix manually.
This code will get data from the source worksheet named "Sheet1". The last row is automatically detected. It assumes that data starts in row 2 (1st row is reserved for headers that aren't used). The macro creates output in a sheet named "Sheet2".
First, 2 collections are created for unique names and types. Thanks to this we know how big the output table will be and have all possible values, to which we can find matches in the second iteration.
Option Explicit
Option Base 1
Sub ProcessData()
Dim vSource As Variant, vOut() As Variant
Dim lastRow As Long, nCounter As Long, outNameCounter As Long, outTypeCounter As Long
Dim colNames As New Collection, colTypes As New Collection
Dim itm
Const nameCol As Long = 1
Const valueCol As Long = 2
Const typeCol As Long = 3
With ThisWorkbook.Worksheets("Sheet1") 'source worksheet named "Sheet1"
lastRow = .Cells.Find(what:="*", searchorder:=xlByRows, searchdirection:=xlPrevious).Row
vSource = .Range(.Cells(1, 1), .Cells(lastRow, 3))
End With
For nCounter = LBound(vSource) + 1 To UBound(vSource) 'skip header
On Error Resume Next
colNames.Add vSource(nCounter, nameCol), CStr(vSource(nCounter, nameCol))
colTypes.Add vSource(nCounter, typeCol), CStr(vSource(nCounter, typeCol))
On Error GoTo 0
Next nCounter
ReDim vOut(1 + colNames.Count, 1 + colTypes.Count) 'create output table based on unique names and types count
vOut(1, 1) = "Name"
For nCounter = 1 To colNames.Count 'fill output table names
vOut(nCounter + 1, 1) = colNames(nCounter)
Next nCounter
For nCounter = 1 To colTypes.Count 'fill output table types
vOut(1, nCounter + 1) = colTypes(nCounter)
Next nCounter
For nCounter = LBound(vSource) + 1 To UBound(vSource) 'match source table data with output table names and types
For outNameCounter = LBound(vOut) + 1 To UBound(vOut)
If vSource(nCounter, nameCol) = vOut(outNameCounter, nameCol) Then
For outTypeCounter = LBound(vOut, 2) + 1 To UBound(vOut, 2)
If vSource(nCounter, typeCol) = vOut(1, outTypeCounter) Then
vOut(outNameCounter, outTypeCounter) = vSource(nCounter, valueCol)
Exit For
End If
Next outTypeCounter
Exit For
End If
Next outNameCounter
Next nCounter
With ThisWorkbook.Worksheets("Sheet2") 'output worksheet named "Sheet2"
Application.ScreenUpdating = False
.Cells.ClearContents 'clear contents of whole worksheet
.Range(.Cells(1, 1), .Cells(UBound(vOut), UBound(vOut, 2))) = vOut 'paste output table
Application.ScreenUpdating = True
End With
End Sub
I liked the idea of using remove duplicates but you should use arrays for the one-to-one tranfers.
Option Explicit
Sub TransposeValues()
Dim i As Long, j As Long
Dim arr1 As Variant, arr2 As Variant, types As Variant, names As Variant
Dim ws1 As Worksheet, ws2 As Worksheet
Set ws1 = Worksheets("sheet5")
Set ws2 = Worksheets.Add(after:=ws1)
'set up types
With ws1.Range(ws1.Cells(1, "C"), ws1.Cells(ws1.Rows.Count, "C").End(xlUp))
ws2.Cells(1, "A").Resize(.Rows.Count, .Columns.Count) = .Value
End With
With ws2.Range(ws2.Cells(1, "A"), ws2.Cells(ws2.Rows.Count, "A").End(xlUp))
.RemoveDuplicates Columns:=1, Header:=xlYes
End With
With ws2.Range(ws2.Cells(1, "A"), ws2.Cells(ws2.Rows.Count, "A").End(xlUp))
.Cells(1, "A").Resize(.Columns.Count, .Rows.Count) = _
Application.Transpose(.Value)
.Clear
End With
'set up names
With ws1.Range(ws1.Cells(1, "A"), ws1.Cells(ws1.Rows.Count, "A").End(xlUp))
ws2.Cells(1, "A").Resize(.Rows.Count, .Columns.Count) = .Value
End With
With ws2.Range(ws2.Cells(1, "A"), ws2.Cells(ws2.Rows.Count, "A").End(xlUp))
.RemoveDuplicates Columns:=1, Header:=xlYes
End With
'collect source array
arr1 = ws1.Range(ws1.Cells(1, "A"), ws1.Cells(ws1.Rows.Count, "A").End(xlUp).Offset(0, 2)).Value
'create target array and matrix header arrays
With ws2
arr2 = .Cells(1, "A").CurrentRegion.Cells.Value
types = .Range(.Cells(1, "A"), .Cells(1, .Columns.Count).End(xlToLeft)).Value
names = .Range(.Cells(1, "A"), .Cells(.Rows.Count, "A").End(xlUp)).Value
End With
'move source to target
For i = 2 To UBound(arr1, 1)
arr2(Application.Match(arr1(i, 1), names, 0), _
Application.Match(arr1(i, 3), types, 0)) = arr1(i, 2)
Next i
'transfer target array to worksheet
ws2.Cells(1, "A").Resize(UBound(arr2, 1), UBound(arr2, 2)) = arr2
'name new target worksheet
ws2.Name = "Target"
End Sub
Currently trying to append all cells in each row into the first cell of that row, and iterate through every row. Problem is I'm dealing with ~3000 rows with about 20 columns of data in each row. Is there any better way to append all cells in a row into one single cell without using a for loop? That could narrow down the code to a single for loop and may speed up the process.
Tried making a nested for loop that iterates through every row then every column per row. It works, but takes far too long when dealing with a large amount of data.
Sub AppendToSingleCell()
Dim value As String
Dim newString As String
Dim lastColumn As Long
Dim lastRow As Long
lastRow = Cells(Rows.Count, "A").End(xlUp).Row
For j = 1 To lastRow
lastColumn = Cells(j, Columns.Count).End(xlToLeft).Column
For i = 2 To lastColumn
If IsEmpty(Cells(j, i)) = False Then
value = Cells(j, i)
newString = Cells(j, 1).value & " " & value
Cells(j, 1).value = newString
Cells(j, i).Clear
End If
Next i
Next j
End Sub
Load everything into a variant array and loop that instead of the range. load the output into another variant array and then put that data as one back in the sheet.
Sub AppendToSingleCell()
With ActiveSheet
Dim lastRow As Long
lastRow = .Cells(.Rows.Count, "A").End(xlUp).row
Dim lastColumn As Long
lastColumn = .Cells.Find(What:="*", After:=.Range("a1"), LookIn:=xlValue, SearchOrder:=xlByColumns, SearchDirection:=xlPrevious).Column
Dim dtaArr() As Variant
dtaArr = .Range(.Cells(1, 2), .Cells(lastRow, lastColumn)).value
Dim otArr() As Variant
ReDim otArr(1 To lastRow, 1 To 1)
Dim i As Long
For i = LBound(dtaArr, 1) To UBound(dtaArr, 1)
For j = LBound(dtaArr, 2) To UBound(dtaArr, 2)
If dtaArr(i, j) <> "" Then otArr(i, 1) = otArr(i, 1) & dtaArr(i, j) & " "
Next j
otArr(i, 1) = Application.Trim(otArr(i, 1))
Next i
.Range(.Cells(1, 2), .Cells(lastRow, lastColumn)).Clear
.Range(.Cells(1, 1), .Cells(lastRow, 1)).value = otArr
End With
End Sub
It's a bit long, but pretty straight forward.
Explanation inside the code's comments.
Code
Option Explicit
Sub AppendToSingleCell()
Dim newString As String
Dim LastRow As Long, LastColumn As Long
Dim Sht As Worksheet
Dim FullArr As Variant, MergeCellsArr As Variant
Dim i As Long, j As Long
Set Sht = ThisWorkbook.Sheets("Sheet1") ' <-- rename "Sheet1" to your sheet's name
With Sht
LastRow = FindLastRow(Sht) ' call sub that finds last row
LastColumn = FindLastCol(Sht) ' call sub that finds last column
' populate array with enitre range contents
FullArr = .Range(.Cells(1, 1), .Cells(LastRow, LastColumn))
ReDim MergeCellsArr(1 To LastRow) ' redim 1-D array for results (same number of rows as in the 2-D array)
' looping through array is way faster than interfacing with your worksheet
For i = 1 To UBound(FullArr, 1) ' loop rows (1st dimension of 2-D array)
newString = FullArr(i, 1)
For j = 2 To UBound(FullArr, 2) ' loop columns (2nd dimension of 2-D array)
If IsEmpty(FullArr(i, j)) = False Then
newString = newString & " " & FullArr(i, j)
End If
Next j
MergeCellsArr(i) = newString ' read new appended string to new 1-D array
Next i
' paste entire array to first column
.Range("A1").Resize(UBound(MergeCellsArr)).value = MergeCellsArr
End With
End Sub
'=======================================================================
Function FindLastCol(Sht As Worksheet) As Long
' This Function finds the last col in a worksheet, and returns the column number
Dim LastCell As Range
With Sht
Set LastCell = .Cells.Find(What:="*", After:=.Cells(1), LookAt:=xlPart, LookIn:=xlFormulas, _
SearchOrder:=xlByColumns, SearchDirection:=xlPrevious, MatchCase:=False)
If Not LastCell Is Nothing Then
FindLastCol = LastCell.Column
Else
MsgBox "Error! worksheet is empty", vbCritical
Exit Function
End If
End With
End Function
'=======================================================================
Function FindLastRow(Sht As Worksheet) As Long
' This Function finds the last row in a worksheet, and returns the row number
Dim LastCell As Range
With Sht
Set LastCell = .Cells.Find(What:="*", After:=.Cells(1), LookAt:=xlPart, LookIn:=xlFormulas, _
SearchOrder:=xlByRows, SearchDirection:=xlPrevious, MatchCase:=False)
If Not LastCell Is Nothing Then
FindLastRow = LastCell.Row
Else
MsgBox "Error! worksheet is empty", vbCritical
Exit Function
End If
End With
End Function
If you're interested in a shorter solution.... It assumes your data begins in cell A1.
Public Sub CombineColumnData()
Dim arr As Variant
Dim newArr() As Variant
Dim varTemp As Variant
Dim i As Long
arr = ActiveSheet.Range("A1").CurrentRegion.Value
ReDim newArr(1 To UBound(arr, 1))
For i = LBound(arr, 1) To UBound(arr, 1)
varTemp = Application.Index(arr, i, 0)
newArr(i) = Join(varTemp, "")
Next i
With ActiveSheet.Range("A1")
.CurrentRegion.Clear
.Resize(UBound(arr, 1), 1) = Application.Transpose(newArr)
End With
End Sub
I have 100K Excel file that has many employee info, I want to shift all existence data to the first row for this employee, the picture below will be louder than my words, can a VBA code do this? or there is a trick in excel that I am not aware of
Try following code.
Sub Demo()
Dim ws As Worksheet
Dim cel As Range, rng As Range
Dim lastRow As Long, lastCol As Long, i As Long
Dim fOccur As Long, lOccur As Long, colIndex As Long
Dim dict As Object, c1
Application.ScreenUpdating = False
Set ws = ThisWorkbook.Sheets("Sheet1") 'change Sheet1 to your data range
Set dict = CreateObject("Scripting.Dictionary")
With ws
lastRow = .Cells(.Rows.Count, "A").End(xlUp).Row 'last row with data in Column A
lastCol = .Cells.Find(What:="*", _
After:=.Range("A1"), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Column 'last column with data in Sheet1
Set rng = .Range("A1:A" & lastRow) 'set range in Column A
c1 = .Range("A2:A" & lastRow)
For i = 1 To UBound(c1, 1) 'using dictionary to get uniques values from Column A
dict(c1(i, 1)) = 1
Next i
colIndex = 16 'colIndex+1 is column number where data will be displayed from
For Each k In dict.keys 'loopthrough all unique values in Column A
fOccur = Application.WorksheetFunction.Match(k, rng, 0) 'get row no. of first occurrence
lOccur = Application.WorksheetFunction.CountIf(rng, k) 'get row no. of last occurrence
lOccur = lOccur + fOccur - 1
'copy range from left to right
.Range(.Cells(fOccur, 1 + colIndex), .Cells(lOccur, lastCol + colIndex)).Value = .Range(.Cells(fOccur, 1), .Cells(lOccur, lastCol)).Value
'delete blanks in range at right
.Range(.Cells(fOccur, 1 + colIndex), .Cells(lOccur, lastCol + colIndex)).SpecialCells(xlCellTypeBlanks).Delete Shift:=xlUp 'delte blank rows
Next k
End With
Application.ScreenUpdating = True
End Sub
Try the below. You can amend the below code to match where you want to move the range:
Dim oW As Worksheet: Set oW = ThisWorkbook.Worksheets("Sheet8")
With oW.UsedRange
.Cut .Offset(0, .Columns.Count + 2)
End With