VBA column looping - vba

I have a large Excel file and I need to replace all values in 12 columns completely.
Right now, there is a formula in each one of the cells, and I need to replace that formula with my own.
How do I loop through all those columns, knowing at what row it starts but don't know the end row (file is updated constantly). The hack of "A600000" seems overkill.
I am new to VBA and some guidance would be really appreciated.

ActiveSheet.UsedRange is the range of all the used cells on the current sheet.
You can use ActiveSheet.UsedRange.Rows.Count and .Columns.Count to get the height and widht of this range.
Here's a very crude function that hits every cell in the range:
Sub test()
Dim thisRange As Range
Set thisRange = ActiveSheet.UsedRange
With thisRange
For y = 1 To .Rows.Count
For x = 1 To .Columns.Count
thisRange.Cells(y, x).Value = "Formula here"
Next x
Next
End With
End Sub
But what you want may be different, can you be more specific?

The below will accomplish what you need to do. You just need to supply the startRow, .Sheets("Name"), and i arguments. If the columns are all the same length, then UsedRange will work fine if there are not random cells with values outside and below the columns you are interested in. Otherwise, try this in your code (on a throw away copy of your workbook)
Sub GetLastRowInColumn()
Dim ws as Excel.Worksheet
Set ws = Activeworkbook.Sheets("YOURSHEETNAMEHERE")
Dim startRow as long
startRow = 1
Dim lastRow as long
Dim i as long
For i = 1 to 12 'Column 1 to Column 12 (Adjust Accordingly)
lRow = ws.Cells(ws.Rows.Count, i).End(xlUp).Row
ws.Range(ws.Cells(startRow, i), ws.Cells(lRow, i)).Formula = "=Max(1)" 'Sample Formula
Next
End Sub

EDIT : Fixed typo
The below function will build the range with varying length columns. Use the function to return the desired range and fill all related cells in one shot.
Function GetVariantColumnRange(MySheet As Excel.Worksheet, _
TopRow As Long, StartColumn As Long, LastColumn As Long) As Excel.Range
Dim topAddress As String
Dim bottomAddress As String
Dim addressString As String
Dim i As Long
For i = StartColumn To LastColumn
topAddress = MySheet.Cells(TopRow, i).Address
bottomAddress = MySheet.Cells(MySheet.Rows.Count, i).End(xlUp).Address
addressString = addressString & ", " & topAddress & ":" & bottomAddress
Next
addressString = Right(addressString, Len(addressString) - _
InStr(1, addressString, ", ", vbBinaryCompare))
Set GetVariantColumnRange = MySheet.Range(addressString)
End Function
Usage follows...
Sub Test()
Dim myrange As Range
Set myrange = GetVariantColumnRange(ThisWorkbook.Sheets(1), 1, 1, 12)
myrange.Select 'Just a visual aid. Remove from final code.
myrange.Formula = "=APF($Jxx, "string1", "string2") "
End Sub

Related

Define array to obtain data from range as "double" var type

I'm fairly new to VBA, so please bear with me.
I want to tell VBA to get an array from a range of cells. The user will paste a column of data into cell C2 so cells below C2 will be populated. The number of cells populated is up to the user.
I am also going to need each of the elements in the array to be taken as doubles as I'm going to make operations with them.
Therefore if the list is
1.2222
2.4444
3.5555
Then I need the array to preserve the decimal points.
How do I do this?
This is what I've got this fur, with no luck:
Set ThisWS = Excel.ActiveWorkbook.Worksheets("Hoja1")
Dim InputValues() As Double 'Define Array
Dim LRow As Long 'Define length of array
With Sheets("Hoja1")
LRow = .Range("C" & .Rows.count).End(xlUp).Row
End With
InputValues = ThisWS.Range("C2:C" & LRow).Value 'Error 13: data type doesn't match
End Sub
Thanks!
Excel.ActiveWorkbook. isn't needed in Excel, it is implied. I didn't need to type cast the cell value CDbl(.Cells(x, "C")).
Sub Example()
Dim InputValues() As Double
Dim lastRow As Long, x As Long
With Worksheets("Hoja1")
lastRow = .Range("C" & .Rows.Count).End(xlUp).Row
ReDim InputValues(lastRow - 2)
For x = 2 To .Range("C" & .Rows.Count).End(xlUp).Row
InputValues(x - 2) = CDbl(.Cells(x, "C"))
Next
End With
End Sub
This example is more efficient but won't make a noticeable difference unless you are working with a very large amount of data.
Sub Example2()
Dim InputValues() As Double, vInputValues As Variant
Dim x As Long
With Worksheets("Hoja1")
vInputValues = .Range("C2", .Range("C" & .Rows.Count).End(xlUp)).Value2
ReDim InputValues(UBound(vInputValues) - 1)
For x = 1 To UBound(vInputValues)
InputValues(x - 1) = CDbl(vInputValues(x, 1))
Next
End With
End Sub
Set ThisWS = Excel.ActiveWorkbook.Worksheets("Hoja1")
Dim CurRow As Long
Dim LRow As Long 'Define length of array
LRow = ThisWS.Range("C" & Rows.count).End(xlUp).Row
Dim InputValues(1 to LRow - 1) As Double 'Define Array
For CurRow = 2 to LRow
InputValues(CurRow - 1) = ThisWS.Range("C" & CurRow).Value
Next CurRow
End Sub
you can simply go like follows
Option Explicit
Sub main()
Dim InputValues As Variant 'Define Array
With Excel.ActiveWorkbook.Worksheets("Hoja1") ' refer to wanted worksheet
InputValues = .Range("C2", .Cells(.Rows.Count, 3).End(xlUp)).value 'fill array with values in column "C" cells from row 2 down to last non emtpy one
End With
End Sub
should you ever need to handle array values as of Double type, then you can use CDbl() function
In VBA you can assign .Value and .Value2 arrays only to a Variant
As a side note if the range is formated as table, you can just do something like
Dim InputValues() ' As Variant by default
InputValues = [transpose(Hoja1!Table1[Column3])] ' Variant(1 to number of rows in Table1)

