VBA Instr function on 100K+ records - excel

I have 100.000 records/rows with 17 columns. One of these columns needs to be checked to output either a 1 or 0 to the next column. For this I use a loop with the Instr function, but after 10 mins it still isn't outputting anything on my machine and I believe the code is too intensive or slow running it row for row.
Dim rng As Range
Set rng = Range("F:F")
For Each cell In rng
TicketType = cell
If InStr(1, TicketType, "locker", 1) > 0 Then
cell.Offset(0, 1) = 1
Else
cell.Offset(0, 1) = 0
End If
Next
There are only 100 TicketTypes to check however, and based on the names of these TicketTypes it should output a 1 or 0 (match or not). So I was thinking, maybe there is a way to sort the entire table, run through it to see which categories there are, store their vertical ranges, run a check and then output +-10.000 rows at once? I noticed this is instant, so I believe it's really the Instr function that is the bottleneck.

Try this:
Dim rng As Range, f
With ActiveSheet
Set rng = Application.Intersect(.Columns("F"), .UsedRange)
f = "=--NOT(ISERROR(SEARCH(""locker""," & rng(1).Address(False, False) & ")))"
Debug.Print f
rng.Offset(0, 1).Formula = f
rng.Offset(0, 1).Value = rng.Offset(0, 1).Value
End With

Variant array approach
As mentioned by BigBen it's faster than looping through each cell by means of VBA.
Sub VariantArray()
With Sheet1
'~~> Set you relevant range here
Dim lastRow As Long, rng As Range
lastRow = .Range("F" & .Rows.Count).End(xlUp).Row
Set rng = .Range("F1:F" & lastRow)
'~~> create a one based 2-dim datafield array
Dim myArray As Variant
myArray = rng
'~~> check TicketType
Dim i As Long
For i = 1 To UBound(myArray)
myArray(i, 1) = IIf(InStr(1, myArray(i, 1), "locker", 1) > 0, 1, 0)
Next i
'~~> fill target with array values
rng.Offset(0, 1) = myArray
End With
End Sub

you could try filtering:
With Worksheets("actualSheetName") '<-- change "actualSheetName" to your actual sheet name
With .Range("F1", .Cells(.Rows.Count, "F").End(xlUp))
.Offset(, 1).Value = 0
.AutoFilter Field:=1, Criteria1:="*locker*"
.SpecialCells(xlCellTypeVisible).Offset(, 1) = 1
End With
.AutoFilterMode = False
End With

