Visual Basic Max function throwing 1004 error - vba

I have 2 table "PurposefulSample" and "scaled". This macro is being written for scaled.
Now when I run this one, it throws a 1004 at rowMax = Application.WorksheetFunction.Max(Range(src.Cells(curRow, 11), src.Cells(curRow, 37))).
Some cells in the given range are also strings. Few others are #N/A too.
Noob in VB. Really appreciate any help.
Sub stdInScaled()
Dim curCol, curRow
curRow = 2
Dim src As Worksheet
Set src = Worksheets("PurposefulSample")
Do While (src.Cells(curRow, 1).Value <> "")
curCol = 11
Do While (CStr(src.Cells(curRow, curCol).Value) <> "")
If (IsNumeric(src.Cells(curRow, curCol).Value)) Then
Dim rowMax
rowMax = Application.WorksheetFunction.Max(Range(src.Cells(curRow, 11), src.Cells(curRow, 37)))
If (rowMax > 1) Then
Cells(curRow, curCol).Value = 100 * CLng(src.Cells(curRow, curCol).Value) / rowMax
Else
Cells(curRow, curCol).Value = "No Business"
End If
Else
Cells(curRow, curCol).Value = "Data NA"
End If
curCol = curCol + 1
Loop
curRow = curRow + 1
Loop
End Sub

Two things:
It is always good practice to qualify the parentage of all range objects, just to ensure no mix up of which cell is being referenced.
With the chance of errors being in the data, an array formula Max will need to used to skip the errors. Also on the formula lets move it up one loop so it does not recalc the same answer every column.
code:
Sub stdInScaled()
Dim curCol, curRow
curRow = 2
Dim src As Worksheet
Set src = Worksheets("PurposefulSample")
Dim trgt As Worksheet
Set trgt = Worksheets("scaled")
Do While (src.Cells(curRow, 1).Value <> "")
curCol = 11
Dim rowMax
Dim rng As String: rng = src.Range(src.Cells(curRow, 11), src.Cells(curRow, 37)).Address
rowMax = src.Evaluate("Max(IF(isnumber(" & rng & ")," & rng & "))")
Do While (CStr(src.Cells(curRow, curCol).Value) <> "")
If (IsNumeric(src.Cells(curRow, curCol).Value)) Then
If (rowMax > 1) Then
trgt.Cells(curRow, curCol).Value = 100 * CLng(src.Cells(curRow, curCol).Value) / rowMax
Else
trgt.Cells(curRow, curCol).Value = "No Business"
End If
Else
trgt.Cells(curRow, curCol).Value = "Data NA"
End If
curCol = curCol + 1
Loop
curRow = curRow + 1
Loop
End Sub

Related

For Nested Loops in Excel VBA