VBA Frequency Highlighter Function in Very Large Excel Sheet

In a previous post user: LocEngineer managed to help me to write a finding function that would find the least frequent values in a column of a particular category.
The VBA code works well for the most part with some particular issues, and the previous question had been answered with a sufficiently good answer already, so I thought this required a new post.
LocEngineer: "Holy smoking moly, Batman! If THAT truly is your sheet.... I'd say: forget "UsedRange". That won't work well enough with THAT spread... I've edited the above code using more hardcoded values. Please adapt the values according to your needs and try that. Woah what a mess."
Here is the code:
Sub frequenz()
Dim col As Range, cel As Range
Dim letter As String
Dim lookFor As String
Dim frequency As Long, totalRows As Long
Dim relFrequency As Double
Dim RAN As Range
RAN = ActiveSheet.Range("A6:FS126")
totalRows = 120
For Each col In RAN.Columns
'***get column letter***
letter = Split(ActiveSheet.Cells(1, col.Column).Address, "$")(1)
'*******
For Each cel In col.Cells
lookFor = cel.Text
frequency = Application.WorksheetFunction.CountIf(Range(letter & "2:" & letter & totalRows), lookFor)
relFrequency = frequency / totalRows
If relFrequency <= 0.001 Then
cel.Interior.Color = ColorConstants.vbYellow
End If
Next cel
Next col
End Sub
The Code is formatted like this: (Notice the merged cells that head each column for titles. The titles go down to row 5 and data starts on row 5) (Also Notice that the rows are very much filled with empty columns, sometimes more so than data.)
Finally, one important change I cant figure out is how to get it to ignore blank cells.
Please advise. Thank you.
If the 2 adjustments to be made are to 1. exclude headers, and 2. blank cells
Exclude the headers in way a bit more dynamic; this excludes the top 6 rows:
With ActiveSheet.UsedRange
Set ran = .Offset(6, 0).Resize(.Rows.Count - 6, .Columns.Count)
End With
In the inner For loop, after this line For Each cel In col.Cells you need an IF:
For Each cel In col.Cells
If Len(cel.Value2) > 0 Then...
Here is the modified version (untested):
Option Explicit
Sub frequenz()
Const MIN_ROW As Long = 6
Const MAX_ROW As Long = 120
Dim col As Range
Dim cel As Range
Dim rng As Range
Dim letter As String
Dim lookFor As String
Dim frequency As Long
With ActiveSheet.UsedRange
Set rng = .Offset(MIN_ROW, 0).Resize(MAX_ROW, GetMaxCell.Column)
End With
For Each col In rng.Columns
letter = Split(ActiveSheet.Cells(1, col.Column).Address, "$")(1)
For Each cel In col
lookFor = cel.Value2
If Len(lookFor) > 0 Then 'process non empty values
frequency = WorksheetFunction.CountIf( _
Range(letter & "2:" & letter & MAX_ROW), lookFor)
If frequency / MAX_ROW <= 0.001 Then
cel.Interior.Color = ColorConstants.vbYellow
End If
End If
Next cel
Next col
End Sub
.
Updated to use a new function when determining the last row and column containing values:
Public Function GetMaxCell(Optional ByRef rng As Range = Nothing) As Range
'It returns the last cell of range with data, or A1 if Worksheet is empty
Const NONEMPTY As String = "*"
Dim lRow As Range, lCol As Range
If rng Is Nothing Then Set rng = Application.ActiveWorkbook.ActiveSheet.UsedRange
If WorksheetFunction.CountA(rng) = 0 Then
Set GetMaxCell = rng.Parent.Cells(1, 1)
Else
With rng
Set lRow = .Cells.Find(What:=NONEMPTY, LookIn:=xlFormulas, _
After:=.Cells(1, 1), _
SearchDirection:=xlPrevious, _
SearchOrder:=xlByRows)
Set lCol = .Cells.Find(What:=NONEMPTY, LookIn:=xlFormulas, _
After:=.Cells(1, 1), _
SearchDirection:=xlPrevious, _
SearchOrder:=xlByColumns)
Set GetMaxCell = .Parent.Cells(lRow.Row, lCol.Column)
End With
End If
End Function

