Searching for text string in a variable - vba

I have to search for multiple text strings in an Excel column (one string at a time). This column has got around 200K values. So if I search through this column 50 times , it takes long time using the "Find" command in VBA.
I am looking for an alternative method to make this search more efficient. Probably, reading this column once in a variable and then search this variable for the text strings I want to. With this method, I hope the searches will be faster than searching in an Excel sheet column itself.
Please suggest the best option to accomplish it.
Any help is highly appreciated.

Here is something small to start with:
Open Excel file and write some values in Range E10:E34. Make sure that one of the values is Plot.
Then run the code:
Option Explicit
Sub TestMe()
Dim varArray As Variant
Dim strToSearch As String: strToSearch = "Plot"
Dim varVal As Variant
Dim lngCounter As Long : lngCounter
varArray = Range("E10:E34")
For Each varVal In varArray
If varVal = strToSearch Then
Debug.Print varVal
Debug.Print lngCounter
Exit For
End If
Next varVal
lngCounter = lngCounter + 1
End Sub
It will give you the row of the E10:E34 range, on which the value is to be found.
The code is not finished. You have to think of the following:
What happens, if we have the value more than once;
What happens, if we want array with more than one columns;
What happens, if the range should be flexible;
What happens, if we just know the start of the value in the cell, but we still want it - e.g. the value is Plot and in the cell we have Plotnik and we want to get it somehow.
Anything else.

Related

VBA: Using Array Formula

I want to find a row number based on two criteria, in column C and E. My data looks like this:
I have googled my problem and using the Match function as an array formula works for this (worked when I used it in Excel, not VBA), but I can't figure out how to make it an array formula in VBA. Different solutions, be it using "[]" or .Evaluate didn't work for me (maybe that was my mistake, though). So how would I modify this code to get the result I want:
Sub Test1()
Dim rowDB As Long
Dim wsDB As Worksheet
Set wsDB = ActiveSheet
rowDB = WorksheetFunction.Match(CDate("30.06.2020") & "EX0500-0001", wsDB.Range("C7:C366") & wsDB.Range("E7:E366"))
End Sub
The error I get is "error 13: type mismatch", so I'm not sure if there's another issue here or just the lack of an array formula.
I played with this for a bit and found several problems:
It seems that CDate() doesn't like "30.06.2020" as input and gets a type error. It seems to be happy with "30-06-2020" so maybe use that format instead or just search for string "30.06.2020" instead? This should be ok as long as all of the date formats are consistent.
The WorksheetFunction.Match() second parameter must be a contiguous range and yours is not. Also I don't think the expression wsDB.Range("C7:C366") & wsDB.Range("E7:E366") makes sense; if you want to combine ranges use the Union() function. But this will not work here because as mentioned the range is not contigous.
I don't think it is possible to use WorksheetFunction.Match() to search for multiple values, so you might have to search for the date in coulmn C and the string in column E separately.
Here is some vba I got working for just searching for one value:
Sub Test4()
Dim rowDB As Long
Dim wsDB As Worksheet
Set wsDB = ActiveSheet
rowDB = WorksheetFunction.Match("30.06.2020", wsDB.Range("C7:C366"))
Debug.Print rowDB
End Sub
Also, If a match is not found, it will get a "Application-defined or object-defined error" so you will need to implement some error handling.

Excel VBA Get Count of Non-Empty Rows in Range

I know this has been asked at least a dozen times, but of all those questions the solutions seemed to be tailored or simply created an error in my code. For some reason I get an error when I try and reference a range from a different worksheet. Below is my code. I have two worksheets (tabs). One has a button to launch my code MENU, the other is the data I am trying to read and write to RAW. All I am trying to do is find out how many rows of data the sheet has so I can loop through the rows and make changes. For some reason I can't wrap my head around it or something.
Private Sub CommandButton21_Click()
Dim wbCurrent As Workbook
Dim wsCurrent As Worksheet
Set wbCurrent = ThisWorkbook
Set wsCurrent = wbCurrent.Worksheets("RAW")
Dim i As Long
Dim siteCol As Long
siteCol = Range("I" & Rows.Count).End(xlUp).Row
For i = 1 To siteCol
wsCurrent.Range("I" & i) = "MARKED"
Next i
Range("I1") = siteCol
End Sub
1- Always use Long for your variables, not Integer unless you have a specific reason. The maximum value of an Integer is 32767, which might be not enough to hold your count of rows.
2- Dont get the number of rows with COUNTA, this has many drawbacks. use this instead:
siteCol = wsCurrent.Range("I" & wsCurrent.Rows.count).End(xlUp).Row
This finds the Row number of the last occupied cell in wsCurrent, assuming that wsCurrent is at the top of the document (it starts on Row 1). Please note that if wsCurrent is completely empty, this will find the row number of the first occupied cell above wsCurrent, or the first row of the document.
3- When you want to assign a whole range to the same value, you can do it at once, which is much faster and simpler, like this (no loop needed):
wsCurrent.Range("I1:I" & siteCol) = "MARKED"
4- No need to Activate a worksheet to work with it. Drop the statement wsCurrent.Activate and try to always work with qualified ranges.

