My for loop is too complex - Excel VBA - excel

I'm still writing a program in Excel VBA, and I've been stuck on a specific problem for hours. I'm trying to determine if someone is available on a specific day, but the information I'm given is for when they aren't available.
So I'm trying to write a series of for loops to compare all of this information and put it into Excel. I'm facing the issue, though, of the fact that it's quite complex:
With ActiveSheet
' Check the ID_Array and Date_Array to see if any names match
For DateIndex = 0 To MinLimit
For IDIndex = 0 To ID_Number
'If (ID_Array(IDIndex, 0) = Date_Array(DateIndex, 0)) Then
' We're going to use our column not only as a match check, but as our way of ending the cycle
For colCounter = 2 To 6
' If the date in the doc and the date in the array match, continue
If (.Cells(1, colCounter).Text = Date_Array(DateIndex, 1)) Then
For rowCounter = 2 To 11
' If the time slot in this row matches up with the time slot in the array, and the name also matches up, mark the name to be unused in this row
If (.Cells(rowCounter, 1).Text = Date_Array(DateIndex, 2)) Then
If (ID_Array(IDIndex, 0) = Date_Array(DateIndex, 0)) Then
ID_Array(IDIndex, 3) = "1"
End If
End If
' If the name has not been flagged as 1, write it down in this row
If (ID_Array(IDIndex, 3) <> "1") Then
supahotstring = ", " + ID_Array(IDIndex, 1) + " " + ID_Array(IDIndex, 2)
.Cells(rowCounter, colCounter).Value = .Cells(rowCounter, colCounter).Text + supahotstring
End If
' If the name HAS been flagged as 1, unflag it for the next row, since it might need to be written there
If (ID_Array(IDIndex, 3) = "1") Then
ID_Array(IDIndex, 3) = "0"
End If
' Now that all names can be checked for availability again, move on to the next row in this column
Next
End If
Next
'End If
Next
Next
End With
Above is the series of for loops I'm using. for reference, I use SQL to draw in the data, so everything is organized by records.
Since the possible available dates are fixed, I use the values I have set up in my Excel doc as a comparison to the dates people have put in. Basically, if someone has marked a specific timeslot on a specific day, I want to flag the record with that name as "1" so that it doesn't get marked down in that row. Then, I see if the current name is marked as "1", apply the appropriate actions, unmark it as "1" so that it can be checked again later.
However, when I run the program, a variety of problems occur: sometimes, the program will freeze up and I'll be forced to end task (I save quite often these days). Other times, when I write, it will simply write the same names in the same order in every box.
I feel like this should be a relatively easy problem to fix, but at this point I need a second opinion. I need to talk it over with someone here who's willing to look at it, and perhaps even find a way to do this without using four for loops.
It's a fairly complex program, and if you need more code for reference then I'm willing to provide it. I've tried to comment this code enough that it's fairly easy to understand. I really appreciate any help you can give.
EDIT: I cannot provide an image of expected output, but I can describe it. Imagine John Doe is going to be available on the first day, after 9:00AM. The program simply writes his name, along with every else's names, eleven time in each box.

Related

Possible faster VBA lookup with wildcards?

