I have this Macro, and finally got it figured out, but it is running very slowly, and would take about 3 days to get through my one sheet of 800 000 lines, and I have 100 sheets. I would appreciate help in this regard.
Sub Calculate_Sheet()
Dim orderSh As Worksheet
Dim wiroSh As Worksheet
Dim lastRow As Long, r As Long
Dim pctComp As Double
With ThisWorkbook
'calculator
Set orderSh = .Sheets("ORDER")
'price list
Set wiroSh = .Sheets("WiroA3C100gsmI100gsm20-22pp ")
End With
lastRow = wiroSh.Cells(Rows.Count, 3).End(xlUp).Row
For r = 2 To lastRow
pctComp = (r / 800000) * 100
Application.StatusBar = "Progress..." & " " & pctComp & " " & "% Complete"
'copy from price list to calculator
orderSh.Range("f4") = wiroSh.Range("c" & r)
orderSh.Range("f5") = wiroSh.Range("d" & r)
orderSh.Range("f6") = wiroSh.Range("e" & r)
orderSh.Range("f7") = wiroSh.Range("f" & r)
orderSh.Range("f8") = wiroSh.Range("g" & r)
orderSh.Range("f9") = wiroSh.Range("h" & r)
orderSh.Range("f10") = wiroSh.Range("i" & r)
orderSh.Range("f11") = wiroSh.Range("j" & r)
orderSh.Range("f12") = wiroSh.Range("k" & r)
orderSh.Range("f13") = wiroSh.Range("l" & r)
'copy result
wiroSh.Range("m" & r).Value = orderSh.Range("F14")
Next r
End Sub
Also you can try to copy only single range, instead of multiple ranges. I think it can slight increase your performance.
I think, you can replace this
orderSh.Range("f4") = wiroSh.Range("c" & r)
orderSh.Range("f5") = wiroSh.Range("d" & r)
orderSh.Range("f6") = wiroSh.Range("e" & r)
orderSh.Range("f7") = wiroSh.Range("f" & r)
orderSh.Range("f8") = wiroSh.Range("g" & r)
orderSh.Range("f9") = wiroSh.Range("h" & r)
orderSh.Range("f10") = wiroSh.Range("i" & r)
orderSh.Range("f11") = wiroSh.Range("j" & r)
orderSh.Range("f12") = wiroSh.Range("k" & r)
orderSh.Range("f13") = wiroSh.Range("l" & r)
with something like this
orderSh.Range(orderSh.cells(4,"F"),orderSh.cells(13,"F")) = wiroSh.Range(wiroSh.cells(r,"C"),wiroSh.cells(r,"l"))
And as j.kaspar mentioned, usage of application.screenupdating = false is great idea, but i would strongly recomend to use something like this on the begining of your macro
Dim previousScreenUpdating as boolean
previousScreenUpdating = application.screenUpdating
application.screenUpdating = false
and this on the end of your macro
application.screenUpdating = previousScreenUpdating
Which can help you, when you have nested function in which you setting multiple screenUpdatings...
And also, if you have some formulas on any sheet, make something similar with (on the begining)
Application.Calculation = xlCalculationManual
and this on the end of code
Application.Calculation = xlCalculationAutomatic
And one last, if you have some event listeners, consider using this (same as with screen updating)
application.enableEvents
Use Application.ScreenUpdating = False on the beginning, and Application.ScreenUpdating = True at the end of the macro.
It will run multiple times faster, when the screen is not being updated. But keep in mind, that 800.000 lines and 100 sheets is a lot and it will take "some" time...
There is absolutely no reason whatsoever to ever turn screen updating off. its a technique used to speed up inefficient code, if your code isnt inefficient you dont need to worry about screen updating.... ever.....
The theory is very simple.. Dont EVER access/use a range in your code unless absolutely necessary....
Instead dump the entire sheets data into an array and work from that, not only is it fast.... i mean super fast, you can repopulate an entire sheet (that is 32000 columns and 1 million rows) immediately using an array......
and you use the exact same logic to work with the array as you would with a range so you really have no excuse..
Dim Arr as variant
Arr = Sheet1.Range("A1:Z100")
now instead of Sheet1.Range("A1").value just use Arr(1,1) to access the value
and updating the sheet with the array is just as easy
Sheet1.Range("A1:Z100").value = arr
its as simple as that, its fast its easy and its the way you SHOULD do it unless its just something small your working on but even then, better to practice the best methods right?
1 thing to note is when you put the array values back to the sheet, you need to use a range that is the same size or larger than the array........ or else it will just fill the range you specify.
There is a feature in excel called "Data Table". This feature could help you without writing VBA. But, sorry, I cannot find the explaination in English.
so I took the suggestion of the Arrays, but I am missing something. Here is how I tweaked the VBA code, put no values are being inserted anywhere?
Sub Calculate_Sheet()
Dim orderSh As Worksheet
Dim wiroSh As Worksheet
Dim lastRow As Long, r As Long
Dim pctComp As Double
Dim Arr1 As Variant
Dim Arr2 As Variant
With ThisWorkbook
'calculator
Set orderSh = .Sheets("ORDER")
'price list
Set wiroSh = .Sheets("WiroA3C100gsmI100gsm20-22pp ")
End With
Arr1 = wiroSh.Range("C1:M800001")
Arr2 = orderSh.Range("F4:F14")
lastRow = wiroSh.Cells(Rows.Count, 3).End(xlUp).Row
For r = 2 To lastRow
'display the row and percentage each 1000 rows
If r Mod 1 = 0 Then
Application.StatusBar = "Row = " & r & Format(r / lastRow, " #0.00%")
End If
'copy from price list to calculator
Arr2(1, 1) = Arr1(r, 1)
Arr2(2, 1) = Arr1(r, 2)
Arr2(3, 1) = Arr1(r, 3)
Arr2(4, 1) = Arr1(r, 4)
Arr2(5, 1) = Arr1(r, 5)
Arr2(6, 1) = Arr1(r, 6)
Arr2(7, 1) = Arr1(r, 7)
Arr2(8, 1) = Arr1(r, 8)
Arr2(9, 1) = Arr1(r, 9)
Arr2(10, 1) = Arr1(r, 10)
'copy result
Arr1(r, 11) = Arr2(11, 1)
Next r
End Sub
Related
I need a loop that will match and select different columns (not in sequential order) and paste them to another sheet all whilst keeping the condition in check. It would also be ideal if when the values get pasted that the formatting for the cell is not carried over, just the value.
Below is the code I am currently using:
Sub Test()
Application.ScreenUpdating = False
Sheets("DATA").Select
lr = Range("B" & Rows.Count).End(xlUp).Row
Range("P3").Select
For i = 3 To lr
If Cells(i, 2) <> "" Then Range(Cells(i, 7), Cells(i, 16), Cells(i, 26)).Copy
Sheets("Sheet2").Range("A" & Rows.Count).End(xlUp).Offset(1, 0)
Next
Application.ScreenUpdating = True
End Sub
The problem is declaring the columns I want the loop to paste. I need the loop to run through the 16th column, check empty values, and then paste the index/matched value in the rows of columns 7,16,and 26 (so not in sequential order).. Any help would be appreciated.
The next code has to do what I understood you need. Please check it and confirm this aspect. It is very fast, working only in memory...
Sub PastingNextPage()
Dim sh As Worksheet, sh1 As Worksheet, arrIn As Variant, arrOut() As Variant
Dim lastRowIn As Long, lastRowOut As Long, nonEmpt As Long, rngP As Range, nrEl As Long
Dim i As Long, j As Long, P As Long
Set sh = Sheets("DATA"): lastRowIn = sh.Range("P" & sh.Rows.count).End(xlUp).Row
Set sh1 = Sheets("Sheet2"): lastRowOut = sh1.Range("A" & sh1.Rows.count).End(xlUp).Row + 1
arrIn = sh.Range("G2:Z" & lastRowIn).Value
nrEl = lastRowIn - Application.WorksheetFunction.CountIf(sh.Range("P2:P" & lastRowIn), "") - 2
P = 10 'column P:P number in the range starting with G:G column
ReDim arrOut(nrEl, 3) 'redim the array to keep the collected values
For i = 1 To lastRowIn - 1
If arrIn(i, P) <> "" Then
arrOut(j, 0) = arrIn(i, 1): arrOut(j, 1) = arrIn(i, P): arrOut(j, 2) = arrIn(i, 20)
j = j + 1
End If
Next i
sh1.Range(sh1.Cells(lastRowOut, "A"), sh1.Cells(lastRowOut + nrEl, "C")).Value = arrOut
End Sub
It does not select anything, you can run it activating any of the two involved sheets. I would recommend to be in "Sheet2" and see the result. If you want to repeat the test, its result will be added after the previous testing resulted rows...
If something unclear or not doing what you need, do not hesitate to ask for clarifications.
Hello StackOverflowers,
I am currently working on a script that has one nested IF statement in it. When run it could potentially compute around 1.4m IF's.
I have run a test with a timer (not too sure on the accuracy of the timer in VBA) and crunching 1000 rows gives me a time of 10 seconds. 10 * 700 = 7000 seconds, which = 1.94 hours.
Can anyone give me any tips for optimisation when dealing with such large data sets?
My code is as follows
Sub itS1Capped()
Dim Start, Finish, TotalTime
Start = Timer
Dim c, d, j, lastRow
c = 1
'find how many rows
With Worksheets("Data")
lastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
End With
'loop through all rows
For Each d In Worksheets("Data").Range("D2:D" & lastRow).Cells 'd = IT S0 Uncapped
j = Worksheets("Data").Range("J" & c + 1).Value 'IT Cap
If j <> 0 Then
If d > j Then
Worksheets("Data").Range("K" & c + 1).Value = j 'IT S1 Capped = j
Else
Worksheets("Data").Range("K" & c + 1).Value = d 'IT S1 Capped = d
End If
Else
Worksheets("Data").Range("K" & c + 1).Value = d 'IT S1 Capped = d
End If
c = c + 1
Next
Finish = Timer
TotalTime = Finish - Start
MsgBox TotalTime
End Sub
So I took inspiration from Mark Moore's use of arrays and found that using an array function rather than copying and pasting a plain function across a range is much faster. On my machine, Mark's procedure runs in 2.2 seconds, and the one below in 1.4 seconds.
Sub FormulaArray()
Dim iUsedRows As Long, rCell As Range, StartTimer As Double, Duration As Double
StartTimer = Timer
iUsedRows = ActiveSheet.UsedRange.Cells(ActiveSheet.UsedRange.Rows.Count, 1).Row
With Range(Cells(1, 11), Cells(iUsedRows, 11))
.FormulaArray = "=IF(J:J<>0,IF(D:D>J:J,J:J,D:D),D:D)"
.Copy
.PasteSpecial xlPasteValues
End With
Duration = StartTimer - Timer
MsgBox Format(Duration, "#0.0000") & " seconds to run"
End Sub
I am a bit old school and so "arrays" are your friend :-) Have had similar problems when I first took over looking after some pretty complex spreadsheets at work that did large numbers of validations. When working with large volumes of data, moving between the workbook and the data on the worksheet is not recommended, because each action is effectively an I/O (Input/ output) operation and these are very time consuming. It is massively more efficient to read all your data into an array, work with the array data and then write it back to the sheet at the end, this is effectively 2 I/O's instead of the 700,000 if you read the sheet data each time. As a rough example, I reduced our previous validation time down from 25 minutes to 4 seconds using this approach.
Sub ValidateSheet()
Dim DataRange As String
Dim SheetArray As Variant
Dim StartCol As String
Dim EndCol As String
Dim StartRow As Long ' long to cope with over 32k records
Dim lastrow As Long
Dim WorksheetToRead As String
Dim ArrayLoopCounter As Long
Dim Start, Finish, TotalTime
Start = Timer
'I use variables for the data range simply to allow it to be changed easily. My real code is actually paramatised so a single reusable procedure
'is used to populate all my arrays
'find how many rows
WorksheetToRead = "Data"
StartCol = "A"
EndCol = "Z"
StartRow = 1
lastrow = Sheets(WorksheetToRead).Cells(Rows.Count, "A").End(xlUp).Row
'set the range to be read into the array
DataRange = StartCol & Trim(Str(StartRow)) & ":" & EndCol & Trim(Str(StartRow - 1 + lastrow))
SheetArray = Worksheets(WorksheetToRead).Range(DataRange).Value ' read all the values at once from the Excel grid, put into an array
'Loop around the data
For ArrayLoopCounter = LBound(SheetArray, 1) To UBound(SheetArray, 1)
If SheetArray(ArrayLoopCounter, 10) <> 0 Then '10 is column J
'Compare D with J
If SheetArray(ArrayLoopCounter, 4) > SheetArray(ArrayLoopCounter, 10) Then '10 is column J
SheetArray(ArrayLoopCounter, 11) = SheetArray(ArrayLoopCounter, 10) 'set col K = Col J
Else
SheetArray(ArrayLoopCounter, 11) = SheetArray(ArrayLoopCounter, 4) 'set col K = Col D
End If
Else
SheetArray(ArrayLoopCounter, 11) = SheetArray(ArrayLoopCounter, 4) 'set col K = Col D
End If
Next ArrayLoopCounter
'Write the updated array back to the sheet
Worksheets(WorksheetToRead).Range(DataRange) = SheetArray
Finish = Timer
TotalTime = Finish - Start
MsgBox TotalTime
End Sub
I cannot test this right now, but i believe if you write a function to replace your nested IF statements, add it to Range("K2") with
Range("K2").Formula = ...
then copy it down to Cells(lastrow, "K"), copy all the functions and paste as values it'll be much faster.
Of course using
Application.Calculation = xlCalculationManual
and
Application.Calculation = xlCalculationAutomatic
like enemy suggested, along with
Application.screenupdate = false
Might make it slightly faster, but I think the function-copy-paste will make the biggest difference.
I don't have time to post updated code at the moment, but hopefully I'll get to it tomorrow.
Hope that helps!
EDIT: Here's the revised code
WARNING: I haven't been able to test this code yet. I'll do so tomorrow and revise if needed.
Sub FunctionCopyPaste()
Dim iLastRow as Integer
With Worksheets("Data")
iLastRow = .UsedRange.Cells(.UsedRange.Rows.Count,1).Row
.Range("K2").Formula = "=IF(J2<>0,IF(D2>J2,J2,D2),D2)"
.Range("K2").Copy Range(Cells(2,4), Cells(iLastRow,1).Row,4))
End With
With Range(Cells(2,4), Cells(iLastRow,4))
.Copy
.PasteSpecial xlPasteValues
End With
End Sub
I'm not sure if it will make a difference, but since you are timing it, I'd be interested to know.
I modified your code slightly. The main change is For each D in worksheets. Otherwise, I used Cells(row,col) instead of Range. Not that I expect that change to save time, I just thought you might like to see another way of defining cells, instead of concatenating letters and numbers.
note: with cells, you can use all variables, and numbers, with no letters. I just used letters to show you the similarities.
also, Since you have a c + 1 in every row, why not also just start on row 2, leave out the multiple (+1s) and go from there?
UN-TESTED
Sub itS1Capped()
Dim Start, Finish, TotalTime 'What are you declaring these variables as?
Dim c, d, j, lastRow
Start = Timer
'find how many rows
lastRow = Sheets("Data").Cells(Rows.Count, "A").End(xlUp).row
'loop through all rows
For c = 2 To lastRow 'c = IT S0 Uncapped (OLD d)
j = Sheets("Data").Cells(c, "J").Value 'IT Cap = Cells(c, 10)
If j <> 0 Then
If c > j Then
Sheets("Data").Cells(c, "K").Value = j 'IT S1 Capped = j
Else
Sheets("Data").Cells(c, "K").Value = c 'IT S1 Capped = c
End If
Else
Sheets("Data").Cells(c, "K").Value = c 'IT S1 Capped = c
End If
Next c
Finish = Timer
TotalTime = Finish - Start
MsgBox TotalTime
End Sub
edit: replaced d with c
Have you tried turning off automatic recalculation before you run your script?
Application.Calculation = xlCalculationManual
And then turn it back on when you're done?
Application.Calculation = xlCalculationAutomatic
That usually speeds up processing of lots of rows assuming you're not changing something that needs recalculating before you work on the next (or subsequent) rows.
I wrote a macro to produce a histogram, given a certain selection. The code for the macro looks like this
Sub HistogramHelper(M As Range)
Dim src_sheet As Worksheet
Dim new_sheet As Worksheet
Dim selected_range As Range
Dim r As Integer
Dim score_cell As Range
Dim num_scores As Integer
Dim count_range As Range
Dim new_chart As Chart
Set selected_range = M
Set src_sheet = ActiveSheet
Set new_sheet = Application.Sheets.Add(After:=src_sheet)
title = selected_range.Cells(1, 1).Value
new_sheet.Name = title
' Copy the scores to the new sheet.
new_sheet.Cells(1, 1) = "Data"
r = 2
For Each score_cell In selected_range.Cells
If Not IsNumeric(score_cell.Text) Then
'MsgBox score_cell.Text
Else
new_sheet.Cells(r, 1) = score_cell
End If
r = r + 1
Next score_cell
num_scores = selected_range.Count
'Creates the number of bins to 5
'IDEA LATER: Make this number equal to Form data
Dim num_bins As Integer
num_bins = 5
' Make the bin separators.
new_sheet.Cells(1, 2) = "Bins"
For r = 1 To num_bins
new_sheet.Cells(r + 1, 2) = Str(r)
Next r
' Make the counts.
new_sheet.Cells(1, 3) = "Counts"
Set count_range = new_sheet.Range("C2:C" & num_bins + 1)
'Creates frequency column for all counts
count_range.FormulaArray = "=FREQUENCY(A2:A" & num_scores + 1 & ",B2:B" & num_bins & ")"
'Make the range labels.
new_sheet.Cells(1, 4) = "Ranges"
For r = 1 To num_bins
new_sheet.Cells(r + 1, 4) = Str(r)
new_sheet.Cells(r + 1, 4).HorizontalAlignment = _
xlRight
Next r
' Make the chart.
Set new_chart = Charts.Add()
With new_chart
.ChartType = xlBarClustered
.SetSourceData Source:=new_sheet.Range("C2:C" & _
num_bins + 1), _
PlotBy:=xlColumns
.Location Where:=xlLocationAsObject, _
Name:=new_sheet.Name
End With
With ActiveChart
.HasTitle = True
.HasLegend = False
.ChartTitle.Characters.Text = title
.Axes(xlCategory, xlPrimary).HasTitle = True
.Axes(xlCategory, _
xlPrimary).AxisTitle.Characters.Text = "Scores"
.Axes(xlValue, xlPrimary).HasTitle = True
.Axes(xlValue, xlPrimary).AxisTitle.Characters.Text _
_
= "Out of " & num_scores & " responses"
' Display score ranges on the X axis.
.SeriesCollection(1).XValues = "='" & _
new_sheet.Name & "'!R2C4:R" & _
num_bins + 1 & "C4"
End With
ActiveChart.SeriesCollection(1).Select
With ActiveChart.ChartGroups(1)
.Overlap = 0
.GapWidth = 0
.HasSeriesLines = False
.VaryByCategories = False
End With
r = num_scores + 2
new_sheet.Cells(r, 1) = "Average"
new_sheet.Cells(r, 2) = "=AVERAGE(A1:A" & num_scores & _
")"
r = r + 1
new_sheet.Cells(r, 1) = "StdDev"
new_sheet.Cells(r, 2) = "=STDEV(A1:A" & num_scores & ")"
End Sub
I am currently using a WorkBook that looks like this:
Eventually, I want to produce a macro that automatically iterates over each column, calling the Histogram Helper function with each column, producing multiple histograms over multiple worksheets. For now, I'm just trying to test putting in TWO ranges into HistogramHelper, like so:
Sub GenerateHistograms()
HistogramHelper Range("D3:D30")
HistogramHelper Range("E3:E30")
End Sub
However, upon running the Macro, I get a dialog box with the error number 400, one of the sheets is produced successfully with the worksheet title Speaker, and another sheet is produced with a numerical title and no content.
What is going on?
Edit: The workbook in question: https://docs.google.com/file/d/0B6Gtk320qmNFbGhMaU5ST3JFQUE/edit?usp=sharing
Edit 2- Major WTF?:
I switched the beginning FOR block to this for debugging purposes:
For Each score_cell In selected_range.Cells
If Not IsNumeric(score_cell.Text) Then
MsgBox score_cell.Address 'Find which addresses don't have numbers
Else
new_sheet.Cells(r, 1) = score_cell
End If
r = r + 1
Next score_cell
Whenever you run this, no matter which range you put as the second Macro call (in this case E3:E30) the program prints out that each cell $E$3- $E$30 is a non-text character. Why oh why?
Don't you need this?
Sheets(title).Activate
TIP: for this kind of recursive implementations implying many creations/deletions and getting every day more and more complex, I wouldn't ever rely on "Active" elements (worksheet, range, etc.), but in specific ones (sheets("whatever")) avoiding problems and easing the debugging.
------------------------ UPDATE
No, apparently, you don't need it.
Then, update selected_range.Cells(1, 1).Value such that it takes different values for each new worksheet, because this is what is provoking the error: creating two worksheets with the same name.
------------------------ UPDATE 2 (after downloading the spreadsheet)
The problem was what I thought: two worksheets created with the same name (well... not exactly: one of the spreadhsheets was intended to be called after a null variable). And the reason for this problem, what I thought too: relying on "Active elements". But the problem was not while using the ActiveSheet, but while passing the arguments: the ranges are given without spreadsheet and were taken from the last created spreadsheet. Thus, solution:
HistogramHelper Sheets("Sheet1").Range("D3:D30")
HistogramHelper Sheets("Sheet1").Range("E3:E30")
Bottom line: don't rely on "Active"/not-properly-defined elements for complex situations.
I have 3 issues with the following piece of code:
Intention of code: I have a table of data, 4 columns (F,G, H and I) wide and X rows long (X is typically between 5 and 400). I have a list of dates in column M, typically no more than 8 dates. Column H of table, contains dates as well. I want to find the dates that are in both columns (H and M) and whenever they appear, go to the same row in column I and set its value to zero, and the one after it (so if a match was in H100, then I100 and I101 would be zeroed).
issues with code: edited 1) as per feedback.
1) I have, using an if formula (=if(H100=M12,1,0), verified that there is one match, as how the spreadsheet sees it. The macro does not find this match, despite confirmation from the if formula. Cells I100 and I101 have nonzero values, when they should be zeroed.
2) the code runs, but takes about 3 minutes to go through 3 sheets of 180 rows of data. What can be done to make it run faster and more efficiently? It could have up to 30 sheets of data, and 400 rows (extreme example but possible, in this instance im happy to let it run a bit).
3) Assuming my data table before the macro is run, is 100 rows long, starting in row 12, after the macro, column I has nonzero values for 111 rows, and zeroes for the next 389. Is there a way I can prevent it from filling down zeroes, and leaving it blank?
I am using a correlate function afterwards on column I and there huge agreement of 0's with 0's is distorting this significantly. Thanks in advance,
Sub DeleteCells()
Dim ws As Worksheet
Dim cell As Range, search_cell As Range
Dim i As Long
Dim h As Long
Application.ScreenUpdating = False
For Each ws In ThisWorkbook.Worksheets
If Not ws.Name = "Cover" Then
For Each cell In ws.Range("H12:H500")
On Error Resume Next
h = ws.Range("G" & Rows.Count).End(xlUp).Row
i = ws.Range("L" & Rows.Count).End(xlUp).Row
Set search_cell = ws.Range("M12:M" & h).Find(what:=cell.Value, LookIn:=xlValues, lookat:=xlWhole)
On Error GoTo 0
If Not search_cell Is Nothing Then
ws.Range("I" & cell.Row).Value = 0
ws.Range("I" & cell.Row + 1).Value = 0
Set search_cell = Nothing
End If
Next cell
End If
Next ws
Application.ScreenUpdating = True
Set ws = Nothing: Set cell = Nothing: Set search_cell = Nothing
End Sub
EDIT: TESTED CODE, will work for 0, 1 row of data in H/M column starting from row 12?
EDIT: Updated the cell to handle case with 1 line of data, untested :|
I will give my solution first, this one should be much faster because it read the cells into memory first
Please comment if it doesn't work or you have further question
Sub DeleteCells()
Dim ws As Worksheet
Dim i As Long
Dim h As Long
Dim MColumn As Variant ' for convinence
Dim HColumn As Variant
Dim IColumn As Variant
Application.ScreenUpdating = False
For Each ws In ThisWorkbook.Worksheets
If Not ws.Name = "Cover" Then 'matching the target sheet
' matching the rows where column M's date matches column H's date
'starting row num is 12
With ws ' for simplifying the code
h = .Range("H" & .Rows.count).End(xlUp).Row
If h = 12 Then ' CASE for 1 row only
If Range("H12").Value = Range("M12").Value Then
Range("I12:I13").Value = ""
End If
ElseIf h < 12 Then
' do nothing
Else
ReDim HColumn(1 To h - 11, 1 To 1)
ReDim MColumn(1 To h - 11, 1 To 1)
ReDim IColumn(1 To h - 10, 1 To 1)
' copying the data from worksheet into 2D arrays
HColumn = .Range("H12:H" & h).Value
MColumn = .Range("M12:M" & h).Value
IColumn = .Range("I12:I" & h + 1).Value
For i = LBound(HColumn, 1) To UBound(HColumn, 1)
If Not IsEmpty(HColumn(i, 1)) And Not IsEmpty(MColumn(i, 1)) Then
If HColumn(i, 1) = MColumn(i, 1) Then
IColumn(i, 1) = ""
IColumn(i + 1, 1) = ""
End If
End If
Next i
'assigning back to worksheet cells
.Range("H12:H" & h).Value = HColumn
.Range("M12:M" & h).Value = MColumn
.Range("I12:I" & h + 1).Value = IColumn
End If
End With
End If
Next ws
Application.ScreenUpdating = True
End Sub
I know there are a ton of questions about constructing looped codes in vBA already but hopefully this will be a quick answer, i wasn't able to find a page addressing this issue.
My goal is to check the values from one range with values in another range, and if is a match it will perform a function and display results at the end. However, if the corresponding value in the range is "N/A" I want the results to display immediately and move onto the next checked value. Right now I am obtaining a 'no for loop' error for my code and i understand why. But I don't know how to fix this problem. Can anyone help?
Sub solubility()
Dim coeff As Range, groups As Range
Dim anion As Range
Dim a As Range
Dim nextrow As Long
Dim j As Range
Worksheets("properties").Select
Range("P7:P2000").Select
Selection.ClearContents
'solubility groups range
groups = Worksheets("Solubility").Range("A2:A33")
'group coefficients range
coeff = Worksheets("Solubility").Range("B2:B33")
anion = Worksheets("properties").Range("AB7:AB887")
For Each a In anion
For Each j In groups
If UCase(a.Value) = UCase(groups(j).Value) Then
If groups(j).Value = "" Or "N/A" Then
Worksheets("properties").Range("P" & a.Row).Value = "N/A"
Next a
Else
anvalue = coeff(j).Value * Range("AC" & a.Row).Value
End If
End If
If UCase(Range("AD" & a.Row).Value) = UCase(groups(j).Value) Then
cavalue = coeff(j).Value * Worksheets("properties").Range("AE" & a.Row).Value
If UCase(Range("AF" & a.Row).Value) = UCase(groups(j).Value) Then
cb1value = coeff(j).Value * Worksheets("properties").Range("AG" & a.Row).Value
End If
If UCase(Range("AH" & a.Row).Value) = UCase(groups(j).Value) Then
cb2value = coeff(j).Value * Worksheets("properties").Range("AI" & a.Row).Value
End If
Next j
If UCase(Range("AD" & a.Row).Value) = UCase("[MIm]") Then
cavalue = Range("AE" & a.Row) * Worksheets("solubility").Range("B2").Value + Range("AE" & a.Row) * Worksheets("solubility").Range("B7").Value
End If
nextrow = Worksheets("properties").Cells(Rows.Count, 15).End(xlUp).Offset(1, 0).Row
Worksheets("properties").Range("P" & nextrow).Value = _
anvalue + cavalue + cb1value + cb2value + Worksheets("solubility").Range("b34").Value
Next a
End Sub
I have the line 'Next a' twice, and excel doesnt like this, but I want to automatically jump to the next checked value without performing the remaining function if I get the "N/A" value.
I know this will rile the feathers of some of my purist brethren, but I would actually suggest a judicious use of GoTo in your case:
For Each a In anion
For Each j In groups
If UCase(a.Value) = UCase(groups(j).Value) Then
If groups(j).Value = "" Or "N/A" Then
Worksheets("properties").Range("P" & a.Row).Value = "N/A"
GoTo NextA
....
End If
End If
....
Next j
....
NextA:
Next a
Overuse of GoTo will quickly turn your code into spaghetti, but in this case I think it is actually the most readable option you have.
You must define a reference to an object using SET:
SET groups = Worksheets("Solubility").Range("A2:A33")
(Same for all ranges)