I have a For loop to look at columns and need to skip two columns. When I run this code, the second For loop (with iCol) does not work.
The code within the loop works fine when I tested outside of the loop. I have tried different options to exclude the two columns from the For loop (select case) but nothing works.
Dim rng As Range
Dim n As Long
Dim iRow As Long
Dim iCol As Long
Dim NameColNum As Integer
Dim LNameColNum As Integer
Dim DoBColNum As Integer
Dim SColNum As Integer
Dim JColNum As Integer
' Sets data range
Set rng = Range(Range("A1"), Range("A" & Rows.Count).End(xlUp))
NameColNum = Application.Match("First Name", rng.EntireRow(1), 0)
LNameColNum = Application.Match("Last Name", rng.EntireRow(1), 0)
DoBColNum = Application.Match("Birth Date", rng.EntireRow(1), 0)
' For S and J cases
SColNum = Application.Match("Created User ID", rng.EntireRow(1), 0)
JColNum = Application.Match("W Name", rng.EntireRow(1), 0)
For iRow = 2 To rng.Rows.Count
If rng.Cells(iRow, NameColNum) = rng.Cells(iRow + 1, NameColNum) And _
rng.Cells(iRow, LNameColNum) = rng.Cells(iRow + 1, LNameColNum) And _
rng.Cells(iRow, DoBColNum) = rng.Cells(iRow + 1, DoBColNum) Then
If rng.Cells(iRow, SColNum).Value = "STAGE" Then
rng.EntireRow(iRow).Interior.ColorIndex = 3
rng.EntireRow(iRow + 1).Interior.ColorIndex = 3
End If
If rng.Cells(iRow, JColNum) = "Smith" Then
rng.EntireRow(iRow).Interior.ColorIndex = 4
rng.EntireRow(iRow + 1).Interior.ColorIndex = 4
End If
For iCol = DoBColNum + 1 To rng.Columns.Count
If iCol <> SColNum And iCol <> JColNum Then
If rng.Cells(iRow, iCol).Value <> rng.Cells(iRow + 1, iCol).Value And _
rng.EntireRow(iRow).Interior.ColorIndex = -4142 Then
rng.EntireRow(iRow).Interior.ColorIndex = iCol
rng.EntireRow(iRow + 1).Interior.ColorIndex = iCol
End If
End If
Next 'iCol
End If
Next 'iRow
rng.Columns.Count is always going to equal 1, because you limited rng to column A on your Set line. Because of this, your second loop will never run (You're trying to loop 4 to 1, etc.).
Instead, change Set rng = Range(Range("A1"), Range("A" & Rows.Count).End(xlUp)) to include all columns that your working with, and get the last row value from column A on another line.
Suggested fix:
Dim lastrow As Long
lastrow = Cells(Rows.Count, "A").End(xlUp).Row
' Sets data range
Set rng = Range(Range("A1"), Range("S" & lastrow))

how to match 2 criteria in macro

I currently have the following codes that look up the column for Columbus. But how do I specify that I only want to look up the column for Columbus in Ohio by also referring to row 4 (State)?
Amount = WorksheetFunction.Match("Columbus", Rows("5:5"), 0)
Try Looping thru all the records -
Dim Amount As Variant
Dim lngRow as long
lngRow = ActiveSheet.Cells(Rows.Count, 1).End(xlUp).Row
For i = 2 To lngRow 'Considering row 1 has headers
If ActiveSheet.Cells(i, 5) = "Columbus" And ActiveSheet.Cells(i, 4) = "Ohio" Then
Amount = i
Exit For
End If
Next i
Thanks
Use Variant Arrays and cycle through that it will be quicker:
With Worksheets("Sheet1") 'Change to your sheet
Dim rngArr() As Variant
rngArr = .Range(.Cells(4, 1), .Cells(5, .Columns.Count).End(xlToLeft)).Value
Dim i As Long
For i = 1 To UBound(rngArr, 2)
If rngArr(1, i) = "Ohio" And rngArr(2, i) = "Columbus" Then Exit For
Next i
If i <= UBound(rngArr, 2) Then
Dim Amount As Long
Amount = i
Else
MsgBox "Not Found"
End If
End With

Speed Up Matching program in Excel VBA

I am writing a VBA code on excel using loops to go through 10000+ lines.
Here is an example of the table
And here is the code I wrote :
Sub Find_Matches()
Dim wb As Workbook
Dim xrow As Long
Set wb = ActiveWorkbook
wb.Worksheets("Data").Activate
tCnt = Sheets("Data").UsedRange.Rows.Count
Dim e, f, a, j, h As Range
xrow = 2
Application.ScreenUpdating = False
Application.Calculation = xlManual
For xrow = 2 To tCnt Step 1
Set e = Range("E" & xrow)
Set f = e.Offset(0, 1)
Set a = e.Offset(0, -4)
Set j = e.Offset(0, 5)
Set h = e.Offset(0, 3)
For Each Cell In Range("E2:E" & tCnt)
If Cell.Value = e.Value Then
If Cell.Offset(0, 1).Value = f.Value Then
If Cell.Offset(0, -4).Value = a.Value Then
If Cell.Offset(0, 5).Value = j.Value Then
If Cell.Offset(0, 3).Value = h.Value Then
If (e.Offset(0, 7).Value) + (Cell.Offset(0, 7).Value) = 0 Then
Cell.EntireRow.Interior.Color = vbYellow
e.EntireRow.Interior.Color = vbYellow
End If
End If
End If
End If
End If
End If
Next
Next
End Sub
As you can imagine, this is taking a lot of time to go through 10000+ lines and I would like to find a faster solution. There must be a method I don't think to avoid the over looping
Here are the condition :
For each line, if another line anywhere in the file has the exact same
:
Buyer ID (col. E)
`# purchased (col. F)
Product ID (col.A)
Payment (col. J)
Date purchased (col. H)
Then, if the SUM of the Amount (col. L) the those two matching line is
0, then color both rows in yellow.
Note that extra columns are present and not being compared (eg- col. B) but are still important for the document and cannot be deleted to ease the process.
Running the previous code, in my example, row 2 & 5 get highlighted :
This is using nested dictionaries and arrays to check all conditions
Timer with my test data: Rows: 100,001; Dupes: 70,000 - Time: 14.217 sec
Option Explicit
Public Sub FindMatches()
Const E = 5, F = 6, A = 1, J = 10, H = 8, L = 12
Dim ur As Range, x As Variant, ub As Long, d As Object, found As Object
Set ur = ThisWorkbook.Worksheets("Data").UsedRange
x = ur
Set d = CreateObject("Scripting.Dictionary")
Set found = CreateObject("Scripting.Dictionary")
Dim r As Long, rId As String, itm As Variant, dupeRows As Object
For r = ur.Row To ur.Rows.Count
rId = x(r, E) & x(r, F) & x(r, A) & x(r, J) & x(r, H)
If Not d.Exists(rId) Then
Set dupeRows = CreateObject("Scripting.Dictionary")
dupeRows(r) = 0
Set d(rId) = dupeRows
Else
For Each itm In d(rId)
If x(r, L) + x(itm, L) = 0 Then
found(r) = 0
found(itm) = 0
End If
Next
End If
Next
Application.ScreenUpdating = False
For Each itm In found
ur.Range("A" & itm).EntireRow.Interior.Color = vbYellow
Next
Application.ScreenUpdating = True
End Sub
Before
After
I suggest a different approach altogether: add a temporary column to your data that contains a concatenation of each cell in the row. This way, you have:
A|B|C|D|E
1|Mr. Smith|500|A|1Mr. Smith500A
Then use Excel's conditional formatting on the temporary column, highlighting duplicate values. There you have your duplicated rows. Now it's only a matter of using a filter to check which ones have amounts equal to zero.
You can use the CONCATENATE function; it requires you to specify each cell separately and you can't use a range, but in your case (comparing only some of the columns) it seems like a good fit.
Maciej's answer is easy to implement (if you can add columns to your data without interrupting anything), and I would recommend it if possible.
However, for the sake of answering your question, I will contribute a VBA solution as well. I tested it on dataset that is a bit smaller than yours, but I think it will work for you. Note that you might have to tweak it a little (which row you start on, table name, etc) to fit your workbook.
Most notably, the segment commented with "Helper column" is something you most likely will have to adjust - currently, it compares every cell between A and H for the current row, which is something you may or may not want.
I've tried to include a little commentary in the code, but it's not much. The primary change is that I'm using in-memory processing of an array rather than iterating over a worksheet range (which for larger datasets should be exponentially faster).
Option Base 1
Option Explicit
' Uses ref Microsoft Scripting Runtime
Sub Find_Matches()
Dim wb As Workbook, ws As Worksheet
Dim xrow As Long, tCnt As Long
Dim e As Range, f As Range, a As Range, j As Range, h As Range
Dim sheetArr() As Variant, arr() As Variant
Dim colorTheseYellow As New Dictionary, colorResults() As String, dictItem As Variant
Dim arrSize As Long, i As Long, k As Long
Dim c As Variant
Set wb = ThisWorkbook
Set ws = wb.Worksheets("Data")
ws.Activate
tCnt = ws.UsedRange.Rows.Count
xrow = 2
Application.ScreenUpdating = False
Application.Calculation = xlManual
' Read range into an array so we process in-memory
sheetArr = ws.Range("A2:H" & tCnt)
arrSize = UBound(sheetArr, 1)
' Build new arr with "helper column"
ReDim arr(1 To arrSize, 1 To 9)
For i = 1 To arrSize
For k = 1 To 8
arr(i, k) = sheetArr(i, k)
arr(i, 9) = CStr(arr(i, 9)) & CStr(arr(i, k)) ' "Helper column"
Next k
Next i
' Iterate over array & build collection to indicate yellow lines
For i = LBound(arr, 1) To UBound(arr, 1)
If Not colorTheseYellow.Exists(i) Then colorResults = Split(ReturnLines(arr(i, 9), arr), ";")
For Each c In colorResults
If Not colorTheseYellow.Exists(CLng(c)) Then colorTheseYellow.Add CLng(c), CLng(c)
Next c
Next i
' Enact row colors
For Each dictItem In colorTheseYellow
'Debug.Print "dict: "; dictItem
If dictItem <> 0 Then ws.ListObjects(1).ListRows(CLng(dictItem)).Range.Interior.Color = vbYellow
Next dictItem
End Sub
Function ReturnLines(ByVal s As String, ByRef arr() As Variant) As String
' Returns a "Index;Index" string indicating the index/indices where the second, third, etc. instance(s) of s was found
' Returns "0;0" if 1 or fewer matches
Dim i As Long
Dim j As Long
Dim tmp As String
ReturnLines = 0
j = 0
tmp = "0"
'Debug.Print "arg: " & s
For i = LBound(arr, 1) To UBound(arr, 1)
If arr(i, 9) = s Then
j = j + 1
'Debug.Print "arr: " & arr(i, 9)
'Debug.Print "ReturnLine: " & i
tmp = tmp & ";" & CStr(i)
End If
Next i
'If Left(tmp, 1) = ";" Then tmp = Mid(tmp, 2, Len(tmp) - 1)
'Debug.Print "tmp: " & tmp
If j >= 2 Then
ReturnLines = tmp
Else
ReturnLines = "0;0"
End If
End Function
On my simple dataset, it yields this result (marked excellently with freehand-drawn color indicators):
Thanks everybody for your answers,
Paul Bica's solution actually worked and I am using a version of this code now.
But, just to animate the debate, I think I also found another way around my first code, inspired by Maciej's idea of concatenating the cells and using CStr to compare the values and, of course Vegard's in-memory processing by using arrays instead of going through the workbook :
Sub Find_MatchesStr()
Dim AmountArr(300) As Variant
Dim rowArr(300) As Variant
Dim ws As Worksheet
Dim wb As Workbook
Set ws = ThisWorkbook.Sheets("Data")
ws.Activate
Range("A1").Select
rCnt = ws.Cells.SpecialCells(xlCellTypeLastCell).Row
For i = 2 To rCnt
If i = rCnt Then
Exit For
Else
intCnt = 0
strA = ws.Cells(i, 1).Value
strE = ws.Cells(i, 5).Value
strF = ws.Cells(i, 6).Value
strH = ws.Cells(i, 8).Value
strL = ws.Cells(i, 10).Value
For j = i To rCnt - 1
strSearchA = ws.Cells(j, 1).Value
strSearchE = ws.Cells(j, 5).Value
strSearchF = ws.Cells(j, 6).Value
strSearchH = ws.Cells(j, 8).Value
strSearchL = ws.Cells(j, 10).Value
If CStr(strE) = CStr(strSearchE) And CStr(strA) = CStr(strSearchA) And CStr(strF) = CStr(strSearchF) And CStr(strH) = CStr(strSearchH) And CStr(strL) = CStr(strSearchL) Then
AmountArr(k) = ws.Cells(j, 12).Value
rowArr(k) = j
intCnt = intCnt + 1
k = k + 1
Else
Exit For
End If
Next
strSum = 0
For s = 0 To UBound(AmountArr)
If AmountArr(s) <> "" Then
strSum = strSum + AmountArr(s)
Else
Exit For
End If
Next
strAppenRow = ""
For b = 0 To UBound(rowArr)
If rowArr(b) <> "" Then
strAppenRow = strAppenRow & "" & rowArr(b) & "," & AmountArr(b) & ","
Else
Exit For
End If
Next
If intCnt = 1 Then
Else
If strSum = 0 Then
For rn = 0 To UBound(rowArr)
If rowArr(rn) <> "" Then
Let rRange = rowArr(rn) & ":" & rowArr(rn)
Rows(rRange).Select
Selection.Interior.Color = vbYellow
Else
Exit For
End If
Next
Else
strvar = ""
strvar = Split(strAppenRow, ",")
For ik = 1 To UBound(strvar)
If strvar(ik) <> "" Then
strVal = CDbl(strvar(ik))
For ik1 = ik To UBound(strvar)
If strvar(ik1) <> "" Then
strVal1 = CDbl(strvar(ik1))
If strVal1 + strVal = 0 Then
Let sRange1 = strvar(ik - 1) & ":" & strvar(ik - 1)
Rows(sRange1).Select
Selection.Interior.Color = vbYellow
Let sRange = strvar(ik1 - 1) & ":" & strvar(ik1 - 1)
Rows(sRange).Select
Selection.Interior.Color = vbYellow
End If
Else
Exit For
End If
ik1 = ik1 + 1
Next
Else
Exit For
End If
ik = ik + 1
Next
End If
End If
i = i + (intCnt - 1)
k = 0
Erase AmountArr
Erase rowArr
End If
Next
Range("A1").Select
End Sub
I still have some mistakes (rows not higlighted when they should be), the above code is not perfect, but I thought it'd be OK to give you an idea of where I was going before Paul Bica's solution came in.
Thanks again !
If your data is only till column L, then use below code, I found it is taking less time to run....
Sub Duplicates()
Application.ScreenUpdating = False
Dim i As Long, lrow As Long
lrow = Cells(Rows.Count, 1).End(xlUp).Row
Range("O2") = "=A2&E2&F2&J2&L2"
Range("P2") = "=COUNTIF(O:O,O2)"
Range("O2:P" & lrow).FillDown
Range("O2:O" & lrow).Copy
Range("O2:O" & lrow).PasteSpecial xlPasteValues
Application.CutCopyMode = False
For i = 1 To lrow
If Cells(i, 16) = 2 Then
Cells(i, 16).EntireRow.Interior.Color = vbYellow
End If
Next
Application.ScreenUpdating = True
Range("O:P").Delete
Range("A1").Select
MsgBox "Done"
End Sub

Combining consecutive values in a column with the help of VBA

I have a data like this :
A049
A050
A051
A053
A054
A055
A056
A062
A064
A065
A066
And I want the output like :
As you can see, I want the ranges which are in consecutive order
I am trying some thing like this:
Private Sub CommandButton1_Click()
Set wb = ThisWorkbook
lastRow = wb.Sheets("Sheet1").Range("A" & wb.Sheets("Sheet1").Rows.Count).End(xlUp).Row
For i = 2 To lastRow
r = wb.Sheets("Sheet1").Range("A" & i).Value
If wb.Sheets("Sheet1").Range("A" & i).Value = wb.Sheets("Sheet1").Range("A" & i+1).Value
Next i
End Sub
But not helping me
Am feeling charitable so have tried some code which should work. It assumes your starting values are in A1 down and puts results in C1 down.
Sub x()
Dim v1, v2(), i As Long, j As Long
v1 = Range("A1", Range("A" & Rows.Count).End(xlUp)).Value
ReDim v2(1 To UBound(v1, 1), 1 To 2)
For i = LBound(v1, 1) To UBound(v1, 1)
j = j + 1
v2(j, 1) = v1(i, 1)
If i <> UBound(v1, 1) Then
Do While Val(Right(v1(i + 1, 1), 3)) = Val(Right(v1(i, 1), 3)) + 1
i = i + 1
If i = UBound(v1, 1) Then
v2(j, 2) = v1(i, 1)
Exit Do
End If
Loop
End If
If v1(i, 1) <> v2(j, 1) Then v2(j, 2) = v1(i, 1)
Next i
Range("C1").Resize(j, 2) = v2
End Sub
Try the below code
Private Sub CommandButton1_Click()
Set wb = ThisWorkbook
lastRow = wb.Sheets("Sheet1").Range("A" & wb.Sheets("Sheet1").Rows.Count).End(xlUp).Row
Dim lastNum, Binsert As Integer
Dim firstCell, lastCell, currentCell As String
Binsert = 1
lastNum = getNum(wb.Sheets("Sheet1").Range("A1").Value)
firstCell = wb.Sheets("Sheet1").Range("A1").Value
For i = 2 To lastRow
activeNum = getNum(wb.Sheets("Sheet1").Range("A" & i).Value)
currentCell = wb.Sheets("Sheet1").Range("A" & i).Value
If (activeNum - lastNum) = 1 Then
'nothing
Else
lastCell = wb.Sheets("Sheet1").Range("A" & (i - 1)).Value
wb.Sheets("Sheet1").Range("B" & Binsert).FormulaR1C1() = firstCell
If (firstCell <> lastCell) Then
wb.Sheets("Sheet1").Range("C" & Binsert).FormulaR1C1() = lastCell
End If
Binsert = Binsert + 1
firstCell = wb.Sheets("Sheet1").Range("A" & i).Value
End If
lastNum = activeNum
Next i
'last entry
wb.Sheets("Sheet1").Range("B" & Binsert).FormulaR1C1() = firstCell
If (firstCell <> currentCell) Then
wb.Sheets("Sheet1").Range("C" & Binsert).FormulaR1C1() = currentCell
End If
End Sub
Public Function getNum(ByVal num As String) As Integer
getNum = Val(Mid(num, 2))
End Function
Another solution. It loops backwards from last row to first row.
Option Explicit
Public Sub FindConsecutiveValues()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Sheet1")
Dim lRow As Long 'find last row
lRow = ws.Range("A" & ws.Rows.Count).End(xlUp).Row
Dim lVal As String 'remember last value (stop value)
lVal = ws.Range("A" & lRow).Value
Const fRow As Long = 2 'define first data row
Dim i As Long
For i = lRow To fRow Step -1 'loop from last row to first row backwards
Dim iVal As Long
iVal = Val(Right(ws.Range("A" & i).Value, Len(ws.Range("A" & i).Value) - 1)) 'get value of row i without A so we can calculate
Dim bVal As Long
bVal = 0 'reset value
If i <> fRow Then 'if we are on the first row there is no value before
bVal = Val(Right(ws.Range("A" & i - 1).Value, Len(ws.Range("A" & i - 1).Value) - 1)) 'get value of row i-1 without A
End If
If iVal - 1 = bVal Then
ws.Rows(i).Delete 'delete current row
Else
If lVal <> ws.Range("A" & i).Value Then 'if start and stop value are not the same …
ws.Range("B" & i).Value = lVal 'write stop value in column B
End If
lVal = ws.Range("A" & i - 1).Value 'remember now stop value
End If
Next i
End Sub

Use VBA open a Excel file, and run a loop, but the loop will be always skipped

I want to run loop on the same workbook, but it also give me nothing. But If I run this VBA directly on the workbook (CGDSOUSD), it works well. So I am wondering how to run VBA after let VBA open a new file.
Dim rownumber As Integer
Dim colnumber As Integer
Dim total As Double
colnumber = 1
For colnumber = 1 To 23
If Cells(8, colnumber) = "DELTA" Then
total = 0
rownumber = 9
Do Until Cells(rownumber, colnumber) = "" And Cells(rownumber + 1, colnumber) = "" And Cells(rownumber + 5, 1) = ""
If Cells(rownumber, 1) = "" And (Cells(rownumber, 7).Value = "DSO TROPS" Or Cells(rownumber, 8).Value = "DSO TROPS" Or Cells(rownumber, 6).Value = "DSO TROPS") Then
total = total + (Cells(rownumber, colnumber).Value)
Else
End If
rownumber = rownumber + 1
Loop
Else
End If
colnumber = colnumber + 1
Next colnumber
total = Round(total, 2) 'will be imputed into E20 in risk tools
MsgBox total
Maybe Do Until is False.
To iterate through cells I always determine the lastrow and use a for loop.
See basic example below.
ps: use ActiveCell and activate one.
'place a value in cell A1 to A4 for test.
Sub test()
Dim lastrow As Long
lastrow = ActiveSheet.Range("A" & Rows.Count).End(xlUp).Row
For i = 1 To lastrow
If Cells(i, 1) <> "" Then
Cells(i, 2).Value = "not empty"
End If
Next i
End Sub