I need to perform a quite a lot of lookups with wildcards on the worksheet using a macro (mainly lookup for value & returning the value from another column though with proper adjustment it can be also just looking for a value with wildcard, and some lookups only as checks if the value exists in the dataset). My data can't be sorted and all the lookups are within a loop A or loops within loop A; wildcards are included mostly for condition "string begins with...". I often have to find a value in one row and find corresponding value in row N rows below or above.
I have a working code, but I wonder if it can be done faster. #response to comment about posting it on Code Review (sorry, I cannot comment yet :)) - preparation the whole code to posting would take a bit too much time for me, confidentiality etc, so I prefer to treat it as a general question to be worked on this example.
Example data (I can add more columns, if I need any helper column):
Example Data picture at Imgur
Assume 100 000 rows (max xPagesCount = 1000, typically around 400; all values for certain xPage is in one block). Due to a lot of possible rows with additional data I can't simply find one value and add numbers to the found row to find the other values by their position.
Example lookups to perform while looping through consecutive xPages (so, for each given xPage):
value in row just below row with "RESTRICTIONS:" text
find name (which is always given with height (column C) = 35)
find RSW number (which can be in several rows depending on page content, but always below name)
find all rows starting with the same four digits as RSW, in two formats: DDDD.LLL.DD and DDDD.DDDDD.DD (L letter, D digit) (I use internal loop here)
check if there is a text "MASTER" (or "MASTER " etc.)
find all values between values "DOCUMENTS:" and "OPTIONS:", which quantity can be from 1 to 50 (I use internal loop here)
I was wondering, what is the fastest way to do such lookups?
What I tried:
using a dictionary on all dataset (keys in column A or C with, values
col.D) but as dictionary can't work on wildcards, I had to add ifs
for not finding a key to perform additional Application.Match
lookup... and then realized it mostly worked on these Match lookups
and not sure I even need a dictionary. I also have duplicate values
within a page and dictionary was getting only first value, regardless
their position (for example, several attachments could have value 1).
The main use remained dict.exists("MASTER") but when I removed
dictionary and changed it to IsError(Application.Match(...)) the code
worked slightly faster.
Application.Match in whole range, typical example: Application.Match(xPage & "4???.*", sh.Range("A1:A" & LastRow), 0)
in few places I use If xValue Like "????.???.??" Then construction
I have dictionary lookups with ifs redirecting to Application.Match:
xValue = dict(xPage & "ATH.416")
If dict(xPage & "ATH.416") = "" Then xValue = Application.Match("ATH.*", Sheets(1).Range("D:D"), 0)
What I consider, but not sure it's worth the effort:
altering the code that at the beginning of the iteration I find the first and the last row for xPage, and then each later check is performed in this range
xStartPage = sh.Range("D" & Application.Match(xPage, sh.Range("A1:A" & LastRow), 0))
'or, I guess better:
xStartPage = xEndPage + 1
If xPage = xPagesCount Then
xEndPage = LastRow
Else
xEndPage = sh.Range("D" & Application.Match(xPage + 1, sh.Range("A1:A" & LastRow), 0) - 1)
End If
xValue = sh.Range("D" & Application.Match("4???.*", sh.Range("D" & xStartPage & ":D" & xEndPage), 0)).Value

Comparing Strings Producing Unexpected Results

I have a list of data and I created a form to enter new data to be added to the list. Upon the click of a button it will take the information (name and email address) from the form and add it to the corresponding sheets in alphabetical order. There are linked cells involved so I can't just add this to the bottom and sort. Instead, I have it searching the last name cell in the correct sheets to insert a row into the correct location.
This was working as expected for the most part until I came along a possibly unique situation that I can't figure out.
Basically, I have an if statement checking to see if the name is a duplicate and afterwards checking to see if the if the new name should be inserted.
For i = 2 To lastrow
''^^IF STATEMENT CHECKING FOR DUPLICATE^^''
'''''''''''''''''''''''''''''''''''''''''''
'''vvIF STATEMENT CHECKING TO ADD DATAvv'''
ElseIf StrComp(lastname, searchl) = 1 And StrComp(lastname, searchl2) = -1 Then
Sheets("Master List").Range("A" & i).Offset(1).EntireRow.Insert (xlDown)
Sheets("Master List").Range("A" & i + 1).Value = firstname
Sheets("Master List").Range("B" & i + 1).Value = lastname
Sheets("Master List").Range("C" & i + 1).Value = fullname
Variables searchl and searchl2 are the last names from search rows i and i + 1, respectively.
My problem is that when I tried to add the last name "Kralik" it tried to insert the data between the last names "Day" and "de Castro"
Originally, I tried comparing the names using the line of code below:
ElseIf lastname > searchl And lastname < searchl2 Then
This executed the exact same way as the code outlined above. I then inserted a break point and decided to use the StrComp method for troubleshooting. Comparing "Kralik" to "Day" produced results expected but the problem occurs when comparing "Kralik" to "de Castro". For some reason, the code thinks "Kralik" is less than "de Castro" and enters the if statement to insert the data at that location. Even more head scratching for me is that I opened a new workbook and quickly typed "Kralik" into A1, "de Castro" into A2 and the formula "=A1>A2" into A3. The formula gave a result TRUE which is what I would have expected from VBA as well.
EDIT: After more tests, I think it must have something to do with the capitalization of "Kralik" vs. "de Castro" my code works as expected as long as the "k" in "Kralik" is uncapitalized. I will use the UCase method on my variables and come back with the results.
EDIT 2: Using UCase works as well. Outlined by GSerg's answer below as to why my original method was not working.
Excel formulas use case insensitive comparisons by default.
VBA uses case sensitive comparisons by default.
If you want case insensitive comparisons, either put
Option Compare Text
at the beginning of the code module to make all text comparisons in that code module case insensitive by default, or request a comparison type in each specific comparison:
ElseIf StrComp(lastname, searchl, vbTextCompare) = 1 And StrComp(lastname, searchl2, vbTextCompare) = -1 Then
On top of that, you should be using binary search in your particular case to find the position to insert. MATCH with match_type = 1 will return you position in a sorted list where the value should go.

