VBA code without using worksheet functions - vba

Is there any possible way to count and sum without using these codes or other worksheet functions?
range("C6").FormulaR1C1 = "=COUNTIF(Transaction!C2,Solution1!RC[-1])"
range("D6").FormulaR1C1 = "=SUMIF(Transaction!C2,Solution1!RC2,Transaction!C6)"
range("E6").FormulaR1C1 = "=SUMIF(Transaction!C2,Solution1!RC2,Transaction!C7)"
range("F6").FormulaR1C1 = "=SUMIF(Transaction!C2,Solution1!RC2,Transaction!C8)"

Related

VBA alternative to Application.Quotient that includes decimals

I am trying to use Application.AverageIfs and I need to divide the answer by 3. I tried this way:
Range("C1:C676") = (Application.IfError(Application.AverageIfs(Sheets(modelName).Range("R:R"....
Sheets(modelName).Range("U:U"), "OGV"), "0") / 3)
and also without the brackets round the first application and the final 3 but this gives a type mismatch error.
Nesting it within Application.Quotient works but it only gives the integer part of the answer and I need the decimal as well. Is there a decimal-friendly alternative? I would prefer to continue to use the application syntax rather than putting range().formula = "=averageifs( if its possible.
Edit: after J.Fox's suggestion I have broken the formula parts into variables. The problem seems to be the criZFrom and criZTo variables which use a range in a separate sheet as criteria. The formula works fine if I replace these variables with "1" and "2" respectively. The code is now:
Set rng = Sheets(wsName).Range("C1:C676")
Set avgCol = Sheets(modelName).Range("M:M")
Set colZFrom = Sheets(modelName).Range("G:G")
Set criZFrom = Sheets(wsName).Range("A1:A676")
Set colZTo = Sheets(modelName).Range("H:H")
Set criZTo = Sheets(wsName).Range("B1:B676")
Set colTime = Sheets(modelName).Range("V:V")
Set colVType = Sheets(modelName).Range("U:U")
criVType = "OGV"
criAM = "AM"
Range("A1:A676").Formula = "=roundup(row()/26,0)"
Range("B1:B676").Formula = "=if(mod(row(),26)=0,26,mod(row(),26))"
rng = Application.AverageIfs(avgCol, colZFrom, criZFrom, colZTo, criZTo, colTime, criAM, colVType, criVType) / 3
Here is some sample data:
from sheets(modelName), this has the data that I am trying to average and most of the criteria ranges:
From sheets(wsName), this has the criteria for the problem variables and is where I want the result to appear (in column C):
Looks like you're missing a closing parenthesis after "OGV") to close out the AverageIfs function, i.e.:
Range("C1:C676") = Application.IfError(Application.AverageIfs(Sheets(modelName).Range("R:R", Sheets(modelName).Range("U:U"), "OGV")), 0) / 3
Also, not sure if .... was just for on here or in your code, but you'd want to use _ instead, as in:
Range("C1:C676") = _
Application.IfError(Application.AverageIfs(Sheets(modelName).Range("R:R", _
Sheets(modelName).Range("U:U"), "OGV")), 0) / 3
Edit: If you're still getting an error, I suggest breaking up your formula into component parts and assigning each part to a variable so you can troubleshoot exactly where the issue is, like so:
Sub test()
Dim rng As Range, col1 As Range, col2 As Range, str As String, modelName As String
modelName = "Sheet1"
Set rng = Range("C1:C676")
Set col1 = Sheets(modelName).Columns(18)
Set col2 = Sheets(modelName).Columns(21)
str = "OGV"
rng = Application.IfError(Application.AverageIfs(col1, col2, str), 0) / 3
End Sub
Can we see some sample data? It might be an issue of what order the arguments are being passed for the AverageIfs function.
Edit 2: I think I might see what the problem is. You're using the AverageIfs function with the intention of validating each line separately based on the specific criteria for each line by using a range for Arg3 and Arg5 instead of single values, which AverageIfs doesn't like. Criteria for Ifs functions will always need to be a single value instead of a range of values. Instead, I think you would need to iterate each line separately using a loop, like this:
Set avgCol = Sheets(modelName).Range("M:M")
Set colZFrom = Sheets(modelName).Range("G:G")
Set colZTo = Sheets(modelName).Range("H:H")
Set colTime = Sheets(modelName).Range("V:V")
Set colVType = Sheets(modelName).Range("U:U")
criVType = "OGV"
criAM = "AM"
Range("A1:A676").Formula = "=roundup(row()/26,0)"
Range("B1:B676").Formula = "=if(mod(row(),26)=0,26,mod(row(),26))"
Dim x as Long
Dim t as Variant
For x = 1 To 676
Set criZFrom = Sheets(wsName).Range("A" & x)
Set criZTo = Sheets(wsName).Range("B" & x)
Set Rng = Sheets(wsName).Range("C" & x)
t = Application.WorksheetFunction.AverageIfs(avgCol, colZFrom, criZFrom.Value, colZTo, criZTo.Value, colTime, criAM, colVType, criVType)
t = CDbl(t / 3)
Rng.Value = t
Next x

Dynamically add items from a file to a ComboBox

I am working on an application that allows the user to dynamically add to and remove items from an excel file. The quantity of items shall be unlimited.
I am looking for a way to grab the items from the excel file and transfer them to the ComboBox.
To make myself clearer: The problem is not iterating through cells, but getting cell values into the ComboBox. I need a method that captures the content of all cells with values in a given column, where the end of range is unknown and then transfer the values to a ComboBox.
The Combobox only accepts values, not any empty cells. I also don't want fields in the ComboBox that say "No Value".
I have tried itering through cells and range methods, but this doesn't get the values into the ComboBox.
What I have so far is:
wb = load_workbook (source_file)
ws = wb.active
self.value_1 = ws['B2'].value
self.value_2 = ws['B3'].value
self.value_3 = ws['B4'].value
self.value_4 = ws['B5'].value
self.value_5 = ws['B6'].value
self.value_6 = ws['B7'].value
self.value_7 = ws['B8'].value
self.value_8 = ws['B9'].value
self.value_9 = ws['B10'].value
self.value_10 = ws['B11'].value
stock_items = [ self.value_1 , self.value_2 , self.value_3 , self.value_4 , self.value_5 ,
self.value_6 , self.value_7 , self.value_8 , self.value_9 , self.value_10 ]
self.combo_items_list = [ ]
for stock_item in stock_items :
if stock_item != None :
self.combo_items_list.append (stock_item)
self.combo.addItems(self.combo_items_list)
This works as expected, but what troubles me is that I have to add a line of code for each item I grab from the excel file, besides having to put an extra entry into the stock_items list. If there were 5.000 items in the file, that would result in 5.000 lines of code and 5000 entries in the list.
Is there a more efficient and elegant way to handle the issue with "counter" or pandas?
Thanks in advance.
I found a way to do this nicely using Pandas, not opnpyxl :
import pandas as pd
import numpy as np
# get sheet and whole column
sales = pd.read_excel ("Inventory.xlsx")
# filter out any None Values
sales_article = sales ["Artigo"] .dropna()
# transform into list
sales_list = sales_article.values.tolist()
# add list to ComboBox
self.combo.addItems(sales_list)
In openpyxl 2.4 worksheets have an iter_cols method that allow you to select a range of cells and have them returned as columns. Just like iter_rows returns them as rows. This is the simplest and most efficient way to do what you want to do.
See https://openpyxl.readthedocs.io/en/default/tutorial.html#accessing-many-cells for details.
An example for your use case:
cells = [cell.value for cell in ws.iter_cols(min_col=2, max_col=2, min_row=2) if cell.value is not None]
wb = load_workbook(source_file)
ws = wb.active
lastrow = ws.UsedRange.Height # don't remember method name
for row in range(lastrow):
value = ws['B' + str(row + 2)].value
if value is not None:
self.combo_items_list.append (value)
self.combo.addItems(self.combo_items_list)
See worksheet docs for other ways to get range of excel rows.
wb = load_workbook (source_file)
ws = wb.active
self.combo_items_list = [ ]
// loop from 2(start)-11(end)
// check if ws['B'<counter>].value is available and not null
// add this value to your array of combo for each ittration.
self.combo_items_list.append (ws['B'<counter>].value)
self.combo.addItems(self.combo_items_list)
Sorry, if i am getting it wrong. I don't know the syntax of given language. Still saw a logical answer and decided to post.

VBA for loop with VLookups avoiding duplicates

I am compiling a list of employees CPD classes and the hours they are, producing a printout essentially for each one. I am pulling data from multiple worksheets and am having trouble with VLOOKUP continually finding the same data. My code currently is:
result = lookup.Find(name) Is Nothing
If (result = False) Then
For Each cell In lookup
className = Application.VLookup(name, lookup, 7, True)
classHours = Application.VLookup(name, lookup, 9, True)
Sheets("employee output").Range("B" & counter).Formula = className
Sheets("employee output").Range("C" & counter).Formula = classHours
empHours = empHours + classHours
counter = counter + 1
numClasses = numClasses + 1
corporate = Range("B" & i) *** vba precompliles the loop so this doesn't work *
Set lookup = Range("corporate:K") ** doesn't work either***
Next
End If
So I need to limit the scope of the lookup range or somehow avoid finding the same data. Any help would be greatly appreciated...
I assume your lookup variable represents the range containing you data.
Your data duplication propably comes from your for each loop. Indeed, you are iterating trough each Cell of your range. That means that once the name is found, the code Inside the loop is executed as many times as there are cells in the range lookup.
Try changing your loop to the following:
Dim row as Range
for each row In lookup.Row
'your code goes here
Next
Whit this, the code will only run once for each rows.

VBA in Excel: writing cell value to a column if it doesn't already exist there

Ok, so I have a table of names in one column and corresponding numbers in another. Most names appear more than once and each time with a different number. The table is likely to be added to in the future. I'm trying to write a VBA macro that will output each name once and the total sum of the numbers attached to them on a separate sheet. I haven't used VBA in like 8 months and I'm really rusty. Suggestions?
remember to add the reference microsoft scripting runtime, i believe. u would need to use dictionary here.
Sub test()
Dim var As Variant
Dim rng As Range
Set rng = Range("A1:A100")
Dim dico As Dictionary
Set dico = New Dictionary
For Each var In rng.Cells
if dico.Exists(var.value) Then
dico(var.value) = dico(var.value) + var.offset(0,1).value
else
dico.Add var.value, var.offset(0,1).value
end if
Next var
Set rng = Range("C1")
Dim i as double
i = 0
For Each var In dico.keys
rng.offset(i).value = var
rng.offset(i,1).value = dico(var)
Next var
End sub

Fastest way to write cells to Excel with Office Interop?

I am writing a function to export data to Excel using the Office Interop in VB .NET. I am currently writing the cells directly using the Excel worksheet's Cells() method:
worksheet.Cells(rowIndex, colIndex) = data(rowIndex)(colIndex)
This is taking a long time for large amounts of data. Is there a faster way to write a lot of data to Excel at once? Would doing something with ranges be faster?
You should avoid reading and writing cell by cell if you can. It is much faster to work with arrays, and read or write entire blocks at once. I wrote a post a while back on reading from worksheets using C#; basically, the same code works the other way around (see below), and will run much faster, especially with larger blocks of data.
var sheet = (Worksheet)Application.ActiveSheet;
var range = sheet.get_Range("A1", "B2");
var data = new string[3,3];
data[0, 0] = "A1";
data[0, 1] = "B1";
data[1, 0] = "A2";
data[1, 1] = "B2";
range.Value2 = data;
If you haven't already, make sure to set Application.ScreenUpdating = false before you start to output your data. This will make things go much faster. The set it back to True when you are done outputting your data. Having to redraw the screen on each cell change takes a good bit of time, bypassing this saves that.
As for using ranges, you still will need to target 1 (one) specific cell for a value, so I see no benefit here. I am not aware of doing this any faster than what you are doing in regards to actually outputting the data.
Just to add to Tommy's answer.
You might also want to set the calculation to manual before you start writing.
Application.Calculation =
xlCalculationManual
And set it back to automatic when you're done with your writing. (if there's a chance that the original mode could have been anything other than automatic, you will have to store that value before setting it to manual)
Application.Calculation =
xlCalculationAutomatic
You could also use the CopyFromRecordset method of the Range object.
http://msdn.microsoft.com/de-de/library/microsoft.office.interop.excel.range.copyfromrecordset(office.11).aspx
The fastest way to write and read values from excel ranges is Range.get_Value and Range.set_Value.
The way is as below:
Range filledRange = Worksheet.get_Range("A1:Z678",Missing);
object[,] rngval = (object[,]) filledRange.get_Value (XlRangeValueDataType.xlRangeValueDefault);
Range Destination = Worksheet2.get_Range("A1:Z678",Missing);
destination.set_Value(Missing,rngval);
and yes, no iteration required. Performance is just voila!!
Hope it helps !!
Honestly, the fastest way to write it is with comma delimiters. It's easier to write a line of fields using the Join(",").ToString method instead of trying to iterate through cells. Then save the file as ".csv". Using interop, open the file as a csv which will automatically do the cell update for you upon open.
In case someone else comes along like me looking for a full solution using the method given by #Mathias (which seems to be the fastest for loading into Excel) with #IMil's suggestion on the Array.
Here you go:
'dt (DataTable) is the already populated DataTable
'myExcelWorksheet (Worksheet) is the worksheet we are populating
'rowNum (Integer) is the row we want to start from (usually 1)
Dim misValue As Object = System.Reflection.Missing.Value
Dim arr As Object = DataTableToArray(dt)
'Char 65 is the letter "A"
Dim RangeTopLeft As String = Convert.ToChar(65 + 0).ToString() + rowNum.ToString()
Dim RangeBottomRight As String = Convert.ToChar(65 + dt.Columns.Count - 1).ToString() + (rowNum + dt.Rows.Count - 1).ToString()
Dim Range As String = RangeTopLeft + ":" + RangeBottomRight
myExcelWorksheet.Range(Range, misValue).NumberFormat = "#" 'Include this line to format all cells as type "Text" (optional step)
'Assign to the worksheet
myExcelWorksheet.Range(Range, misValue).Value2 = arr
Then
Function DataTableToArray(dt As DataTable) As Object
Dim arr As Object = Array.CreateInstance(GetType(Object), New Integer() {dt.Rows.Count, dt.Columns.Count})
For nRow As Integer = 0 To dt.Rows.Count - 1
For nCol As Integer = 0 To dt.Columns.Count - 1
arr(nRow, nCol) = dt.Rows(nRow).Item(nCol).ToString()
Next
Next
Return arr
End Function
Limitations include only allowing 26 columns before it would need better code for coming up with the range value letters.