I'm trying to determine the minimum and maximum values of a 5 cell range (C:G) for all non-blank rows in a worksheet and place the respective results in columns L and M.
I'm getting a Run-time error '1004' Application-defined or object-defined error.
Sub test()
ActiveSheet.Range("A1").Select
ActiveCell.Offset(1, 0).Select
Do While ActiveCell.Value <> Empty
ActiveCell.Offset(0, 11) = WorksheetFunction.Min(Range(ActiveCell.Offset(0, 2), ActiveCell.Offset(0, 6)))
ActiveCell.Offset(0, 12) = WorksheetFunction.Max(Range(ActiveCell.Offset(0, 2), ActiveCell.Offset(0, 6)))
ActiveCell.Offset(1, 0).Select
Loop
ActiveSheet.Range("A1").Select
End Sub
I'm pretty sure my problem is in the specification of the range but not sure what it is.
The first and last selects are just a convention I use.
The second select is to step past a header row.
The third select is to increment the row.
If there is a simpler way to do this, please let me know.
I can't reproduce the error you mention, your code seems to run as is.
That said there a many ways to improve this code
Avoid Select (as mentioned in comments)
The Application object offers Min and Max functions, no need to use WorksheetFunctions for these
Better approach to range references is a combination of Offset and Resize
Your code, refactored to used these techniques
Sub Demo()
Dim ws As Worksheet
Dim rng As Range
Dim rw As Range
' Get a reference to the source data range
Set ws = ActiveSheet
With ws
Set rng = .Cells(2, 1)
' Just in case there is only one data row
If Not IsEmpty(rng.Offset(1, 0)) Then
Set rng = .Range(rng, rng.End(xlDown))
End If
End With
' Loop the range
For Each rw In rng.Rows
rw.Offset(0, 11) = Application.Min(rw.Offset(0, 1).Resize(, 5))
rw.Offset(0, 12) = Application.Max(rw.Offset(0, 1).Resize(, 5))
Next
End Sub
That said, you can go further and use a Variant Array approach. This runs much faster than looping a range (impact will vary depending on number of data rows)
Sub Demo2()
Dim ws As Worksheet
Dim rng As Range
Dim dat As Variant
Dim res As Variant
Dim i As Long
' Get a reference to the source data range
Set ws = ActiveSheet
With ws
Set rng = .Cells(2, 1)
' Just in case there is only one data row
If Not IsEmpty(rng.Offset(1, 0)) Then
Set rng = .Range(rng, rng.End(xlDown))
End If
End With
' Set up source and result arrays
dat = rng.Offset(, 2).Resize(, 5).Value
ReDim res(1 To UBound(dat, 1), 1 To 2)
With Application
' Loop the array
For i = 1 To UBound(dat, 1)
res(i, 1) = .Min(.Index(dat, i))
res(i, 2) = .Max(.Index(dat, i))
Next
End With
' Return results to sheet
rng.Offset(0, 11).Resize(, 2) = res
End Sub
Another technique is to avoid a loop entirely by (temporarily) placing formula into the sheet in one go. This will be much faster still (for more than a few data rows)
Sub Demo3()
Dim ws As Worksheet
Dim rng As Range
Dim rw As Range
' Get a reference to the source data range
Set ws = ActiveSheet
With ws
Set rng = .Cells(2, 1)
If Not IsEmpty(rng.Offset(1, 0)) Then
Set rng = .Range(rng, rng.End(xlDown))
End If
End With
' Place formulas into sheet
rng.Offset(0, 11).FormulaR1C1 = "=Min(RC[-9]:RC[-5])"
rng.Offset(0, 12).FormulaR1C1 = "=Max(RC[-9]:RC[-5])"
' replace formulas with values (optional)
rng.Value = rng.Value
End Sub
How about this?
Sub MinAndMax()
Dim rng As Range
Set rng = Range("A2:A" & Range("A2").End(xlDown).Row)
Range("L1") = WorksheetFunction.Min(rng)
Range("M1") = WorksheetFunction.Max(rng)
End Sub
Define the range upfront
Write the min and max to the cells directly
Related
I'm trimming all entries in a column (except header in row 1) to the last four characters using:
Range("A2").Select
Do While ActiveCell <> ""
ActiveCell = Right(ActiveCell.Value, 4)
ActiveCell.Offset(1, 0).Select
Loop
It works, but is quite slow on large files. Does anyone know how I could speed this up?
Along with the linked answers in the comments, I prefer to use variant arrays when looping. They are stored in memory. Whenever one accesses the work sheet vba needs to slow down. by limiting our interactions we can speed things up.
Sub right4()
With ActiveSheet 'better to use actual worksheet ie Worksheets("Sheet1")
Dim rng As Range
Set rng = .Range("A2", .Cells(.Rows.Count, 1).End(xlUp))
End With
Dim rngarr As Variant
rngarr = rng.Value
Dim i As Long
For i = 1 To UBound(rngarr, 1)
rngarr(i, 1) = Right(rngarr(i, 1), 4)
Next i
rng.Value = rngarr
End Sub
If one wants to not use a loop:
Sub right4()
With ActiveSheet 'better to use actual worksheet ie Worksheets("Sheet1")
Dim rng As Range
Set rng = .Range("A2", .Cells(.Rows.Count, 1).End(xlUp))
rng.Value = .Evaluate("INDEX(RIGHT(" & rng.Address(0, 0) & ",4),)")
End With
End Sub
Though I would guess that timing on the second will come a close second to the first code.
I want to develop a macro to check if values from one column in a sheet are found as substrings in the columns of another sheet.
So, I want to check each cell of my first column, and if it's not empty, compare it to every cell in the second sheet, of columns 1 and 3.
For this I used a "for each" loop, and then a "for" loop, with i from 1 to Rows.Count.
This is where it gets tricky as I'm not sure if it's the right way to approach this. Also, the parameters of the instr() function to check if values are found as substrings don't seem to match, as I get a "type mismatch" error when trying to run the code.
Sub test()
Set Wks1 = Worksheets("Sheet1")
Set Wks2 = Worksheets("Sheet2")
Dim i As Long
Dim rng As Range, cell As Range
Set rng = Wks1.Range("C3:O69")
For Each cell In rng
If Not (IsEmpty(cell.Value)) Then
For i = 1 To Wks2.Rows.Count
If (InStr(Cells(i, 1), cell.Value, 1) <> 0) Or (InStr(Cells(i, 3), _
cell.Value, 1) <> 0) Then
Cells(i, 4) = "String contains substring"
End If
Next i
End If
Next cell
End Sub
That should work
Sub test()
Dim i As Long
Dim rng As Range, cell As Range
Dim lastRow as Long
Set Wks1 = Worksheets("Sheet1")
Set Wks2 = Worksheets("Sheet2")
Set rng = Wks1.Range("C3:O69")
lastRow = Wks2.Cells(Rows.Count, 1).End(xlUp).row
For Each cell In rng
If Not (IsEmpty(cell.Value)) Then
For i = 1 To lastRow
With Wks2
If (InStr(.Cells(i, 1), cell.Value) <> 0) Or (InStr(.Cells(i, 3), cell.Value) <> 0) Then
.Cells(i, 4) = "String contains substring"
End If
End With
Next i
End If
Next cell
End Sub
I'm trying to compare sheet1 "A" column values to sheet2 "E:E" column values and copy/paste the whole line of every match to sheet3. Please help me to complete this task. I'm very new to VBA.
Thank you very much in advance!
Sub DelDups_TwoLists()
Dim iListCount As Integer
Dim iCtr As Integer
' Turn off screen updating to speed up macro.
Application.ScreenUpdating = False
' Get count of records to search through (list that will be deleted).
iListCount = Sheets("sheet1").Cells(Rows.Count, "A").End(xlUp).Row
' Loop through the "master" list.
For Each x In Sheets("Sheet2").Range("E:E" & Sheets("Sheet1").Cells(Rows.Count, "A").End(xlUp).Row)
' Loop through all records in the second list.
For iCtr = iListCount To 1 Step -1
' Do comparison of next record.
' To specify a different column, change 1 to the column number.
If x.Value = Sheets("Sheet1").Cells(iCtr, 1).Value Then
' If match is true then delete row.
Sheets("Sheet1").Cells(iCtr, 1).EntireRow.Copy
Sheets("Sheet3").Select.Paste
End If
Next iCtr
Next
Application.ScreenUpdating = True
MsgBox "Done!"
End Sub
Sub DelDupsTwoLists()
Dim lastRowWs1 As Long, lastRowWs2 As Long
Dim ws1 As Worksheet, ws2 As Worksheet, ws3 As Worksheet
Set ws1 = Worksheets(1)
Set ws2 = Worksheets(2)
Set ws3 = Worksheets(3)
lastRowWs1 = LastRow(ws1.Name, 1)
lastRowWs2 = LastRow(ws2.Name, 5) 'E = 5
Dim myCell1 As Range, myCell2 As Range
Dim ws1Range As Range, ws2Range As Range
Set ws1Range = ws1.Range(ws1.Cells(1, "A"), ws1.Cells(lastRowWs1, 1))
Set ws2Range = ws2.Range(ws2.Cells(1, "E"), ws2.Cells(lastRowWs2, 1))
Dim rangeToDelete As Range
For Each myCell1 In ws1Range
For Each myCell2 In ws2Range
If myCell1.Value = myCell2.Value Then
Dim lastRowWs3: lastRowWs3 = LastRow(ws3.Name, 1) + 1
myCell2.EntireRow.Copy Destination:=ws3.Cells(lastRowWs3, 1)
If Not rangeToDelete Is Nothing Then
Set rangeToDelete = Union(rangeToDelete, myCell2.EntireRow)
Else
Set rangeToDelete = myCell2.EntireRow
End If
End If
Next
Next
If Not rangeToDelete Is Nothing Then
Debug.Print "Deleting rangeToDelete - "; rangeToDelete.Address
rangeToDelete.Delete
End If
Debug.Print "Done!"
End Sub
Public Function LastRow(wsName As String, Optional columnToCheck As Long = 1) As Long
Dim ws As Worksheet
Set ws = Worksheets(wsName)
LastRow = ws.Cells(ws.Rows.Count, columnToCheck).End(xlUp).Row
End Function
Pretty much I rewrote the whole code from scratch. It pretty much uses the initial n2 complexity, but is rather faster than that, because the deletion of the rows in WorkSheet(2) is done in a single last step rangeToDelete.Delete, which saves a lot of time.
Pretty much, the code defines 2 ranges with which is works - ws1Range and ws2Range, using the LastRow function. Once it defines them, it starts looping through them and comparing them. Hence the n2 complexity. In case of equal values, the row is copied and the cell is added to the rangeToDelete.
Note - it will probably not work as "out of the box solution", but try to debug further with F8 and see what happens.
Additionally:
Using Integer is not a great idea in VBA.
"_" in the Sub name is used for Events in VBA, thus it is not a great idea to use it. (although it works)
How to avoid using Select in Excel VBA
Give this a try (see comments in code for more details):
Sub DelDups_TwoLists()
' Turn off screen updating to speed up macro.
Application.ScreenUpdating = False
With ActiveWorkbook
Dim wsSrc As Worksheet: Set wsSrc = .Sheets("Sheet1") 'declare and set the source worksheet
Dim wsDst As Worksheet: Set wsDst = .Sheets("Sheet3") 'declare and set the destination worksheet
Dim R1 As Long, R2 As Long, C As Long, lRow As Long, lCol As Long 'declare variables to use
With wsSrc
lCol = .Cells(1, Columns.Count).End(xlToLeft).Column 'get the last column value in the source sheet, at row 1, will reuse this laster
Dim arrData_1 As Variant: arrData_1 = .Range(.Cells(1, 1), .Cells(.Cells(Rows.Count, 1).End(xlUp).Row, 1)) 'declare and allocate the source data to an array
End With
With .Sheets("Sheet2")
Dim arrData_2 As Variant: arrData_2 = .Range("E1:E" & .Cells(Rows.Count, 1).End(xlUp).Row) 'declare and allocate the compare data to an array
End With
End With
With wsDst
For R1 = LBound(arrData_1) To UBound(arrData_1) 'for each row in the source data
For R2 = LBound(arrData_2) To UBound(arrData_2) 'for each row in the compare data
If arrData_1(R1, 2) = arrData_2(R2, 1) Then 'if there is a match
lRow = .Cells(Rows.Count, 1).End(xlUp).Row + 1 'get the last row in the destination sheet
.Range(.Cells(lRow, 1), .Cells(lRow, lCol)).Value = _
wsSrc.Range(wsSrc.Cells(R1, 1), wsSrc.Cells(R1, lCol)).Value 'allocate the matching values
Exit For 'exit early here if there is a match, go to next row to check
End If
Next R2
Next R1
End With
Application.ScreenUpdating = True
MsgBox "Done!"
End Sub
I believe I am correctly coping the range I need from my second worksheet but I do not understand where/how the paste works. I am trying to paste to a different worksheet.
Sub Click()
Dim rng As Range
Dim dat As Variant
With Worksheets(2)
Set rng = .[d31:o31]
dat = rng
rng.Clear
.Range(rng.Cells(1, 1), Cells(rng.Row, rng.Rows.Count)) = Application.Transpose(dat)
End With
End Sub
Use the Upper Boundaries of the array to determine the size of the destination.
Explicit parent worksheet references will help to determine proper range references.
Sub Click()
Dim dat As Variant
With Worksheets(2)
with .range(.cells(31, "D"), .cells(31, "O"))
dat = .value
.Clear
.Cells(1, 1).resize(ubound(dat, 2), ubound(dat, 1)) = _
Application.Transpose(dat)
end with
End With
End Sub
Within .range(.cells(31, "D"), .cells(31, "O")), .Cells(1, 1) is .cells(31, "D") on the worksheet.
I found this code on another post that will single out a line - but it deletes all others EXCEPT the specified line.
I work with large numbers of address lists and I need something I can run that will identify and delete rows with addresses that we've been asked not to mail to. I've just discovered VBA some I'm extremely green. But I'd like to have a module that allows me to add multiple addresses as the list grows.
Sub DeleteRows()
Dim i as long, LastRow As long
with activesheet
LastRow = .Cells(.Rows.Count, 1).End(xlUp).row
For i = LastRow to 2 step -1
If .Cells(i, 1).Value <> "certain value" Then
.Rows(i).Delete
End If
Next i
End With
End Sub
Simply change this:
If .Cells(i, 1).Value <> "certain value" Then - where cell value different then "certain value"
to this:
If .Cells(i, 1).Value = "certain value" Then - where cell value equal to "certain value"
Sub DeleteRows()
Dim i As Long, LastRow As Long
With ActiveSheet
LastRow = .Cells(.Rows.count, 1).End(xlUp).row
For i = LastRow To 2 Step -1
If .Cells(i, 1).value = "certain value" Then
.Rows(i).Delete
End If
Next i
End With
End Sub
You could use Union to gather the qualifying rows in one go and delete. Also, have a separate sheet where you store the addresses to match on. Read those addresses into an array, then loop the sheet where data is to be deleted from and check whether a given address is found in your array. If found, use Union to store that cell for later deletion.
At the end of looping the data to check, delete the rows associated with the stored cells in the union'd range object in one go.
Option Explicit
Public Sub DeleteThemRows()
Dim arr(), unionRng As Range, i As Long, lastRow As Long, rng As Range
Dim wsAddress As Worksheet, wsDelete As Worksheet
Set wsAddress = ThisWorkbook.Worksheets("Addresses")
Set wsDelete = ThisWorkbook.Worksheets("DataToDelete")
With wsAddress '<= Assume addresses stored in column A starting from cell A1
lastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
Select Case lastRow
Case 1
ReDim arr(1, 1): arr(1, 1) = .Range("A1").Value
Case Is >= 2
arr = .Range("A1:A" & lastRow).Value
End Select
arr = Application.WorksheetFunction.Index(arr, 0, 1)
End With
With wsDelete '<==Assume address column to check is column A
lastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
Dim loopRange As Range
Set loopRange = .Range("A1:A" & lastRow)
If Application.WorksheetFunction.CountA(loopRange) = 0 Then Exit Sub
For Each rng In loopRange.SpecialCells(xlCellTypeConstants)
If Not IsError(Application.Match(rng.Value, arr, 0)) Then
If Not unionRng Is Nothing Then
Set unionRng = Union(unionRng, rng)
Else
Set unionRng = rng
End If
End If
Next
End With
If Not unionRng Is Nothing Then unionRng.EntireRow.Delete
End Sub
You could use Debug.Print unionRng.Address first to check what will be deleted.
Sub FastDelete()
Dim rng As Range, rngData As Range, rngVisible As Range
Const CRITERIA$ = "SOME_VALUE"
Set rng = Range("A1").CurrentRegion '//Whole table
With rng '//Table without header
Set rngData = .Offset(1).Resize(.Rows.Count - 1)
End With
'// Filter by column "A"
rng.AutoFilter Field:=1, Criteria1:=CRITERIA
On Error Resume Next '//In case if no values filtered
Set rngVisible = rng.SpecialCells(xlCellTypeVisible)
If Err = 0 Then
rngVisible.EntireColumn.Delete
End If
On Error GoTo 0
End Sub