Returning multiple values using Vlookup in excel

I have an excel sheet set up to automatically calculate meetings per day by day of the week. I would like to write a formula to return all dates I have a meeting scheduled (comma separated preferably), but I am having some difficulty. Using Vlookup, I can only get it to return the first date.
For example, here is what my data looks like:
A B C
Initial Meetings Follow-up Meetings Date
1 1 7/29/2015
0 1 7/30/2015
1 1 7/31/2015
0 0 8/1/2015
0 0 8/2/2015
I would like to write a formula to return "7/29/2015, 7/31/2015" in one cell, and "7/29/2015, 7/30/2015, 7/31/2015" in another, but I seem to be stuck.
You can't do this with vLookup.
This can be done relatively easily in a VB script, but it would affect portability as many if not most users disable macros by default and in many cases users are prevented from using Macros because their company disables them and makes it policy that users should not use them.
If you are OK with Macros, you can put the following into a new module and then use =MultiVlookup(lookup_value,table_array, col_index_num) in the same way as you'd use vlookup and it should give you a comma separated list of multiple matches:
Public Function MultiVlookup(find_value, search_range, return_row)
Dim myval ' String to represent return value (comma-separated list)
Dim comma ' Bool to represent whether we need to prefix the next result with ", "
comma = False
'Debug.Print find_value.value, return_row
For Each rw In search_range.Rows ' Iterate through each row in the range
If rw.Cells(1, 1).value = find_value Then ' If we have found the lookup value...
If comma Then ' Add a comma if it's not the first value we're adding to the list
myval = myval + ", "
Else
comma = True
End If
myval = myval + Str(rw.Cells(1, return_row).value)
End If
Next
MultiVlookup = myval
End Function
This may not be the cleanest way of doing it, and it isn't a direct copy of vlookup (for instance it does not have a fourth "range lookup" argument as vlookup does), but it works for my test:
Finally my original suggestion (in case it helps others - it's not the exact solution to the question) was:
I've not tried it myself, but this link shows what I think you might be looking for.
Great code, but don't forget to add the following is you use Option Explicit:
Dim rw As Range
WHEELS

Turning an excel formula into a VBA function

I'm a bit new to trying to program and originally was just trying to improve a spreadsheet but it's gone beyond using a basic function in excel. I have a table that I am having a function look at to find a building number in the first column and then look at start and finish dates in two other respective columns to find out if it should populate specific blocks on a calendar worksheet. The problem occurs because the same building number may appear multiple times with different dates and I need to to find an entry that matches the correct dates.
I was able to create a working though complicated formula to find the first instance and learned I can add a nested if of that formula again in the false statement with a slight change. I can continue doing that but it becomes very large and cumbersome. I'm trying to find a way to make a function for the formula with a variable in it that would look at how many times the it has already been used so it keeps searching down the table for an answer that fits the parameters.
This is currently my formula:
=IFERROR(IF(AND(DATE('IF SHEET (2)'!$F$7,MATCH('IF SHEET (2)'!$C$2,'IF SHEET (2)'!$C$2:'IF SHEET (2)'!$N$2,0),'IF SHEET (2)'!C$4)>=VLOOKUP("2D11"&1,A2:F6,4,0),DATE('IF SHEET (2)'!$F$7,MATCH('IF SHEET (2)'!$C$2,'IF SHEET (2)'!$C$2:'IF SHEET (2)'!$N$2,0),'IF SHEET (2)'!C$4)<=VLOOKUP("2D11"&1,A2:F6,4,0)),IF(VLOOKUP("2D11"&1,A2:F6,3,0)="2D11",VLOOKUP("2D11"&1,A2:F6,6,FALSE)),"NO ANSWER"),"ERROR")
Where you see 2D11&1 is where I need the variable for 1 so it would be "number of times it's been used in the function +1" then I could just loop it so it would keep checking till it ran out of 2D11's or found one that matched. I haven't posted before and I'm doing this through a lot of trial and error so if you need more info please post and say so and I'll try to provide it.
So rather than have someone try to make sense of the rediculous formula I posted I though I would try to make it simpler by just stating what I need to accomplish and trying to see how to turn that into a VBA function. So I'm kinda looking at a few steps:
Matches first instance of building name in column A with
building name for the row of the output cell.
Is date connected with the output cell >= start date of first entry(which is user entered in column D).
Is date connected with the output cell <= end date of first entry(which is user entered in column E).
Enters Unit name(located in column F) for first instance of the building if Parts 1, 2, and 3 are all True.
If parts 1, 2, or 3 are False then loops to look at next instance of the building name down column 1.
Hopefully this makes things clearer than the formula so I'm able to get help as I'm still pretty stuck due to low knowledge of VBA.
Here is a simple solution...
Building_name = ???
Date = ???
Last_Row = Range("A65536").End(xlUp).Row
For i = 1 To Last_Row
if cells(i,1).value = Building_Name Then
if date >= cells(i,4).value Then
if date <= cells(i,5).value Then
first instance = cells(i,6).value
end if
end if
end if
next
you should add a test at the end to avoid the case where there is no first instance in the table
If I understand correctly, you have a Table T1 made of 3 columns: T1.building, T1.start date, T1.end date.
Then you have 3 parameters: P1=building, P2=start date, P3=end date.
You need to find the first entry in table T1 that "fits" within the input parameters dates, that is:
P1=T1.building
P2<=T1.start date
P3>=T1.end date
If so, you can define a custom function like this
Public Function MyLookup(Key As Variant, DateMin As Variant, DateMax As Variant, LookUpTable As Range, ResultColumn As Integer) As Range
Dim iIndx As Integer
Dim KeyValue As Variant
Dim Found As Boolean
On Error GoTo ErrHandler
Found = False
iIndx = 1
Do While (Not Found) And (iIndx <= LookUpTable.Rows.Count)
KeyValue = LookUpTable.Cells(iIndx, 1)
If (KeyValue = Key) And _
(DateMin <= LookUpTable.Cells(iIndx, 2)) And _
(DateMax >= LookUpTable.Cells(iIndx, 3)) Then
Set MyLookup = LookUpTable.Cells(iIndx, ResultColumn)
Found = True
End If
iIndx = iIndx + 1
Loop
Exit Function
ErrHandler:
MsgBox "Error in MyLookup: " & Err.Description
End Function
That may not be the most performant piece of code in the world, but I think it's explanatory.
You can download this working example

Separating Data in Excel

I receive data which is numbered consecutively but under each number there is zero, 1 , 2 or 3 pieces of information before the next number. I need to separate the data so that the numbers are evenly spaced!
Any help would be greatlyfully received - I should have done Excel at University but that was decades ago!
Thankyou,
Graeme
Hi Robert,As a Australian new user I am not allowed to insert images! Hence imagine each comma as separating cells and a dash as an empty Excel cell with the first row what I initially receive. The "g,h" etc are sentences. The 2nd row is what I would like to achieve. Unfortunately it wont format to show the cells going down not across so it has to be across here as it would in Excel or an inserted image! Thanks!
Receive 1,g,h,2,k,3,a,c,v,4,5,r,t
Require 1,g,h,-,2,k,-,-,3,a,c,v,4,-,-,-,5,r,t,-,
Hence if I received 3 pieces of information for each "number" (client) the numbers would automatically be evenly spaced but this unfortunately does not occur!
Graeme, let me know if you can create VBA Macro's based on an example.
I just wrote one that does the job, although being far form elegant.
<-----------CODE START--------->
Sub uniformdata()
ReceiveCol = 1 'Column where received data starts
ReceiveRow = 1 'Row where where received data starts
PublishCol = 1 'Column where Published data starts
PublishRow = 2 'Column where Published data starts
Do Until Cells(ReceiveRow, ReceiveCol).Value = ""
For counter = 1 To 4
If counter = 1 Then
Cells(PublishRow, PublishCol).Value = Cells(ReceiveRow, ReceiveCol).Value
ReceiveCol = ReceiveCol + 1
Else
If IsNumeric(Cells(ReceiveRow, ReceiveCol).Value) Then
Cells(PublishRow, PublishCol).Value = "-"
Else
Cells(PublishRow, PublishCol).Value = Cells(ReceiveRow, ReceiveCol).Value
ReceiveCol = ReceiveCol + 1
End If
End If
PublishCol = PublishCol + 1
Next
Loop
End Sub
<---------CODE END--------->
What it does is to go through your list from left to right until it finds an empty cell. Inside it constantly runs a loop for 4 times. On the first pass it assumes that your number is there, and immediately writes it. On the other 3 passes, it checks if the next received cell is a number or not. If it is a number it knows to fill in the gap with a dash, if it is not a number the character found is used.
Let me know if this solves your issue. In my example, it worked like a charm.
Regards,
Robert Ilbrink

Resources