Why does my VBA code keep hanging? - vba

I attached two links to a flowchart of how the VBA code should go about, and a screenshot of my two sheets.
Basically, I have two sheets - "Disbursements" & "Cheque Info". On the Disbursements' sheet, I need to consider only the rows with count > 1 (column I). For example, I won't consider row 8 of column I, but will consider row 12. Every row that has a count of > 1 should have a value on row H by the end of the run.
After considering which row has a count >1, we check if the corresponding amount (column F) is equal to Cheque Info's column E. Then if for example, for row 12 of disbursements, 1,384.35 is equal to row 9 of Cheque Info. We must get the difference of these dates then store it to a variable "Current". But there are many "1,384.35" that we must get the minimum difference for the dates, thus a need for a loop.
Again, I need to do loops for each row that has a count of >1 on disbursements' column I, so that I will get the date on Cheque Info (with the same amount) that has a minimum gap from the date on Disbursements sheet. For example, the date that has the least gap for 1/18/2016 (for the amount 1,384.35) is 1/4/2016.
Here is my current code:
Sub F110Loop()
Dim x As Integer 'current amount
Dim y As Integer
Dim d As Double 'delta between Disbursement date and Cheque Release date
Dim Current As Integer
Dim Least As Integer
Dim Dis As Worksheet
Dim Cheque As Worksheet
Dim wb As Workbook
Set wb = ThisWorkbook
Set Dis = wb.Sheets("Disbursements")
Set Cheque = wb.Sheets("Cheque Info")
wb.Activate
For x = 4 To 600
Do While Dis.Cells(x, 9).Value > 1
'IF same amount, get row number to get corresponding date, reference that date
For y = 3 To 600
If Dis.Cells(x, 6).Value = Cheque.Cells(y, 5).Value Then
'THEN get delta
Current = Dis.Cells(x, 4).Value -Cheque.Cells(y, 2)
'IF current is less than the least delta
ElseIf Current < Least Then
'THEN update new value of delta
Current = Least
Else
'copy paste the date (from the least delta row)
Cheque.Cells(y, 2).Copy Destination:=Dis.Cells(x, 8)
End If
Next y
Loop
Next x
End Sub

Your code hangs because you dont have any check s for null/vbnullstring or 0 values. i.e.:
IF Dis.Cells(x,6).Value <> vbNullString OR Dis.Cells(x,6).Value <> 0 Then....
reason this is needed is to keep excle from looping through each cell till out of memory...

Your Do Whileloop is an infinite loop. Once it catches a cell such that Dis.Cells(x, 9).Value > 1 it will loop forever, because inside the loop, nothing will change, neither x nor Dis.Cells(x, 9).Value.
I think you have to think again the logic of the subroutine. Maybe replacing that loop with a simple IF test would do.