As suggested by BigBen, a far better solution is the usage of a worksheet function, like Find.All() (at least that how I think it's called). If it finds something, it gives a number, else it gives an error. You might turn this into an interesting formula like this:
=IF(IF.ERR(FIND.ALL("locker";A2);0)=0;0;1)

Related

How to delete a row if every cell in a range contains the same text

Real project sample here: http://s000.tinyupload.com/?file_id=06911274635715855845
Sample here
its all in the title,
Lets say i got a doc with ten columns and three hundred rows, A and B contain a number and C to J can contain many words and sometimes the word "Banana".
I'd like to automate a task that goes line by line on the worksheet and deletes the whole row if every cell between C and J contains "Banana", ignoring A and B.
Usually when i have such a question i submit my ideas but i'm quite stumped here from the get go.
Would you be kind enough to help?
Try the next code, please. It will delete all rows having the same string in columns C to J ("Banana" inclusive...). It would be very fast. The deletion is done at the end, at once:
Edited:
Since, in an worksheet containing tables, the non contiguous entire rows range deletion is not allowed, I adapted the code to test if such a table is involved, intersect the collected range to be deleted (its EntireRow) with the table and delete the intersected table rows.
Please, test next updated code:
Sub testDeleteRowsSameWord()
Dim sh As Worksheet, lastRow As Long, i As Long, rngDel As Range
Set sh = ActiveSheet ' use here your necessary sheet
lastRow = sh.Range("C" & Rows.Count).End(xlUp).Row
For i = 1 To lastRow
If WorksheetFunction.CountIf(sh.Range("D" & i & ":EA" & i), _
sh.Range("D" & i).Value) = 128 Then
If rngDel Is Nothing Then
Set rngDel = sh.Range("A" & i)
Else
Set rngDel = Union(rngDel, sh.Range("A" & i))
End If
End If
Next i
If Not rngDel Is Nothing Then
If sh.ListObjects.Count > 0 Then
If sh.ListObjects.Count > 1 Then MsgBox _
"This solution works only for a table...": Exit Sub
Dim Tbl As ListObject, rngInt As Range
Set Tbl = sh.ListObjects(1)
Set rngInt = Intersect(Tbl.Range, rngDel.EntireRow)
If rngInt.Count > 0 Then
rngInt.Delete xlUp
Else
rngDel.EntireRow.Delete xlUp
End If
Else
rngDel.EntireRow.Delete xlUp
End If
End If
End Sub
They are infinite ways to achieve what you want.
One for example can be something like :
Dim i As Integer, j As Integer
Dim mBanana As Boolean
For i = 299 To 0 Step -1 'rows 1 to 300
mBanana = True
For j = 0 To 7 'columns C to J
If Sheets("nameofyoursheet").Range("C1").Offset(i, j).Value <> "Banana" Then
mBanana = False
End If
Next j
If mBanana = True Then
Sheets("nameofyoursheet").Range("C1").Offset(i, j).EntireRow.Delete
End If
Next i
Note that the numbers of rows and columns are hardcoded in the parameters of the For, you can easily adapt the code.

Copy and paste if one cell is blank and the other is not

So data gets pasted in to column B as the code keeps running it'll do a condition check to see there's any values in column B and paste a value in to the adjacent column A. I need to make it so it does two condition checks:
If there's values in column b, but then to check if there's values in column A before pasting so it doesn't overwrite different data that's been pasted already.
For Each Cell In y.Sheets("Compiled").Range("A:B")
If Range("B:B").Value <> "" And Range("A:A").Value = "" Then
Cell.Offset(0, -1).PasteSpecial xlPasteValues
End If
Next
You were close, don't try to loop over a multiple column range:
Sub Test()
For Each Cell In y.Sheets("Compiled").Range("B:B")
If Cell.Value <> "" And Cell.Offset(0, -1).Value = "" Then
Cell.Offset(0, -1).Value = Cell.Value
End If
Next
End Sub
NOTE: You are looping through every cell in Range("B:B") which is probably unnecessary. It'd be better if you use a lastrow value, or a static range like Range("B2:B1000"). Or you could use a criteria to exit your loop like If Cell.Value = "" Then Exit For.
Here's a version of the code that implements the lastrow value that dwirony mentioned in their answer. This also throws everything in arrays, so it might go a bit faster if you have a really large dataset.
Option Explicit
Sub test()
Dim ACol As Variant
Dim BCol As Variant
Dim lastrow As Long
Dim i As Long
lastrow = Range("B:B").Find("*", searchorder:=xlByRows, searchdirection:=xlPrevious).row
BCol = Range("B1:B" & lastrow).Value
ACol = Range("A1:A" & lastrow).Value
For i = LBound(BCol) To UBound(BCol)
If IsEmpty(ACol(i, 1)) And Not IsEmpty(BCol(i, 1)) Then
ACol(i, 1) = BCol(i, 1)
End If
Next i
Range("A1:A" & lastrow).Value = ACol
End Sub

Fill in Column with values from another column if statement(s)

I currently have a VBScript that takes in an Excel document and re-formats it into another Excel document that's more organized.
This code must also look at the values of the CATALOG column ("B1") and place it in the Drawings column ("M1") ONLY if the beginning of the value starts with "EDASM", "EDBSM" etc., yet the "ED" prefix must be eliminated when it's moved.
For example, Catalog number EDF12-01114 would result in nothing being placed in the drawings column, but with EDSM10265, we would need SM10265 to be placed in the drawings column (drop the "ED").
All I've got so far is this, which isn't even complete:
Set objRange = objWorkSheet.Range("M1").EntireColumn
IF
objWorkSheet.Range("B1").Row = "EDF*" THEN 'Maybe correct-ish? Not sure about syntax
objRange = Null
Else
objRange = ("B1") 'Totally an awful guess, but I have no clue what to put here
End If
I've seen similar code that has loops and whatnot, but none of them seem to be doing what I need to be done. Thank you!
EDIT: Current code based off of BruceWayne's. Still doesn't return anything in Excel datasheet's Drawing column, but it looks like it's closer...
Sub move_Text()
Dim lastRow, nextRow, cel , rng
lastRow = Cells(Rows.Count, 2).End(xlUp).Row ' Since your Col. B is the data, let's find that column's last row
Set rng = Range(Cells(1, 2), Cells(lastRow, 2))
nextRow = 1
For Each cel In rng
If Left(cel.Value, 3) <> "EDF" Then
Cells(nextRow, 13).Value = Mid(cel.Value, 3, Len(cel.Value) - 2)
nextRow = nextRow + 1
End If
Next
End Sub
Another edit!
Catalog column is now "C", not "B". Also, I have two header rows, so the first catalog number is located in "C3".
Thanks again! We're getting closer.
Here's the Google Drive files: https://drive.google.com/folderview?id=0B2MeeQ3BKptFYnZfQWpwbTJxMm8&usp=sharing
IMPORTANT TO REMEMBER
In the Google Drive files: TestScript.vbs is the file where all the code is. When the script is run, select ExcelImport. That should return FinalDocument
I guess this is what you are looking for:
Sub move_Text()
Dim lastRow, nextRow, cel, rng
'get last row with data in Column B
lastRow = Cells(Rows.Count, "B").End(xlUp).Row
'set your range starting from Cell B2
Set rng = Range("B2:B" & lastRow)
'loop through all the cells in the range to check for "EDF" and "ED"
For Each cel In rng
'below condition is to check if the string starts with "EDF"
If cel.Value Like "EDF*" Then
'do nothing
'below condition is to check if the string starts with "ED"
ElseIf cel.Value Like "ED*" Then
'drop first two characters of cell's value and write in Column M
cel.Offset(0, 11).Value = Right(cel.Value, Len(cel.Value) - 2)
'else condition will be executed when none of the above two conditions are satisfied
'else condition is based on the link mentioned in your question that will handle words like "ELECTRICAL BOX"
Else
'write cell's value in Column Q
cel.Offset(0, 11).Value = cel.Value
End If
Next
End Sub
EDIT : For VBScirpt
________________________________________________________________________________
Sub Demo()
Dim lastRow, nextRow, cel, rng
Const xlShiftToRight = -4161
Const xlUp = -4162
Const xlValues = -4163
Const xlWhole = 1
Const xlPrevious = 2
With objWorksheet
'get last row with data in Column B
lastRow = .Cells(.Rows.Count, "C").End(xlUp).Row
'set your range starting from Cell B2
Set rng = .Range("C2:C" & lastRow)
End With
'loop through all the cells in the range to check for "EDF" and "ED"
For Each cel In rng
'below condition is to check if the string starts with "EDF"
If InStr(1, cel.Value, "EDF", 1) = 1 Then
'do nothing
'below condition is to check if the string starts with "ED"
ElseIf InStr(1, cel.Value, "ED", 1) = 1 Then
'drop first two characters of cell's value and write in Column M
cel.Offset(0, 10).Value = Right(cel.Value, Len(cel.Value) - 2)
'else condition will be executed when none of the above two conditions are satisfied
'else condition is based on the link mentioned in your question that will handle words like "ELECTRICAL BOX"
Else
'write cell's value in Column M
cel.Offset(0, 10).Value = cel.Value
End If
Next
End Sub
How's this work for you?
Sub move_Text()
Dim lastRow&, nextRow&
Dim cel As Range, rng As Range
lastRow = Cells(Rows.Count, 2).End(xlUp).Row ' Since your Col. B is the data, let's find that column's last row
Set rng = Range(Cells(1, 2), Cells(lastRow, 2))
nextRow = 1
For Each cel In rng
If Left(cel.Value, 2) = "ED" Then
Cells(nextRow, 13).Value = Mid(cel.Value, 3, Len(cel.Value) - 2)
nextRow = nextRow + 1
End If
Next cel
End Sub
It will set the range to be your Column B, from row 1 to the last row. Then, loop through each cell in there, checking the left two letters. If "ED", then move the data, but take off the "ED".
Edit: Just realized you're using VBScript. Remove the as Range and & from the declarations, so it's just Dim lastRow, nextRow, cel, rng.
If your criteria is met, this will copy values (minus the ED prefix) from Column B to Column M.
Sub move_Text()
Dim lastRow , i
lastRow = Cells(Rows.Count, 3).End(xlUp).Row
For i = 3 To lastRow
If Left(Cells(i, 3), 2) = "ED" And Not (Left(Cells(i, 3), 3) = "EDF") Then
Cells(i, 13).Value = Right(Cells(i, 3, Len(Cells(i, 3)) - 2)
End If
Next
End Sub
Why not use some of excel's formulas to speed the whole thing up:
Sub My_Amazing_Solution ()
Range("M3").FormulaR1C1 = "=IF(TRIM(LEFT(RC[-10],2))=""ED"",RIGHT(TRIM(RC[-10]),LEN(RC[-10])-2),"""")"
Range("M3").AutoFill Destination:=Range("M3:M" & Range("C1048576").End(xlUp).Row), Type:=xlFillDefault
Application.Wait Now + TimeValue("00:00:03")
Range("M3:M" & Range("C1048576").End(xlUp).Row).Copy
Range("M3").PasteSpecial xlPasteValues
End sub
This should do it for you!

Using a VBA For Loop to concatenate column in excel

I have a column of data in excel. I want to loop through the data and combine the contents into a single string. I can specify the cell range, but what if the range is unknown. I want to be able to loop until the cell becomes empty. here is what I have so far.
Sub ConcatenationLoop()
Dim rng As Range, i As Integer
Set rng = Range("A1", "A5")
For i = 1 To rng.Rows.Count
With Range("B1")
If .Value = "" Then
.Value = rng.Range("A" & i)
Else
.Value = .Value & ", " & rng.Range("A" & i)
End If
End With
Next
is it possible to combine with something like:
Do Until IsEmpty(ActiveCell)
Much help is appreciated!
End Sub
With Worksheets("YourSheetName")
lastrow = .Cells(.Rows.Count, "A").End(xlUp).Row
End With
Loop it to LastRow.
Get the first empty cell from the top using
lLastRow = sheet.Cells(1, 2).End(xlDown).Row
The use this in your for loop
For i = 1 To lLastRow
You could use the following skeleton:
Sub ALoop()
Dim r As Long
r = 2 '//Start row
While Len(Cells(r, "A")) > 0 '//Or While Not IsEmpty(...)
'// Your code
r = r + 1 '//Don't forget to increment row
Wend
End Sub

Iterative SUMIF Function Using VBA

Consider the following table:
What I would like to be able to do is create something like on the right hand side. This essentially requires telling Excel to sum all values for which the cell is zero until it encounters a 1, at which point it should begin the count again. I imagine this can be done using VBA, so I just need to determine how to actually set up that code. I imagine that the building blocks should be something like this:
Dim row As Long
Dim sum As List
row = Excel row definition
While ColB <> ""
If ColB.value = 0
Append ColC.value to Sum
Else Do Nothing
row = row + 1
Loop
Any help with the structure and syntax of the code would be much appreciated.
Try this:
Sub test()
Dim cel As Range, sRng As Range, oRng As Range, Rng As Range
Dim i As Long: i = 1
On Error GoTo halt
With Sheet1
.AutoFilterMode = False
Set Rng = .Range("B1", .Range("B" & .Rows.Count).End(xlUp))
Rng.AutoFilter 1, 0
Set sRng = Rng.Offset(1, -1).Resize(Rng.Rows.Count - 1) _
.SpecialCells(xlCellTypeVisible)
Rng.AutoFilter 1, 1
Set oRng = Rng.Offset(1, 0).SpecialCells(xlCellTypeVisible)
.AutoFilterMode = False
End With
If sRng.Areas.Count >= oRng.Areas.Count Then i = 2
For Each cel In oRng.Areas
If i > sRng.Areas.Count Then Exit For
If cel.Cells.Count = 1 Then
cel.Offset(0, 1).Formula = _
"=SUM(" & sRng.Areas(i).Address(True, True) & ")"
Else
cel.Cells(cel.Cells.Count).Offset(0, 1).Formula = _
"=SUM(" & sRng.Areas(i).Address(True, True) & ")"
End If
i = i + 1
Next
Exit Sub
halt:
Sheet1.AutoFilterMode = False
End Sub
Edit1:
Above works regardless of how many zero's or one's you have in Column B.
If error occurs, it will exit. I leave the coding on how you want the error handled.

Resources