Using Vlookup to copy and paste data into a separate worksheet using VBA

Alright I'm a beginner with VBA so I need some help. Assuming this is very basic, but here are the steps I am looking at for the code:
-Use Vlookup to find the value "Rec" in column C of Sheet1, and select that row's corresponding value in column D
-Then copy that value from column D in Sheet1 and paste it into the first blank cell in column B of another worksheet titled Sheet2
I've got a basic code that uses Vlookup to find Rec as well as it's corresponding value in column D, then display a msg. The code works fine, and is the following:
Sub BasicFindGSV()
Dim movement_type_code As Variant
Dim total_gsv As Variant
movement_type_code = "Rec"
total_gsv = Application.WorksheetFunction.VLookup(movement_type_code,Sheet1.Range("C2:H25"), 2, False)
MsgBox "GSV is :$" & total_gsv
End Sub
I also have another one that will find the next blank cell in column B Sheet2, it works as well:
Sub SelectFirstBlankCell()
Dim Sheet2 As Worksheet
Set Sheet2 = ActiveSheet
For Each cell In Sheet2.Columns(2).Cells
If IsEmpty(cell) = True Then cell.Select: Exit For
Next cell
End Sub
Not sure how to integrate the two, and I'm not sure how to make the code paste the Vlookup result in Sheet2. Any help would be greatly appreciated, thanks!
So for being a beginner you're off to a good start by designing two separate subroutines that you can confirm work and then integrating. That's the basic approach that will save you headache after headache when things get more complicated. So to answer your direct question on how to integrate the two, I'd recommend doing something like this
Sub BasicFindGSV()
Dim movement_type_code As Variant
Dim total_gsv As Variant
movement_type_code = "Rec"
total_gsv = Application.WorksheetFunction.VLookup(movement_type_code, Sheet1.Range("C2:H25"), 2, False)
AssignValueToBlankCell (total_gsv)
End Sub
Sub AssignValueToBlankCell(ByVal v As Variant)
Dim Sheet2 As Worksheet
Set Sheet2 = ActiveSheet
For Each cell In Sheet2.Columns(2).Cells
If IsEmpty(cell) = True Then cell.Value2 = v
Next cell
End Sub
That being said, as Macro Man points out, you can knock out the exact same functionality your asking for with a one liner. Keeping the operational steps separate (so actually a two liner now) would look like this.
Sub FindGSV()
AssignValueToBlankCell WorksheetFunction.VLookup("Rec", Sheet1.Range("C2:H25"), 2, False)
End Sub
Sub AssignValueToBlankCell(ByVal v As Variant)
Sheet3.Range("B" & Rows.Count).End(xlUp).Offset(1, 0).Value2 = v
End Sub
Like I said, if you plan to continue development with this, it's usually a good idea to design your code with independent operations the way you already have begun to. You can build off of this by passing worksheets, ranges, columns, or other useful parameters as arguments to a predefined task or subroutine.
Also, notice that I use Value2 instead of Value. I notice you're retrieving a currency value, so there's actually a small difference between the two. Value2 gives you the more accurate number behind a currency formatted value (although probably unnecessary) and is also faster (although probably negligible in this case). Just something to be aware of though.
Also, I noticed your use of worksheet objects kind of strange, so I thought it'd help to mentioned that you can select a worksheet object by it's object name, it's name property (with sheets() or worksheets()), index number (with sheets() or worksheets()), or the "Active" prefix. It's important to note that what you're doing in your one subroutine is reassigning the reference of the Sheet2 object to your active sheet, which means it may end up being any sheet. This demonstrates the issue:
Sub SheetSelectDemo()
Dim Sheet2 As Worksheet
Set Sheet2 = Sheets(1)
MsgBox "The sheet object named Sheet2 has a name property equal to " & Worksheets(Sheet2.Name).Name & " and has an index of " & Worksheets(Sheet2.Index).Index & "."
End Sub
You can view and change the name of a sheet object, as well as it's name property (which is different) here...
The name property is what you see and change in the worksheet tab in Excel, but once again this is not the same as the object name. You can also change these things programmatically.
Try this:
Sub MacroMan()
Range("B" & Rows.Count).End(xlUp).Offset(1, 0).Value = _
WorksheetFunction.VLookup("Rec", Sheet1.Range("C2:H25"), 2, False)
End Sub
The Range("B" & Rows.Count).End(xlUp) command is the equivalent of going to the last cell in column B and pressing Ctrl + ↑
We then use .Offset(1, 0) to get the cell after this (the next blank one) and write the value of your vlookup directly into this cell.
If Both work, then good, you have two working subs and you want to integrate them. You probably want to keep them so they might be useful for some other work later. Integrating them means invoking them in some third routine.
For many reasons, it is surely better and advised to avoid as much as possible to use (select, copy, paste) in VBA, and to use rather a direct copying method (range1.copy range2).
You need to make your routines as functions that return ranges objects, then in some third routine, invoke them
Function total_gsv() as range
Dim movement_type_code As Variant: movement_type_code = "Rec"
Set total_gsv = Application.WorksheetFunction.VLookup(movement_type_code,Sheet1.Range("C2:H25"), 2, False)
End Sub
Function FindFirstBlankCell() as Range
Dim Sheet2 As Worksheet: Set Sheet2 = ActiveSheet
For Each cell In Sheet2.Columns(2).Cells
If IsEmpty(cell) Then Set FindFirstBlankCell= cell: exit For
Next cell
End Sub
Sub FindAndMoveGsv()
total_gsv.copy FindFirstBlankCell
... 'some other work
End Sub