The Do Loop will not exit until Dis.Cells(x, 9).Value > 1. Inside the Do Loop you change values in Dis.Cells(x, 8). If Dis.Range("I3:I600") does not have formulas in it or if anyone of the cells in Dis.Cells(x, 9).Value never exceed1 then theDo Loop` will never exit.
Do While Dis.Cells(x, 9).Value > 1
'IF same amount, get row number to get corresponding date, reference that date
For y = 3 To 600
Next y
Loop
You should also turn off ScreenUpdating while the code is running. If you don't need any formulas to recalculate then set Calculation to xlCalculationManual.
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
Why are you using Range.Copy?
Cheque.Cells(y, 2).Copy Destination:=Dis.Cells(x, 8)
A direct assignment is much more effecient
Dis.Cells(x, 8) = Cheque.Cells(y, 2)
If there are no formula that you need to recalculate then using an array instead should cut you execution time to under 1 second.

Related

=LEFT(H2,5) To show data for all rows and stop at last row of data

Hi could someone enlighten me with some VBA code to insert formula =LEFT(H2,5) into column M and then stop at the last row of data.
The data it will be referencing will be inserted from the web so when i refresh the data pull the rows could be more or less so it can't be a fixed without using VB
Thanks
Rhys
You don't need a loop for this:
Sub qwerty()
Dim N As Long, r As Range
N = Cells(Rows.Count, "H").End(xlUp).Row
Set r = Range("M2:M" & N)
r.Formula = "=LEFT(H2,5)"
End Sub
You will find that the addresses in the formulas adjust just like in copy/paste.
Would a while loop work for you?
Dim i As Integer
i = 2 'starting row number
While Cells(i, 1).Value <> "" 'Empty row
Cells(i, 13).Formula = "=LEFT(H2,5)" 'replace this with something for that row, concatenating i to H will work i think.
i = i + 1
Wend
You'll also want to put this code to whenever the data is refreshed so it inserts the formula to all rows again.
Dim x As Long
x = Application.CountA(ActiveSheet.Columns(13))
ActiveSheet.Cells(2, 13) = "=LEFT(H2,5)"
ActiveSheet.Cells(2, 13).Resize(x - 1).Formula = ActiveSheet.Cells(2, 13).Formula
use excel function CountA to get the total number of row that you need to populate and assign that number to x
then put the actual formula on cells M2 then copy the formula until the last row using resize function

Break from for loop back into if statement vba

I am trying to perform an action that will see if the date in range (dateRng) is less than today's date, and then, if it is, perform the for loop to hide the rows w.here a value in the column is zero. (I am paying off loans, and every month I want it to hide any loans that have been paid off.) Months are across columns, loans are in rows. Loan balance is (i, j).
The problem is that it never exits the for loop to go back and check the date after every new 'j' (column). It just stays in the for loop. I have tried break, exit, continue, etc. None seem to work, at least where I place them. How do I get it to check for the date, compare to 'today', THEN run the for loop to check each cell in the column, before moving on to column 2, checking the date and performing the same for loop.
It would be good to have it be dynamic, but that is not necessary, as every month I could just change the ranges in the code. This is strictly for my personal use. Any help is appreciated. thank you.
Sub hidePaid()
Dim day As Range, loanRng As Range, loanSum As Worksheet, dateRng As Range, cel2 As Range, i As Long, j As Long, col As Range
Set loanSum = ThisWorkbook.Worksheets("Loan Sum")
loanSum.Activate
Set dateRng = ActiveSheet.Range("D2:R2")
Set loanRng = ActiveSheet.Range("D4:R16")
For Each day In dateRng
If day.Value < Date Then
For j = 1 To loanRng.Columns.Count
For i = 1 To loanRng.Rows.Count
If loanRng.Cells(i, j).Value < 1 Then
loanRng.Cells(i, j).EntireRow.Hidden = True
End If
Next i
Next j
End If
Next
End sub
I added comments in the code to show my changes.
You were close, but had one to many loops and like you say, needed to find the right place for the exit.
Sub hidePaid()
Dim day As Range
Dim loanRng As Range
Dim loanSum As Worksheet
Dim dateRng As Range
Dim i As Long
Set loanSum = ThisWorkbook.Worksheets("Loan Sum")
loanSum.Activate
Set dateRng = ActiveSheet.Range("D2:R2")
Set loanRng = ActiveSheet.Range("D4:R16")
'This loop processes by column
For Each day In dateRng
'Once the date in the column is greater than today, it will stop processing
'It assumes the values in dateRng are valid dates
'(I.e. '01/01/2016' not just 'Jan', you can use number format in Excel to
'get a date to show as 'Jan' if that is better for you)
If DateDiff("d", Now(), day.Value) > 0 Then Exit For
'The line of code you had should have worked in sense,
'it would have touched every column but only procesed those before today
'It also assumes that value in the cell to be an actual date
'If day.Value < Date Then
'You do not need a column loop here as you are already in one in the
'previous loop
'For j = 1 To loanRng.Columns.Count
'This loop processes all the rows that are not already hidden and if
'the value is equal to 0 then it hides the row
'Note: you had the check to be less than 1, .50 is less than 1 and you don't
'want to get caught out on a loan!
For i = 1 To loanRng.Rows.Count
If (loanRng.Cells(i, day.Column - 3).Value = 0) And (loanRng.Cells(i, day.Column - 3).EntireRow.Hidden = False) Then
loanRng.Cells(i, day.Column - 3).EntireRow.Hidden = True
End If
Next i
Next
'Its good practice to clear out resources when finishing
Set dateRng = Nothing
Set loanRng = Nothing
Set loanSum = Nothing
End Sub

Code to compare each cell in a column to every cell in another column

I have two columns with random times and the times come from two different sources so the columns do not have the same amount of data points. I want to start with the first time in the first column and compare it to each time in the second column. If there is a match in times, I would like to pull relevant data. After a match is found (if there is one) I would like for the code to go to the second cell in the first column and compare it to every value in the second column and so on.
Here is the code I have so far:
Sub TransferInfo()
'Activate the Sub to Convert and Format Dates
Call ConvertDates
'Define Variables
Dim st As Worksheet
Dim ts As Worksheet
Dim lastrow As Long
Dim i As Integer
j = 2
'Find and set the last used row
Set st = ThisWorkbook.Worksheets("Data Table")
lastrow = st.Cells(st.Rows.Count, "B").End(xlUp).Row
Set ts = ThisWorkbook.Worksheets("ShopFloor")
'Cycle through/compare Row J, Column 18 based on each cell in Row I, Column 14
For i = 2 To lastrow
Do Until IsEmpty(ts.Cells(j, 8)) Or IsEmpty(st.Cells(j, 2))
If st.Cells(i, 14).Value = ts.Cells(j, 18).Value Then
st.Cells(i, 15).Value = ts.Cells(j, 2).Value
Exit Do
Else
st.Cells(i, 15).Value = ""
End If
j = j + 1
Loop
j = 2
Next i
End Sub
The other sub that I call at the beginning of this sub simply rounds the times in each column to the nearest 15 minute interval to increase the likelihood of matches between the columns.
My question is: The code does not copy and paste any more information although there are times that match between the two columns. Why would the code that I have not work? Also, with larger data sets I am afraid that this the code may crash Excel and because I have a loop within a loop trying to process a lot of data a lot of times, but I don't know of a more efficient way to accomplish what I am trying to without this code.
If anyone has any insights as to why this code doesn't work I would greatly appreciate any help.
Thanks!
Based on your code, it looks like you just need an INDEX/MATCH formula. Use this in O2 and copy down:
=IFERROR(INDEX(B:B,MATCH(N2,R:R,0)),"")
No need for VBA

VBA Excel word search and copying formulas

I'm searching for a VBA macro for Excel, which can detect the word "mean", in column A. After this it would copy the yellow row with the formula in C to J.
The formula counts the average from one row after the last "mean" to the next =AVERAGE (C1323:C1437)
after every sixth mean there also needs to be Area and 150 copyied two rows after mean and I and J Need to be changed. Consequently I and J would refer to the cell A1441 in this case (=G1439/C1439*$A$1441) till the end of the file.
I'm not quite sure if it's easy or not but I'm totally overchallenged. I would be very thankful for help.
Sub Makro1()
'
' Makro1 Makro
'
' Tastenkombination: Strg+q
strSearchWord = "Mean"
i = Application.WorksheetFunction.CountIf(Range("A:A"), strSearchWord)
Y = 2
For x = i To 0
i = Application.WorksheetFunction.Match(strSuchWort, Range("A:A"), 0)
Range("C" & i).Select
Application.CutCopyMode = False
ActiveCell.FormulaR1C1 = "=AVERAGE(R[-147]C:R[-1]C)" ' that's still wrong, should be something like i-y?
Selection.AutoFill Destination:=Range("C" & i:"J" & i), Type:=xlFillDefault
Range("CY:JY").Select
i = Y
'for each fifth i
'Range("A" & i + 3).Select
' ActiveCell.FormulaR1C1 = "=RC[-2]/RC[-6]*R2159C1"
Next x
End Sub
it's still wrong, but my first draft.
#stucharo the Area correction is difficult to describe I've added a better Picture with formulas. I hpe that now it's understandable
If your line ActiveCell.FormulaR1C1 = "=AVERAGE(R[-147]C:R[-1]C)" needs to change the number of rows betwen means each time then you'll need to add a variable as you comment suggests. Also, just writing the string to the cells value (ActiveCell.Value) means that you will see it written as a formaula when you click the cell in the workbook (and it'll highlight the range etc.). You could try replacing it with:
ActiveCell.Value = "=AVERAGE(R[" & i - Y & "]C:R[-1]C)"
although since I can't see the first row of your sheet I'm not certain that'll give you the correct range of rows each time.
If your row number is likely to change and you are copying over the same number of columns each time then it might also be just as easy to write the formula directly to cells within a loop, rather than explicitly copying it.
Adding text after every 6th "mean" would require you to keep count of how many means had passed so far. This can be done by incrememnting a counter variable and using the Mod operator will tell you the remainder after a division. Therefor numberOfMeans Mod 6 will give you the remainder when divided by 6 and when this equals zero you know you have a multiple of 6. I've tried to capture all this into the code below.....
Sub Test()
Application.ScreenUpdating = False
Dim startRow As Integer
startRow = 2
Dim endrow As Integer
endrow = Range("A2").End(xlDown).row
Dim lastMeanRow As Integer
lastMeanRow = startRow - 1
Dim areaRow as Integer
areaRow = lastMeanRow + 3
Dim meanCounter As Integer
meanCounter = 0
Dim avgColHeight As Integer
Dim col As Integer
Dim row As Integer
'Check each row in the sheet
For row = startRow To endrow
'Cols i and j in every row need to be modified
For col = 9 To 10
Cells(row, col).Value = "=RC[-2]/RC[-6]*R" & areaRow & "C1"
Next col
'If column 1 of that row contains "mean" then
If Cells(row, 1).Value = "mean" Then
'Calculate the column height to average over....
avgColHeight = row - lastMeanRow - 1
'...and loop through each of the columns....
'(including i and j to add average)
For col = 3 To 10
'....inserting the averaging formula.
Cells(row, col).Value = "=AVERAGE(R[-" & avgColHeight & "]C:R[-1]C)"
Next col
'Then increment the counter to keep track of the number of means
meanCounter = meanCounter + 1
'If the number of means is a multiple of 6 then
If (meanCounter Mod 6 = 0) Then
'insert the "Area" and "150" strings
Cells(row + 2, 1).Value = "Area"
Cells(row + 3, 1).Value = "150"
areaRow = row + 3
End If
'Finally change the lastMeanRow to the mean row we have just processed.
lastMeanRow = row
End If
'Do it again until we reach the end of the data
Next row
Application.ScreenUpdating = True
End Sub
I also noticed your point on the value of area changing periodically. Writing this programatically, as above, will aloow you to add some logic over the value of "Area" and when it changes.
You clearly have a long list of data and want to automate the creation of the rows and formulas you describe.
It is possible write VBA to scan through the data and modify the formulas etc but first I would question if this is the best approach to give you what you need.
Excel has a feature called "pivot tables" which essentially allows you to summerise data in a list.
for instance if the list had one row for each city in the world and gave the population in the city, and a column gave which country it was in. A pivot table could be used to create the average population for a country of the countries cities. I suspect you are doing this sort of thing.
If you don't know about pivot tables you should find out about them. See here
In your case your mean row is summeriseing data in the rows above it. To use pivot tables you would have to have a column that defined which group each row is in. You pivot table would sue this column as a row summary and you would then create the average for all the other column.
#Nathalie. It's hard to help without knowing more. eg Is the data delivered with the mean text already inserted. It looks like column A has a number the represent the row number within the group (and this could be used by a formula to create the "Group Name" column you need for pivot tables.
You can get the pivot tables to do the area adjustment by:
Creating a new set of columns which contains formulas that cause the values in columns C to J to be copied except for when it is the 6th set of data in which case you adjust the values in C to J accordingly).
You probably need to introduce columns that:
A. give the "group name"
B. give a count of which group it is in so every 6th you can do the adjustment you need.
4 by using pivot tables and basic techniques you will find it easie rot update the refresh the data, should you need to.

Creating Macro for Copying data from one sheet to another,calculating the difference between dates in excel

The below mentioned data is for door access in a company where in we need to find the number of hours spent by a employee in office.
A employee can come in the office and swipe in and swipe out multiple times and all these details are register in the excel in non sorted order for all the employees.
I have a excel containing multiple columns
First two columns A,B are merged cells having date in this format(2015/01/25 7:27:30 PM).
The third column C has Access information having multiple entries for the below values(Entry/Exit).
For example
Column A Column B Access Employee ID Employee Name
==================================================
1. 2015/01/25 7:27:30 AM Entry 111 XYZ
2. 2015/01/25 7:30:30 AM Entry 333 ABC
3. 2015/01/25 8:30:30 AM Exit 111 XYZ
4. 2015/01/25 9:30:30 AM Entry 111 XYZ
5. 2015/01/25 9:30:30 AM Entry 444 PQR
6. 2015/01/25 10:30:30 Pm Exit 333 ABC
7. 2015/01/26 7:30:30 AM Exit 333 ABC
And so on.
Please note that the same employee can have multiple swipe in and out's throughout the day and will be clobbered among other employees information
The Goal is to as below
1) Copy the data from one sheet to another for the employees having spent time less than 9 hours for a specific day.
Here is the sample code that i have written it is work in progress
Sub HoursList()
Dim cell As Range
Dim cell1 As Range
Dim NewRange As Range
Dim NewRange1 As Range
Dim MyCount As Long
Dim ExistCount As Long
Dim ExistsCount As Boolean
Dim temp As Long
Dim MyCount1 As Long
Dim wsh As Worksheet, i As Long, lngEndRowInv As Long
Set wsh = Worksheets("Standard Door History ")
'Set cell = Range("A1")
ExistCount = 0
ExitsCount = False
MyCount = 1
MyCount1 = 1
i = 12
lngEndRowInv = wsh.Range("P" & wsh.Rows.Count).End(xlUp).Row
'----For every cell in row G on the Data sheet----'
For Each cell In wsh.Range("C12:D9085")
If cell.Value = "Entry" Then
'ExistCount = ExistCount + 1
If MyCount = 1 Then Set NewRange = cell.Offset(0, -1)
'----Sets up a new range to copy all data from the row if column in that row contains the value in question----'
Set NewRange = Application.Union(NewRange, cell.EntireRow)
MyCount = MyCount + 1
End If
Next cell
For Each cell1 In NewRange
If cell1.Value = "Mayur" Then
If MyCount1 = 1 Then Set NewRange1 = cell.Offset(0, -1)
'----Sets up a new range to copy all data from the row if column in that row contains the value in question----'
Set NewRange1 = Application.Union(NewRange1, cell.EntireRow)
MyCount1 = MyCount1 + 1
End If
Next cell1
If ExistCount > 0 Then
NewRange.Copy Destination:=Worksheets("Test").Range("A3")
End If
End Sub
Thanks
Here is a very rough version that you could use in VBA. It needs refining and error trapping, and future proofing, but it does what you want it to. It takes data from the active sheet and current adds it to the second worksheet. The date for looking up is in cell N1 of the first sheet.
Option Explicit
Sub CopyNine()
Dim LastRow As Integer
Dim DateToFind As Variant
Dim CellDate As Variant
Dim Count As Integer
Dim cel As Range
Dim DateRange As Range
Dim StaffID As String
Dim TimeStamp As Double
Dim StaffSummary As Object
Dim DS As Worksheet
Dim SS As Worksheet
Dim SSRow As Integer
LastRow = Range("A1").End(xlDown).Row
'You may wish to turn this into an input instead
DateToFind = Range("N1").Formula
Set DS = ActiveSheet
'You may wish to change this
Set SS = Sheets(2)
SSRow = 2
'Get a range containing all the correctly dated cells from the dataset
For Each cel In Range("A2:A" & LastRow).Cells
CellDate = Left(cel.Formula, InStr(1, cel.Formula, ".") - 1)
If CellDate = DateToFind Then
If DateRange Is Nothing Then
Set DateRange = cel
Else
Set DateRange = Union(DateRange, cel)
End If
End If
Next
'Create a summary dictionary of all staff IDs and their time spent in the office where 1 = 1 day
Set StaffSummary = CreateObject("scripting.dictionary")
For Each cel In DateRange.Cells
StaffID = cel.Offset(0, 3).Value
'These may need to be updated depending on your entry in the 'Entry/Exit' column
If cel.Offset(0, 2).Value = "Entry" Then
TimeStamp = -cel.Formula
Else
TimeStamp = cel.Formula
End If
If Not StaffSummary.exists(StaffID) Then
StaffSummary.Add StaffID, TimeStamp
Else
StaffSummary.Item(StaffID) = StaffSummary.Item(StaffID) + TimeStamp
End If
Next
'Copy the titles from the data sheet
SS.Range("A1:E1").Value = DS.Range("A1:E1").Value
'Copy the appropriate rows across using the dictionary you created
For Each cel In DateRange.Cells
StaffID = cel.Offset(0, 3).Value
If StaffSummary.Item(StaffID) <= 9 / 24 Then 'This is 9 hours so copy across
SS.Range("A" & SSRow & ":E" & SSRow).Value = DS.Range(cel, cel.Offset(0, 4)).Value
SSRow = SSRow + 1
End If
Next
End Sub
I would suggest using Excel's inbuilt abilities before VBA, especially if you are new to VBA. This will involve adding additional columns to your input sheet though which you can hide, but may not be ideal for your situation. It could also get quite slow as there are some large calculations, but it does depend on your original data set.
I would suggest the following (although there will be a lot of variations on it!):
1) Create a summary table for the particular day.
Create a date column in column F which is =TRUNC(A2) and copy down the table.
In M1 have your input date - e.g. 2015/01/25
In column L list all the unique Staff IDs
Below the date in M, use a SUMIFS formula and time formatting to determine how many hours each person spent. In M3 for example =SUMIFS($A:$A,$D:$D,$L2,$C:$C,"Exit",$F:$F,$M$1) - SUMIFS($A:$A,$D:$D,$L2,$C:$C,"Entry",$F:$F,$M$1) then formatting as hh:mm:ss.
In column N, use =M2<TIME(9,0,0) and drag down to work out if that individual has spent less than 9 hours in the building on that day.
You should now have a table showing all the staff and how many hours they spent in the building on that day, and a TRUE or FALSE whether they spent less than 9 hours.
2) Create your additional columns to pull the data to another sheet
In Column G, determine whether the entry is for the date in question (in cell M1) using =F2=$M$1 (should give a TRUE or FALSE)
In Column H, determine if that individual has spent less than 9 hours (from the summary table) using =INDEX(N:N, MATCH(D2, L:L,0))
In Column I, determine whether that entry should be copied across using =AND(G2, H2)
Finally in Column J, determine which entry this is to copy across using `=IF(I2, COUNTIFS($I$1:I2,TRUE),"")
Copy each of these down to the bottom of the table (you can hide them later)
3) Create your table on the next sheet for copying down - I have called my original worksheet "Data" and my second one "Copy"
In column A, use =ROW()-1 to create a sequential list of numbers
In column B, use =MATCH(A2, Data!J:J,0) to find out which row of data from the original table is being copied across
In column C, use =IFERROR(INDEX(Data!A:A,$B2),"") to pull the data from the first column
Copy this formula across to column G
Copy all of these down the sheet to however many rows of data you would like
Hide columns A, B and D since these will contain irrelevant information
You should then have an autoupdating table based on the date in cell M1 on the original data sheet. As mentioned above, this can be adapted in many ways, and it may not be ideal for your situation depending on your data set size, but it may be a start for you. If it is not suitable, then please use the theory to adapt some VBA code, as this can also be done in VBA in a very similar way.