Dynamically set Ranges to the columns in VBA

I am importing some date to worksheet which needs to be ranged for validation and reference in other worksheets.
Say I have 4 columns in worksheet(WS1) but the row count is dynamic on every import. How can i range the columns(A:D)?
Please help.
Regards,
Mani
Use a lastRow variable to determine the last row. I included a few examples of this. Also on this example is a lastCol variable.. You can use this if the number of Columns is dynamic as well.
Private Sub lastRow()
Dim lastRow As Long
Dim lastCol As Long
Dim sheet As String
sheet = "WS1"
lastRow = Sheets(sheet).Range("A" & Rows.Count).End(xlUp).row 'Using Range()
lastRow = Sheets(sheet).Cells(Rows.Count, "A").End(xlUp).row 'Using Cells()
lastCol = Sheets(sheet).Cells(2, Columns.Count).End(xlToLeft).Column
End Sub
You can loop through your sheet easily enough using variables also. Using Cells(row,col) instead of Range(A1). you can use numbers or a letter in quotes for the column as shown in the example.
This example looks at WS1 and matches someValue. If the value in Column A of WS1 = somevalue, the record is copied to a "Master" Sheet.
Sub LoopExample()
Dim mRow As Long 'used for a master row
For lRow = 2 To lastRow
If Sheets(sheet).Cells(lRow, 1) = someValue Then
'perform something here like this. Copy columns A:D to the Master Sheet if match
For lCol = 1 To 4 'or you could go 1 to lastCol if you needed it dynamic
Sheets("MASTER").Cells(mRow, lCol) = Sheets(sheet).Cells(lRow, lCol) 'mRow as Row on Master
Next lCol
mRow = mRow + 1 'Increment the Master Row
End If
Next lRow
End Sub
Thanks anyways. But what i wanted was just to Name ranges the columns in worksheet.
I have already accomplished the copy and paste (Loading data b/w worksheets).
This is what i wanted.
vRowCount = DestWorkSheet.Cells(1, 1).SpecialCells(xlCellTypeLastCell).Row
vColCount = DestWorkSheet.Cells(1, 1).SpecialCells(xlCellTypeLastCell).Column
DestWorkSheet.usedRange.Columns.AutoFit
AddNamedRange Dest_RATES, DATA_Dest_RATES
Where AddNamedRange is a function,
Public Sub AddNamedRange(ByVal sheetCodeName As String, ByVal namedRange As String)
Dim rngToBeNamed As Range
Dim ws As Worksheet
On Error GoTo AddNamedRange_Error
Set rngToBeNamed = GetUsedRange(sheetCodeName)
Set ws = rngToBeNamed.Worksheet
ws.Names.Add name:=namedRange, RefersTo:=ws.Range(rngToBeNamed.Address)
On Error GoTo 0
Exit Sub
AddNamedRange_Error:
MsgBox "Error " & Err.Number & " (" & Err.Description & ") in procedure AddNamedRange of Module UtilitiesRange"
End Sub
Regards,
Mani
Seems like you could just use something like this in the sheet module:
Private Sub Worksheet_Change(ByVal target As Range)
Dim i As Long
Dim NamesOfNames(1 To 4) As String
NamesOfNames(1) = "NameOfColumn1"
NamesOfNames(2) = "NameOfColumn2"
NamesOfNames(3) = "NameOfColumn3"
NamesOfNames(4) = "NameOfColumn4"
For i = 1 To 4
ThisWorkbook.Names.Add Name:=NamesOfNames(i), _
RefersTo:=Range(Cells(1, i), Cells(Cells(Rows.Count, i).End(xlUp).Row, i))
Next i
End Sub