Excel VBA Cell value rounded when passed to an array

I'm hoping this has a simple solution. I need to take values in from a worksheet, perform calculations and insert them into a hidden worksheet (to be uploaded later into a database). I don't normally program in VBA, so I'm not sure what I'm doing wrong. My problem is this: when I copy data from the worksheet into the array, they are rounded like an integer when I've specified long. The following code snippet will give you an idea of the problem. Am I initializing the array wrong?
Dim ThisWS As Worksheet
Set ThisWS = Excel.ActiveWorkbook.Worksheets("BchSheet")
Dim BTW() As Long 'Beaker Tare Weight
ReDim Preserve BTW(Samples)
BTW(1) = ThisWS.Cells(18, 6).Value 'Value in cell is 98.7036
MsgBox (ThisWS.Cells(18, 6).Value) 'Returns 98.7036
MsgBox (BTW(1)) 'Returns 99
The Long data type, like Integer, only holds whole numbers.
Use the Double data type to store decimals, or the Currency data type if you are working with calculations where fixed-point is necessary or you don't want to deal with floating-point numbers.

Iterating through all the cells in Excel VBA or VSTO 2005

I need to simply go through all the cells in a Excel Spreadsheet and check the values in the cells. The cells may contain text, numbers or be blank. I am not very familiar / comfortable working with the concept of 'Range'. Therefore, any sample codes would be greatly appreciated. (I did try to google it, but the code snippets I found didn't quite do what I needed)
Thank you.
If you only need to look at the cells that are in use you can use:
sub IterateCells()
For Each Cell in ActiveSheet.UsedRange.Cells
'do some stuff
Next
End Sub
that will hit everything in the range from A1 to the last cell with data (the bottom right-most cell)
Sub CheckValues1()
Dim rwIndex As Integer
Dim colIndex As Integer
For rwIndex = 1 To 10
For colIndex = 1 To 5
If Cells(rwIndex, colIndex).Value <> 0 Then _
Cells(rwIndex, colIndex).Value = 0
Next colIndex
Next rwIndex
End Sub
Found this snippet on http://www.java2s.com/Code/VBA-Excel-Access-Word/Excel/Checksvaluesinarange10rowsby5columns.htm It seems to be quite useful as a function to illustrate the means to check values in cells in an ordered fashion.
Just imagine it as being a 2d Array of sorts and apply the same logic to loop through cells.
If you're just looking at values of cells you can store the values in an array of variant type. It seems that getting the value of an element in an array can be much faster than interacting with Excel, so you can see some difference in performance using an array of all cell values compared to repeatedly getting single cells.
Dim ValArray as Variant
ValArray = Range("A1:IV" & Rows.Count).Value
Then you can get a cell value just by checking ValArray( row , column )
You can use a For Each to iterate through all the cells in a defined range.
Public Sub IterateThroughRange()
Dim wb As Workbook
Dim ws As Worksheet
Dim rng As Range
Dim cell As Range
Set wb = Application.Workbooks(1)
Set ws = wb.Sheets(1)
Set rng = ws.Range("A1", "C3")
For Each cell In rng.Cells
cell.Value = cell.Address
Next cell
End Sub
For a VB or C# app, one way to do this is by using Office Interop. This depends on which version of Excel you're working with.
For Excel 2003, this MSDN article is a good place to start.
Understanding the Excel Object Model from a Visual Studio 2005 Developer's Perspective
You'll basically need to do the following:
Start the Excel application.
Open the Excel workbook.
Retrieve the worksheet from the workbook by name or index.
Iterate through all the Cells in the worksheet which were retrieved as a range.
Sample (untested) code excerpt below for the last step.
Excel.Range allCellsRng;
string lowerRightCell = "IV65536";
allCellsRng = ws.get_Range("A1", lowerRightCell).Cells;
foreach (Range cell in allCellsRng)
{
if (null == cell.Value2 || isBlank(cell.Value2))
{
// Do something.
}
else if (isText(cell.Value2))
{
// Do something.
}
else if (isNumeric(cell.Value2))
{
// Do something.
}
}
For Excel 2007, try this MSDN reference.
There are several methods to accomplish this, each of which has advantages and disadvantages; First and foremost, you're going to need to have an instance of a Worksheet object, Application.ActiveSheet works if you just want the one the user is looking at.
The Worksheet object has three properties that can be used to access cell data (Cells, Rows, Columns) and a method that can be used to obtain a block of cell data, (get_Range).
Ranges can be resized and such, but you may need to use the properties mentioned above to find out where the boundaries of your data are. The advantage to a Range becomes apparent when you are working with large amounts of data because VSTO add-ins are hosted outside the boundaries of the Excel application itself, so all calls to Excel have to be passed through a layer with overhead; obtaining a Range allows you to get/set all of the data you want in one call which can have huge performance benefits, but it requires you to use explicit details rather than iterating through each entry.
This MSDN forum post shows a VB.Net developer asking a question about getting the results of a Range as an array
You basically can loop over a Range
Get a sheet
myWs = (Worksheet)MyWb.Worksheets[1];
Get the Range you're interested in If you really want to check every cell use Excel's limits
The Excel 2007 "Big Grid" increases
the maximum number of rows per
worksheet from 65,536 to over 1
million, and the number of columns
from 256 (IV) to 16,384 (XFD).
from here http://msdn.microsoft.com/en-us/library/aa730921.aspx#Office2007excelPerf_BigGridIncreasedLimitsExcel
and then loop over the range
Range myBigRange = myWs.get_Range("A1", "A256");
string myValue;
foreach(Range myCell in myBigRange )
{
myValue = myCell.Value2.ToString();
}
In Excel VBA, this function will give you the content of any cell in any worksheet.
Function getCellContent(Byref ws As Worksheet, ByVal rowindex As Integer, ByVal colindex As Integer) as String
getCellContent = CStr(ws.Cells(rowindex, colindex))
End Function
So if you want to check the value of cells, just put the function in a loop, give it the reference to the worksheet you want and the row index and column index of the cell. Row index and column index both start from 1, meaning that cell A1 will be ws.Cells(1,1) and so on.
My VBA skills are a little rusty, but this is the general idea of what I'd do.
The easiest way to do this would be to iterate through a loop for every column:
public sub CellProcessing()
on error goto errHandler
dim MAX_ROW as Integer 'how many rows in the spreadsheet
dim i as Integer
dim cols as String
for i = 1 to MAX_ROW
'perform checks on the cell here
'access the cell with Range("A" & i) to get cell A1 where i = 1
next i
exitHandler:
exit sub
errHandler:
msgbox "Error " & err.Number & ": " & err.Description
resume exitHandler
end sub
it seems that the color syntax highlighting doesn't like vba, but hopefully this will help somewhat (at least give you a starting point to work from).
Brisketeer