I have the first name in column A and the last name in Column B and I need to combine them into just column A. Also not sure if I need to check this in the code but some of the cells are empty with now names. I have tried many things but they all want me to pull the two and enter them into a different or 3rd column. But I need to put them into column A.
This is the code I have and it keeps giving me the merge error.
With Worksheet
For Counter = LastRow To FirstRow Step -1
Range("BD2:BE1000").Merge Across:=True
Next Counter
End With
You can just use string concatenation here (assuming that lastrow (1000) and firstrow (2) have been set up properly in your sample code).
With Worksheet
For Counter = LastRow To FirstRow Step -1
.Range("BD" & counter).Value = .Range("BD" & counter).value & .Range("BE" & counter).value
Next Counter
End With
Concatenate (non-empty) names into one column
[1] In a first step you can assign your data range (object variable e.g. rng) to a variant 1-based 2-dim datafield array by a one liner v = rng or v = rng.Value2.
[2] In a second step you loop through all array rows and check for non-empty names concatenating these findings in the array's first columns (overwriting the original single name part).
[3] Resizing the receiving range to 1 column only (and the number of non-empty rows allows you to write the results back to sheet.
Code example
Option Explicit ' declaration head of your code module enforces declaration of variables/objects
Sub ConcatenateNames()
Dim v As Variant, rng As Range
With ThisWorkbook.Worksheets("MySheet") ' <<~~ change to your sheet name
' [1] assign names to 2-dim datafield array v
Set rng = .Range("BD2:BE1000") ' set user defined range to memory
v = rng.Value2 ' get data
' [2] loop through data
Dim i As Long, ii As Long
For i = 1 To UBound(v)
' [2a] check for non empty names
If Len(Trim(v(i, 1)) & Trim(v(i, 2))) > 0 Then
' [2b] concatenate first and last names in array v
ii = ii + 1 ' increment counter
v(ii, 1) = v(i, 1) & " " & v(i, 2)
End If
Next i
' [3] write back to sheet and resize receiving range to ii rows and 1 column
rng.Clear ' clear original data
rng.Resize(ii, 1) = v ' write names back to sheet
End With
End Sub
Further hint
Take care of the leading point . before "Range" referring to your worksheet object: Set rng = .Range("BD2:BE1000")
Related
I have a column where each cell has multiple strings divided by a "/". I have as well a list of strings.
I want to search for each item of the list in the column and each time that I find an item write in another column with the addition of the Shortname next to it.
Each one of those columns are in different worksheets.
Something like the image below:
The first column is where I want to search, the second is what I want to search, and the third is the resulting column.
For every string that starts with "GPRF_", I want to write it in the third column with the Shortname associated to it.
In this example "GPRF_TxChPower" appears 3 times, so it is written 3 times with each Shortname associated to it before passing to the next item.
Example
For now, I used this line:
IF(ISNUMBER(SEARCH(G35;TestConfigs!$B$3&"|||"&TestConfigs!$B$4&"|||"&TestConfigs!$B$5&"|||"&TestConfigs!$B$6&"|||"&TestConfigs!$B$7&"|||"&TestConfigs!$B$8&"|||"&TestConfigs!$B$9&"|||"&TestConfigs!$B$10&"|||"&TestConfigs!$B$11));1;0)
That basically searches what I want(G35), which is in the worksheet Commun, in the column(B3:11) on the worksheet TestConfigs, if it is found returns 1 else 0 to the cell (F35), also in the worksheet Commun.
And then in the column I want the values to be written I did:
=IF(Commun!F35=1;Commun!G35;"")
If the result in F35 is 1, I write what is written in G35, else "".
Please, try the next code. It uses only two sheets, processing against "GPRF_" prefix. Using arrays and working mostly in memory, it should be very fast. It assumes that the range to be processed has the headers on the second row and cells to be iterated starting from the third row. It returns in the Next sheet. It may return anywhere if you correctly Set sh2...
Sub ExtractShortNameByPrefix()
Dim sh1 As Worksheet, lastR As Long, sh2 As Worksheet, arr, arrCell
Dim i As Long, j As Long, dict As Object
Const strPref As String = "GPRF_" 'prefix to be searched for
Const colToReturn As Long = 1 'column number where to return the processed array
Set sh1 = ActiveSheet 'use here the sheet to be processed (your Worksheet1)
Set sh2 = sh1.Next 'use here the sheet where to return (your Worksheet3)
lastR = sh1.Range("A" & sh1.rows.count).End(xlUp).row 'last row
arr = sh1.Range("A3:B" & lastR).Value2 'place the range in an array for faster iteration and processing
Set dict = CreateObject("Scripting.Dictionary")
For i = 1 To UBound(arr)
If InStr(arr(i, 1), strPref) > 0 Then
arrCell = Split(arr(i, 1), "/") 'split the string by "/" separator if prefix exists
For j = 0 To UBound(arrCell)
If left(arrCell(j), Len(strPref)) = strPref Then
dict(arrCell(j) & "_" & arr(i, 2)) = 1 'place in the dictionay as key UNIQUE concatenations...
End If
Next j
End If
Next i
'drop the processed dictionary keys:
With sh2.cells(2, colToReturn).Resize(dict.count, 1)
.Value2 = Application.Transpose(dict.Keys)
.cells(1, 1).Offset(-1).value = strPref
.EntireColumn.AutoFit
End With
You can change "strPrefix" and "columnToReturn" constants to process a different prefix and return in a different column...
Please, send some feedback after testing it.
I did not get you question right, but if you want to find how many times one text is repeated in other text you can use this:
=(LEN(B3)-LEN(SUBSTITUTE(B3,B4,"")))/LEN(B4)
where B3 is long text, and B4 is text to search.
I am new to VBA and am trying to copy the column from Row 2 onwards where the column header (in Row 1) contains a certain word- "Unique ID".
Currently what I have is:
Dim lastRow As Long
lastRow = ActiveWorkbook.Worksheets("Sheets1").Range("A" & Rows.Count).End(xlUp).Row
Sheets("Sheets1").Range("D2:D" & lastRow).Copy
But the "Unique ID" is not always in Column D
You can try following code, it loops through first row looking for a specified header:
Sub CopyColumnWithHeader()
Dim i As Long
Dim lastRow As Long
For i = 1 To Columns.Count
If Cells(1, i) = "Unique ID" Then
lastRow = Cells(Rows.Count, i).End(xlUp).Row
Range(Cells(2, i), Cells(lastRow, i)).Copy Range("A2")
Exit For
End If
Next
End Sub
When you want to match info in VBA you should use a dictionary. Additionally, when manipulating data in VBA you should use arrays. Although it will require some learning, below code will do what you want with minor changes. Happy learning and don't hesitate to ask questions if you get stuck:
Option Explicit
'always add this to your code
'it will help you to identify non declared (dim) variables
'if you don't dim a var in vba it will be set as variant wich will sooner than you think give you a lot of headaches
Sub DictMatch()
'Example of match using dictionary late binding
'Sourcesheet = sheet1
'Targetsheet = sheet2
'colA of sh1 is compared with colA of sh2
'if we find a match, we copy colB of sh1 to the end of sh2
'''''''''''''''''
'Set some vars and get data from sheets in arrays
'''''''''''''''''
'as the default is variant I don't need to add "as variant"
Dim arr, arr2, arr3, j As Long, i As Long, dict As Object
'when creating a dictionary we can use early and late binding
'early binding has the advantage to give you "intellisense"
'late binding on the other hand has the advantage you don't need to add a reference (tools>references)
Set dict = CreateObject("Scripting.Dictionary") 'create dictionary lateB
dict.CompareMode = 1 'textcompare
arr = Sheet1.Range("A1").CurrentRegion.Value2 'load source, assuming we have data as of A1
arr2 = Sheet2.Range("A1").CurrentRegion.Value2 'load source2, assuming we have data as of A1
'''''''''''''''''
'Loop trough source, calculate and save to target array
'''''''''''''''''
'here we can access each cell by referencing our array(<rowCounter>, <columnCounter>
'e.g. arr(j,i) => if j = 1 and i = 1 we'll have the values of Cell A1
'we can write these values anywhere in the activesheet, other sheet, other workbook, .. but to limit the number of interactions with our sheet object we can also create new, intermediant arrays
'e.g. we could now copy cel by cel to the new sheet => Sheets(arr(j,1).Range(... but this would create significant overhead
'so we'll use an intermediate array (arr3) to store the results
'We use a "dictionary" to match values in vba because this allows to easily check the existence of a value
'Together with arrays and collections these are probably the most important features to learn in vba!
For j = 1 To UBound(arr) 'traverse source, ubound allows to find the "lastrow" of the array
If Not dict.Exists(arr(j, 1)) Then 'Check if value to lookup already exists in dictionary
dict.Add Key:=arr(j, 1), Item:=arr(j, 1) 'set key if I don't have it yet in dictionary
End If
Next j 'go to next row. in this simple example we don't travers multiple columns so we don't need a second counter (i)
'Before I can add values to a variant array I need to redim it. arr3 is a temp array to store matching col
'1 To UBound(arr2) = the number of rows, as in this example we'll add the match as a col we just keep the existing nr of rows
'1 to 1 => I just want to add 1 column but you can basically retrieve as much cols as you want
ReDim arr3(1 To UBound(arr2), 1 To 1)
For j = 1 To UBound(arr2) 'now that we have all values to match in our dictionary, we traverse the second source
If dict.Exists(arr2(j, 1)) Then 'matching happens here, for each value in col 1 we check if it exists in the dictionary
arr3(j, 1) = arr(j, 2) 'If a match is found, we add the value to find back, in this example col. 2, and add it to our temp array (arr3).
'arr3(j, 2) = arr(j, 3) 'As explained above, we could retrieve as many columns as we want, if you only have a few you would add them manually like in this example but if you have many we could even add an additional counter (i) to do this.
End If
Next j 'go to the next row
'''''''''''''''''
'Write to sheet only at the end, you could add formatting here
'''''''''''''''''
With Sheet2 'sheet on which I want to write the matching result
'UBound(arr2, 2) => ubound (arr2) was the lastrow, the ubound of the second dimension of my array is the lastcolumn
'.Cells(1, UBound(arr2, 2) + 1) = The startcel => row = 1, col = nr of existing cols + 1
'.Cells(UBound(arr2), UBound(arr2, 2) + 1)) = The lastcel => row = number of existing rows, col = nr of existing cols + 1
.Range(.Cells(1, UBound(arr2, 2) + 1), .Cells(UBound(arr2), UBound(arr2, 2) + 1)).Value2 = arr3 'write target array to sheet
End With
End Sub
I would like to copy data from one sheet to another.
I put the range that I want to copy into an array (LookupSource) because it's faster to work on arrays than looping through cells.
After filling my two dimensional array (LookupSource), I would like to keep only some records based on critieria (Column A = 10000), so I am trying to copy from LookupSource, the rows that fetch this criteria to the two dimensional array (DataToCopy) which will be copied to the destination sheet.
My problem is that I am not able to do that because as it seems I am not able to make a dynamic resize for the first dimension (rows) of the second array (DataToCopy).
Any Idea how to fill DataToCopy from LookupSource based on my condition ?
The error "index out of range" that I am getting is at the Line : ReDim Preserve DataToCopy(1 to j, 1 to 6)
not at first time, but on second time that I enter the For loop after the Next I
I suppose it's because the J is variable and I am not allowed to change the first dimension of the array.
How to deal with that ?
Any better Idea from what I am doing ?
to give you an example here is a small part of the sheet that I want to copy (I took only 8 rows, but in real there thousands). I want to copy only the rows that have 10000 in column A.
Here is my code
Dim LookupSource as Variant
Dim DataToCopy() As Variant
Dim i As Long
Dim j As Long
With MySheet
'MyRange is a defined name that reprensent column A, B, C, D, E, F
LookupSource = .Range(.Range("MyRange")(1, 1), .Range("MyRange")(8, 6)).Value2
j = 1
For i = LBound(LookupSource) To UBound(LookupSource)
If LookupSource(i, 1) = 10073 Then
ReDim Preserve DataToCopy(1 to j, 1 to 6)
DataToCopy(j, 1) = LookupSource(i, 1)
DataToCopy(j, 2) = LookupSource(i, 2)
DataToCopy(j, 3) = LookupSource(i, 3)
DataToCopy(j, 4) = LookupSource(i, 4)
DataToCopy(j, 5) = LookupSource(i, 5)
DataToCopy(j, 6) = LookupSource(i, 6)
j = j + 1
End If
Next i
end with
How to overcome the restrictions of ReDim Preserve in multidimensional arrays
As mentioned by #ScottCraner, a ReDim Preserve can change only the last dimension of a given (datafield) array.
Therefore trying to resize a 2-dimensional array's first dimension (="rows") will fail.
However you can overcome this inconvenience applying the relatively unknown filtering capability of Application.Index() (c.f. section [2]) and profit from the additional bonus of less loops.
Further reading: see Some pecularities of the Application.Index() function
Sub GetRowsEqual10000()
With Sheet1
Dim lastRow As Long: lastRow = .Range("A" & .Rows.Count).End(xlUp).Row
Dim rng As Range: Set rng = .Range("A2:F" & lastRow)
End With
'[1] get data
Dim data: data = rng
'[2] rearrange data via Application.Index() instead ReDim Preserve plus loops
data = Application.Index(data, ValidRows(data, Condition:=10000), Array(1, 2, 3, 4, 5, 6))
End Sub
Help function ValidRows()
Function ValidRows(arr, Condition) As Variant
'Purpose: a) check condition (e.g. values equalling 10000) and b) get valid row numbers c) in a 2-dim vertical array
ReDim tmp(1 To UBound(arr)) ' provide for 1-based 2-dim array
Dim i As Long, ii As Long
For i = 1 To UBound(arr) ' loop through 1st "column"
If arr(i, 1) = Condition Then ' a) check condition
ii = ii + 1: tmp(ii) = i ' b) collect valid row numbers
End If
Next i
ReDim Preserve tmp(1 To ii) ' resize tmp array (here the 1st dimension is also the last one:)
ValidRows = Application.Transpose(tmp) ' c) return transposed result as 2-dim array
End Function
Edit due to comment (2020-04-22)
Short hints to the most frequent use of Application.Index():
Frequently the Application.Index() function is used to
get an entire row or column array out of a 2-dim array without need to loop.
Accessing your 1-based 2-dimensional datafield array like that requires to
indicate a single row or column number and
to set the neighbour argument column or row number to 0 (zero), respectively which might result in e.g.
Dim horizontal, vertical, RowNumber As Long, ColumnNumber As Long
RowNumber = 17: ColumnNumber = 4
horizontal = Application.Index(data, RowNumber, 0)
vertical = Application.Index(data, 0, ColumnNumber)
(Addressing a single array element will be done directly, however via data(i,j)
instead of a theoretical Application.Index(data, i, j))
How to use Application.Index() for restructuring/filtering purposes:
In order to profit from the advanced possibilities of Application.Index() you
need to pass not only the array name (e.g. data), but the row|column arguments as Arrays, e.g.
data = Application.Index(data, Application.Transpose(Array(15,8,10)), Array(1, 2, 3, 4, 5, 6))
Note that the rows parameter becomes a "vertical" 2-dim array by transposition, where Array(15,8,10)
would even change the existing row order
(in the example code above this is done in the last code line within the ValidRows() function).
The columns argument Array(1,2,3,4,5,6) on the other hand remains "flat" or "horizontal" and
allows to get all existing column values as they are.
So you eventually you are receiving any data elements within the given element indices
(think them as coordinates in a graphic).
Range Lookup Function
The Code
Option Explicit
'START ****************************************************************** START'
' Purpose: Filters a range by a value in a column and returns the result '
' in an array ready to be copied to a worksheet. '
'******************************************************************************'
Function RangeLookup(LookUpValue As Variant, LookupRange As Range, _
Optional LookupColumn As Long = 1) As Variant
Dim LookUpArray As Variant ' LookUp Array
Dim DataToCopy As Variant ' DataToCopy (RangeLookup) Array
Dim countMatch As Long ' DataToCopy (RangeLookUp) Rows Counter
Dim r As Long, c As Long ' Row and Column Counters
' Check the arguments.
Select Case VarType(LookUpValue)
Case 2 To 8, 11, 17
Case Else: Exit Function
End Select
If LookupRange Is Nothing Then Exit Function
If LookupColumn < 1 Or LookupColumn > LookupRange.Columns.Count _
Then Exit Function
' Copy values of Lookup Range to Lookup Array.
LookUpArray = LookupRange
' Task: Count the number of values containing LookUp Value
' in LookUp Column of LookUp Array which will be
' the number of rows in DataToCopy Array.
' The number of columns in both arrays will be the same.
' Either:
' Count the number of values containing LookUp Value.
countMatch = Application.WorksheetFunction _
.CountIf(LookupRange.Columns(LookupColumn), LookUpValue)
' Although the previous looks more efficient, it should be tested.
' ' Or:
' ' Loop through rows of LookUpArray.
' For r = 1 To UBound(LookUpArray)
' ' Check if the value in current row in LookUp Column
' ' is equal to LookUp Value.
' If LookUpArray(r, LookupColumn) = LookUpValue Then
' ' Increase DataCopy Rows Counter.
' countMatch = countMatch + 1
' End If
' Next r
' Check if no match was found.
If countMatch = 0 Then Exit Function
' Task: Write the matching rows in LookUp Array to DataToCopy Array.
' Resize DataToCopy Array to DataToCopy Rows counted in the previous
' For Next loop and the number of columns in Lookup Array.
ReDim DataToCopy(1 To countMatch, 1 To UBound(LookUpArray, 2))
' Reset DataToCopy Rows Counter.
countMatch = 0
' Loop through rows of LookUp Array.
For r = 1 To UBound(LookUpArray)
' Check if the value in current row in LookUp Column
' is equal to LookUp Value.
If LookUpArray(r, LookupColumn) = LookUpValue Then
' Increase DataCopy Rows Counter.
countMatch = countMatch + 1
' Loop through columns of LookUp (DataToCopy) Array.
For c = 1 To UBound(LookUpArray, 2)
' Write the current value of LookUp Array to DataToCopy Array.
DataToCopy(countMatch, c) = LookUpArray(r, c)
Next c
End If
Next r
' Write values from DataToCopy Array to RangeLookup Array.
RangeLookup = DataToCopy
End Function
'END ********************************************************************** END'
You should use it e.g. like this:
Sub TryRangeLookup()
Dim LookupRange As Range
Dim DataToCopy As Variant
With MySheet
'MyRange is a defined name that reprensent column A, B, C, D, E, F
Set LookupRange = .Range(.Range("MyRange")(1, 1), _
.Range("MyRange")(8, 6)).Value2
End With
RangeLookUp 10073, DataCopy
If Not IsArray(DataToCopy) Then
MsgBox "No data found.": Exit Sub ' or whatever...
Endif
' Continue with code...
End Sub
I have tried below code to fill a two dimensional array in Excel VBA and I was able to get the desired results. I would like to know if there is a better way of doing this or if you foresee any technical issue once I have a significantly large size of data in real case situations. Any ideas or suggestions would be appreciated for improvement.
Sub test_selection()
' My below array is based on values contained within
' selected cells
' The purpose of using two dimensional array is to
' keep values in one column of array
' while retaining cell addresses in 2nd
' dimension to print some info in relevant cells
' offset to the selected cells
Dim anArray() As String
firstRow = Selection.Range("A1").Row
LastRow = Selection.Rows(Selection.Rows.Count).Row
colum = Selection.Columns.Column
arrSize = LastRow - firstRow
ReDim anArray(0 To arrSize, 1)
cnt = 0
For i = firstRow To LastRow
anArray(cnt, 0) = CStr(Cells(i, colum).Value2)
anArray(cnt, 1) = Cells(i, colum).Address
cnt = cnt + 1
Next i
Call TestGetFileList(anArray)
End Sub
When you have a significantly large size of data, that loop through the worksheet is going to be slow. Probably better to grab all of the data at once and reprocess it in memory.
Option Explicit
Sub test_selection()
' My below array is based on values contained within
' selected cells
' The purpose of using two dimensional array is to
' keep values in one column of array
' while retaining cell addresses in 2nd
' dimension to print some info in relevant cells
' offset to the selected cells
Dim i As Long, r As Long, c As String, anArray As Variant
With Selection
c = Split(.Cells(1).Address, "$")(1)
r = Split(.Cells(1).Address, "$")(2) - 1
anArray = .Columns(1).Cells.Resize(.Rows.Count, 2).Value2
End With
For i = LBound(anArray, 1) To UBound(anArray, 1)
anArray(i, 1) = CStr(anArray(i, 1))
anArray(i, 2) = "$" & c & "$" & i + r
Next i
TestGetFileList anArray
End Sub
I have two spreadsheets. One is empty, the other contains data. I open both and need to pull data from the one containing data, pasting into the blank spreadsheet. Criteria is to copy cell A when any cell from B through AH is not null and paste the data from cell A into the blank spreadsheet. The number of rows varies. Cells B - AH would either be null or contain an integer (1 - 5). If ANY cell in B - AH contains an integer, I need to copy cell A to the blank spreadsheet.
I know how to find the last cell and loop from row 1 to lastcell but not sure how I would loop through columns B - AH to find the first not null cell.
I'm thinking it would be best to create an array to store cell A when the criteria is met then, when complete, move to the blank spreadsheet and paste the array. Just not sure how I would code the search for any not null cells in each row.
This code should do what you want. It determines the presence of integers in the B:AH range by using the worksheet function COUNT().
Private Sub CountIntegers()
Dim Arr As Variant ' output array
Dim Rng As Range
Dim Rl As Long ' last row
Dim R As Long ' row counter
Dim i As Long ' index of Arr
With ActiveSheet
Rl = .Cells(.Rows.Count, "A").End(xlUp).Row
ReDim Arr(1 To Rl)
For R = 2 To Rl ' assuming row 1 to have headers
Set Rng = Range(.Cells(R, "B"), .Cells(R, "AH"))
If Application.Count(Rng) Then
i = i + 1
Arr(i) = .Cells(R, "A").Value
End If
Next R
End With
ReDim Preserve Arr(i)
' specify the output sheet here:-
Set Rng = Worksheets("Sheet2").Cells(2, 1).Resize(i)
Rng.Value = Application.Transpose(Arr)
End Sub