Similar Grouping in vba

I have two for loops in vba that are iterating over column b and checking to see if the first word in the current cell is the same as the first word in any other cell and if so copying them into another column, therefore grouping similar items. But, when I go to copy and paste the matches it finds, it only copy and pastes the matches, not the original cells that it is comparing against. I would like to have the matches and the original cells as well in the grouping but I am unsure where to modify my code so it will do so. I am rather new to vba so any help would be greatly appreciated.
Sub FuzzySearch()
Dim WrdArray1() As String, WrdArray2() As String, i As Long, Count As Long, Rng1 As Range
Dim WS As Worksheet, positionx As Long, positiony As Long
Dim rng2 As Range
Set WS = ThisWorkbook.ActiveSheet
With WS
Set Rng1 = .Range("B2:B" & .Range("B" & .Rows.Count).End(xlUp).Row)
End With
For i = 1 To Rng1.Rows.Count
With Columns("B")
.Find(what:="*", after:=.Cells(1, 1), LookIn:=xlFormulas).Activate
End With
position = 1
For j = 1 To Rng1.Rows.Count
WrdArray1 = Split(ActiveCell.Value, " ")
ActiveCell.Offset(1).Activate
WrdArray2 = Split(ActiveCell.Value, " ")
If UBound(WrdArray2) < 0 Then
End
End If
If WrdArray1(0) = WrdArray2(0) Then
ActiveCell.Copy Destination:=ActiveSheet.Range("C" & position)
position = position + 1
Count = Count + 1
End If
Next j
Next i
End Sub
Given that you are using a mixture of arrays and Ranges it would probably be easier and less confusing to populate one of the arrays with the final output (including the comparator) within a loop and then transfer the array to the worksheet in a single command.
However, perhaps consider the following approach which lets Excel do all the 'heavy lifting'. It's the same number of code lines but I have annotated it for your information. This illustrates the filling of an array in a loop and transferring it to a Range. Change the various variables to suit your situation.
Sub grpAndCount()
Dim ws As Worksheet
Dim strow As Long, endrow As Long, stcol As Long
Dim coloffset As Long, r As Long
Dim newstr As String
Dim drng As Range
Dim strArr() As String
'Data start r/c
strow = 6 'Row 6
stcol = 2 'Col B
'Offset no of Cols from Data to place results
coloffset = 2
Set ws = Sheets("Sheet1")
With ws
'find last data row
endrow = Cells(Rows.Count, stcol).End(xlUp).Row
'for each data row
For r = strow To endrow
'get first word
newstr = Left(.Cells(r, stcol), InStr(.Cells(r, stcol), " ")-1)
'put string into array
ReDim Preserve strArr(r - strow)
strArr(r - strow) = newstr
Next r
'put array to worksheet
Set drng = .Range(.Cells(strow, stcol + coloffset), .Cells(endrow, stcol + coloffset))
drng = Application.Transpose(strArr)
'sort newly copied range
drng.Sort Key1:=.Cells(strow, stcol + coloffset), Order1:=xlAscending, Header:=xlNo
'provide a header row for SubTotal
.Cells(strow - 1, stcol + coloffset) = "Header"
'resize range to include header
drng.Offset(-1, 0).Resize(drng.Rows.Count + 1, 1).Select
'apply Excel SubTotal function
Application.DisplayAlerts = False
Selection.Subtotal GroupBy:=1, Function:=xlCount, Totallist:=Array(1)
Application.DisplayAlerts = True
'remove 'Header' legend
.Cells(strow - 1, stcol + coloffset) = ""
End With
End Sub

Search for proper column and find duplicates - macro amendment needed [VBA]

I have a VBA Macro for excel to find duplicates. It works but it is specified to a certain column. I would like to search through column headers which are in the 1st row and find the header called "Email" (the best would be "Email*" as sometimes this header contains some other words after the "Email" word). I think this script doesn't adjust to the number of rows and it is limited to 65536 values. I would prefer to let this script adjust to the number of values in the column. I have a similar VBA macro which does the perfect job. I thought I would be able to use this macro as an example and amend the one which I am currently working on...however I failed. Could anyone help me to do the proper amendments to the first code?
VBA MACRO WHICH I WOULD LIKE TO AMEND:
Option Explicit
Sub DeleteDups()
Dim x As Long
Dim LastRow As Long
Sheets("test").Activate
LastRow = Range("A65536").End(xlUp).Row
For x = LastRow To 1 Step -1
If Application.WorksheetFunction.CountIf(Range("A1:A" & x), Range("A" & x).Text) > 1 Then
Range("A" & x).Interior.Color = RGB(255, 48, 48)
End If
Next x
End Sub
VBA MACRO WHICH WORKS FINE AND I WANTED TO USE AS AN EXAMPLE:
Function getAllColNum(ByVal rowNum As Long, ByVal searchString As Variant) As Object
Dim allColNum As Object
Dim i As Long
Dim j As Long
Dim width As Long
Set allColNum = CreateObject("Scripting.Dictionary")
colNum = 1
With ActiveSheet
width = .Cells(rowNum, .Columns.Count).End(xlToLeft).Column
For i = 1 To width
If InStr(UCase(Trim(.Cells(rowNum, i).Value)), UCase(Trim(searchString))) > 0 Then
allColNum.Add i, ""
End If '
Next i
End With
Set getAllColNum = allColNum
End Function
Sub GOOD_WORKS_No_Dots_at_End_of_Emails()
Dim strSearch As String
strSearch = "Email"
Dim colNum As Variant
Dim allColNum As Object
Sheets("Data").Activate
Dim LR As Long, i As Long
Set allColNum = getAllColNum(1, searchString)
For Each colNum In allColNum
LR = Cells(Rows.Count, colNum).End(xlUp).Row
For i = 1 To LR
With Range(Cells(i, colNum), Cells(i, colNum))
If Right(.Value, 1) = "." Then .Value = Left(.Value, Len(.Value) - 1)
End With
Next i
Next colNum
Sheets("Automation").Activate
MsgBox "No Dots at the end of email addresses - Done!"
End Sub
MY WORK SO FAR
Function getAllColNum(ByVal rowNum As Long, ByVal searchString As Variant) As Object
Dim allColNum As Object
Dim i As Long
Dim j As Long
Dim width As Long
Set allColNum = CreateObject("Scripting.Dictionary")
colNum = 1
With ActiveSheet
width = .Cells(rowNum, .Columns.Count).End(xlToLeft).Column
For i = 1 To width
If UCase(Trim(.Cells(rowNum, i).Value)) Like UCase(Trim(searchString)) Then
allColNum.Add i, ""
End If '
Next i
End With
Set getAllColNum = allColNum
End Function
Sub testing_testing()
Dim strSearch As String
strSearch = "Email"
Dim colNum As Variant
Dim allColNum As Object
Sheets("Data").Activate
Dim LR As Long, i As Long
Set allColNum = getAllColNum(1, searchString)
For Each colNum In allColNum
LR = Cells(Rows.Count, colNum).End(xlUp).Row
For i = 1 To LR
With Range(Cells(i, colNum), Cells(i, colNum))
If Application.WorksheetFunction.CountIf(Range("R1:A" & x), Range("R" & x).Text) > 1 Then
Range("A" & x).Interior.Color = RGB(255, 48, 48)
End With
End If
Next i
Next colNum
Sheets("Automation").Activate
MsgBox "Finiding duplicates - Done!"
End Sub
Seems to be more complicated and as I mentioned I have limited knowledge of VBA. However, I found a different script which might be easier to amend.
This macro finds the email address column and marks the whole column
Option Explicit
Sub GOOD_WORKS_Mark_Email_Duplicates()
Dim x As Long
Dim LastRow As Long
Sheets("test").Activate
LastRow = Range("A65536").End(xlUp).Row
For x = LastRow To 1 Step -1
If Application.WorksheetFunction.CountIf(Range("A1:A" & x), Range("A" & x).Text) > 1 Then
Range("A" & x).Interior.Color = RGB(255, 48, 48)
End If
Next x
MsgBox "Email duplicates has been marked - red cells. Check if there are any red cells in the Email column"
End Sub
This one finds duplicates using countif function (which is good to me. The only problem is that I have this macro as a button, where the range is specified
Sub Highlight_Duplicates(Values As Range)
Dim Cell
For Each Cell In Values
If WorksheetFunction.CountIf(Values, Cell.Value) > 1 Then
Cell.Interior.ColorIndex = 6
End If
Next Cell
End Sub
Then the action button:
Private Sub CommandButton1_Click()
Highlight_Duplicates (Sheets("Test").Range("C2:C92"))
End Sub
It is fine for me to run 1st macro and then the 2nd. However, I don't know how to get rid of Range in the action button. Any ideas?
In your getAllColNum function, change this:
If InStr(UCase(Trim(.Cells(rowNum, i).Value)), _
UCase(Trim(searchString))) > 0 Then
to this:
If UCase(Trim(.Cells(rowNum, i).Value)) Like UCase(Trim(searchString)) Then
and that will allow you to pass a wildcard header like "email" and get all matching columns.