I am trying to get the earliest start date (min) and the furthest end date (max) based on criteria in a source column. I have created several functions based on a solution I found on the internet. I have also tried an array formula solution without using VBA. Neither of the approaches have worked. I have found similar questions/answers on SO but none that correctly apply to my situation.
In my example below I have a Task worksheet and an Export worksheet. The Export worksheet is the source data. In the Task worksheet I am trying to enter a formula that finds the minimum start date. Each Task ID can have several dates so I am trying to find the lowest and highest start dates for each of the tasks. I originally tried using an array formula but ran into the same problem which is that sometimes the formula produces the correct answer and sometimes it gives an incorrect answer and I cannot locate the source of the issue. Any help is much appreciated!
VBA Functions:
Function getmaxvalue(Maximum_range As Range)
Dim i As Double
For Each cell In Maximum_range
If cell.Value > i Then
i = cell.Value
End If
Next
getmaxvalue = i
End Function
Function getminvalue(Minimum_range As Range)
Dim i As Double
i = getmaxvalue(Minimum_range)
For Each cell In Minimum_range
If cell.Value < i Then
i = cell.Value
End If
Next
getminvalue = i
End Function
Function GetMinIf(SearchRange As Range, SearchValue As String, MinRange As Range)
Dim Position As Double
Position = 1
Dim getminvalue As Double
getminvalue = MinRange.Rows(1).Value
For Each cell In SearchRange
If LCase(SearchValue) = LCase(cell.Value) And MinRange.Rows(Position).Value < getminvalue Then
getminvalue = MinRange.Rows(Position).Value
End If
Position = Position + 1
Next
GetMinIf = getminvalue
End Function
Function GetMaxIf(SearchRange As Range, SearchValue As String, MaxRange As Range)
Dim Position As Double
Position = 1
Dim getmaxvalue As Double
For Each cell In SearchRange
If LCase(SearchValue) = LCase(cell.Value) And MaxRange.Rows(Position).Value > getmaxvalue Then
getmaxvalue = MaxRange.Rows(Position).Value
End If
Position = Position + 1
Next
GetMaxIf = getmaxvalue
End Function
The issue is that you are trying to equate positions incorrectly. Use this for the MinIf, it no longer needs the secondary function:
Function GetMinIf(SearchRange As Range, SearchValue As String, MinRange As Range)
Dim srArr As Variant
srArr = Intersect(SearchRange.Parent.UsedRange, SearchRange).Value
Dim mrArray As Variant
mrarr = Intersect(MinRange.Parent.UsedRange, MinRange).Value
Dim minTemp As Double
minTemp = 9999999999#
Dim i As Long
For i = 1 To UBound(srArr, 1)
If LCase(SearchValue) = LCase(srArr(i, 1)) And mrarr(i, 1) < minTemp Then
minTemp = mrarr(i, 1)
End If
Next i
GetMinIf = minTemp
End Function
Max:
Function GetMaxIf(SearchRange As Range, SearchValue As String, MaxRange As Range)
Dim srArr As Variant
srArr = Intersect(SearchRange.Parent.UsedRange, SearchRange).Value
Dim mrArray As Variant
mrarr = Intersect(MaxRange.Parent.UsedRange, MaxRange).Value
Dim maxTemp As Double
maxTemp = 0
Dim i As Long
For i = 1 To UBound(srArr, 1)
If LCase(SearchValue) = LCase(srArr(i, 1)) And mrarr(i, 1) > maxTemp Then
maxTemp = mrarr(i, 1)
End If
Next i
GetMaxIf = maxTemp
End Function
As far as formula go IF you have OFFICE 365 then use MINIFS
=MINIFS(Export!F:F,Export!A:A,A2)
=MAXIFS(Export!G:G,Export!A:A,A2)
If not use AGGREGATE:
=AGGREGATE(15,7,Export!$F$2:F$26/(Export!$A$2:A$26=A2),1)
=AGGREGATE(14,7,Export!$G$2:G$26/(Export!$A$2:A$26=A2),1)
I was trying to use Scott's method as part of a macro to transform an invoice. However, the rows of the invoice fluctuate every month and could grow to as many as a million in the future. Anyway, the bottomline is that I had to write the formula in a way where I could make the last row dynamic, which made the macro go from taking 10-15 minutes (by hardcoding a static last row like 1048576 to run to ~ 1 minute to run. I reference this thread to get the idea for the MINIFS workaround and another thread to figure out how to do a dynamic last row. Make vba excel function dynamic with the reference cells
I'm sure there are other methods, perhaps using offset, etc. but I tried other methods and this one was pretty quick. Anyone can use this VBA formula if they do the following:
15 to 14 to do a maxifs, keep as is for minifs
change the relevant rows and columns in Cells(rows, columns) format below.
The True/False parameters passed to .Address() will lock/unlock the rows/columns respectively (i.e. add a $ in front if True).
Change the last row
First, get the last row
Dim LastRow As Long
LastRow = Cells(Rows.Count, "A").End(xlUp).Row
Second, here is the dynamic minifs
Range("F2").Formula = "=AGGREGATE(15,7," & Range(Cells(2, 6), Cells(LastRow, 6)).Address(True, True) & "/(" & Range(Cells(2, 1), Cells(LastRow, 1)).Address(True, True) & "=" & Range(Cells(2, 1), Cells(2, 1)).Address(False, True) & "),1)"
Third, autofill down.
Range("F2").AutoFill Destination:=Range("F2:F" & LastRow)
Related
Given that I have a column of values in the format
01.2020
12.2021
3.2019
02.2020
etc.
over a million rows or so, I want a VBA function to find the maximum value of the digits to the left of the period.
Something like
Option Explicit
Sub test()
Dim r As Range, c As Range
Dim max As Long, current As Long
Dim s As String
With År_2020
Set r = .Range(.Range("D2"), .Range("D" & .Rows.Count).End(xlUp))
End With
For Each c In r
current = CLng(Left(c, InStr(1, c, ".", vbBinaryCompare) - 1))
If current > max Then max = current
Next c
Debug.Print max
End Sub
works, but I feel like there ought to be a simpler solution.
Can someone please give some input on whether they can find a simpler solution?
Formula solution
formula in B1: =VALUE(LEFT(A1,FIND(".",A1)-1))
in C1: =MAX(B:B)
VBA solution
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Sheet1")
Dim LastRow As Long 'find last used row
LastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
Dim ValuesToConvert() As Variant 'read data into array
ValuesToConvert = ws.Range("A1", "A" & LastRow).Value 'will throw an error if there is only data in A1 but no data below, make sure to cover that case
Dim iVal As Variant
For iVal = LBound(ValuesToConvert) To UBound(ValuesToConvert)
'truncate after period (might need some error tests if period does not exist or cell is empty)
ValuesToConvert(iVal, 1) = CLng(Left(ValuesToConvert(iVal, 1), InStr(1, ValuesToConvert(iVal, 1), ".") - 1))
Next iVal
'get maximum of array values
Debug.Print Application.WorksheetFunction.Max(ValuesToConvert)
Seems to work. And it is short.
Sub test()
Dim max As Long
max = [MAX(TRUNC(D:D))]
Debug.Print max
End Sub
I always like to propose a naive solution. On the face of it you could do this:
=TRUNC(MAX(--D:D))
(leave the truncation till the end because the part of the number after the decimal point doesn't affect the result)
As pointed out below, this only works correctly for those locales that use a decimal point (period) not a decimal comma.
Put the code I'm working with at the bottom.
I am trying to enter a set of formulas in a range with code. The formulas enter correctly, but they only calculate if I click on each cell, click on the formula box, and hit enter.
I've tried all suggestions I can find online, hence the last three lines of code. It's not a matter of the cells changing format, they remain General after the code runs. I'm completely stumped.
Sub ButtonTest()
Dim Number As Integer
Number = Range("M2").Value
Dim TestArray() As String
ReDim TestArray(0, 1 To Number)
Dim Values As Integer
Dim RangeValues As Integer
Dim Variable As Integer
Variable = 3
TestArray(0, 1) = "=1"
For Values = 2 To Number
TestArray(0, Values) = "=" & Cells(3, Variable).Address & "+1"
Variable = Variable + 1
Next Values
Range("C3:" & Cells(3, Number + 2).Address).Formula = TestArray
Application.Calculation = xlCalculationAutomatic
ActiveSheet.EnableCalculation = True
ActiveSheet.Calculate
End Sub
To enter a formula from an array use .FormulaArray rather than .Formula
Range("C3:" & Cells(3, Number + 2).Address).FormulaArray = TestArray
Not sure if I'm off the mark here, but I always had the impression that formulas couldn't be written to a Range in an array (as you would with values, for example). I believe you have to write one cell at a time.
There may be a method that can be called to convert array values to evaluated cells, but I don't know of it - be happy to hear if anyone can enlighten me though.
In the meantime, the code below gives an example of how your code could be made to work:
Dim iterationCount As Long, i As Long
Dim cell As Range
iterationCount = Sheet1.Range("M2").Value
Set cell = Sheet1.Range("C3")
cell.Formula = "=1"
For i = 1 To iterationCount
cell.Offset(, 1).Formula = "=" & cell.Address & " + 1"
Set cell = cell.Offset(, 1)
Next
I have a very simple question, which unfortunately I can not resolve. Thus would appreciate your help. Here is the thing:
I should obtain last column from the range as a range. For example, if I have A26:D32, I should get D26:D32 as a result and input into the loop.
This is the code I have so far:
Function getSmth(CustomCol As Range)
Dim i As Double
For Each cell In CustomCol 'start of the loop. CustomCol here should return D26:D32 already
If cell.Value > i Then
i = cell.Value
End If
Next
....
End Function
What I have tried to do was writing CustomCol.Columns(6), as I know the last column, but it did not work out.
Would really be glad for your help!
You can try this:
Function GetLasRangeCol(rng As Range) As Range
Set GetLasRangeCol = rng.Columns(rng.Columns.Count).Cells
End Function
That you may use in your calling code as:
For Each cell In GetLasRangeCol(myRange)
Where ‘myRange’ is a valid range reference, i.e: either a variable of Range type or some Range object (like ‘Range(“A5:B21”)’) to get last column out of
There's probably a shorter way to it, but this does the trick:
Function lastColumn(rg As Range) As Range
Set lastColumn = Range(Cells(Range(Split(rg.Address(0, 0), ":")(0)).Row, _
Range(Split(rg.Address(0, 0), ":")(1)).Column).Address(0, 0) _
& ":" & Split(rg.Address(0, 0), ":")(1))
End Function
It take a Range object as a parameter and returns a Range object.
Example Usage:
Sub demo()
Dim rg As Range, rg2 As Range
Set rg = Range("A26:D32")
Set rg2 = lastColumn(rg)
Debug.Print "The last column is: " & rg2.Address
End Sub
...returns: The last column is: $D$26:$D$32
There's probably a "tidier" method (perhaps using INTERSECT) but this will work fine.
Explanation:
This is the same function as above, but broken down so it's easier to understand:
Function lastColumn(rg As Range) As Range
Dim firstRow, lastRow, firstCol, lastCol, leftPart, rightPart
leftPart = Split(rg.Address(0, 0), ":")(0)
rightPart = Split(rg.Address(0, 0), ":")(1)
firstRow = Range(leftPart).Row
firstCol = Range(leftPart).Column
lastCol = Range(rightPart).Column
Set lastColumn = Range(Cells(firstRow, lastCol), Range(rightPart))
End Function
More Information:
MSDN : Application.Range Property (Excel)
MSDN : Range.Cells Property (Excel)
Office Support : Split Function (VBA)
If you want the last column as a range, then
myRange.Columns(myRange.Columns.Count)
If you want the number of the last column then
myRange.Columns(myRange.Columns.Count).Column
To find last column in row 1:
Dim LastCol As Long
LastCol = Sheets("Sheet1").Cells(1, Columns.Count).End(xlToLeft).Column
Excel Data
The image is for the excel data I am playing around with. I will attach my code later. But I am trying to fill Column H with the first found cell of each row from Column A-E. Ex. for row 1 it should find "B" and place that to H, row 2 should have "c" place that to "H", and so on row 3 "is" to H, row 4 "a" to H.
I cannot for the life of me figure this out. VBA has never been my strongest suit and I have been playing around with this for 2 days now. Here is my code.
Function findValue() As String
Dim rng As Range
Dim row As Range
Dim cell As Range
Dim val As String
' Sets range of 5 columns to search in by column
Set rng = Range("A:E")
' searches through count of rows
For i = 2 To Range("A" & Rows.Count).End(xlUp).row
For Each cell In rng.Cells(i)
If IsEmpty(cell) = True Then
MsgBox cell
MsgBox i
Else
'MsgBox Range.(cell & i).Value
findValue = cell
Set rng = Range("A:E")
Exit For
End If
Next cell
Next i
End Function
Any Help is greatly appreciated.
The formula is:
=INDEX(A1:E1,AGGREGATE(15,6,COLUMN(A1:E1)/(A1:E1<>""),1))
If this is intended as a UDF, I believe that the following code is what you are after:
Function findValue() As String
Application.Volatile = True
Dim r As Long
Dim c As Long
r = Application.Caller.Row
For c = 1 To 5
If Not IsEmpty(Cells(r, c)) Then
findValue = Cells(r, c).Value
Exit Function
End If
Next
findValue = ""
End Function
An alternative method, where you pass the range to be checked rather than just checking the current row, would be:
Function findValue(rng As Range) As String
Dim c As Range
For Each c In rng
If Not IsEmpty(c) Then
findValue = c.Value
Exit Function
End If
Next
findValue = ""
End Function
This could then be used in cell H2 as =findvalue(A2:E2), and has the advantage that it does not need to be marked Volatile. ("Volatile" functions have to be recalculated every time anything at all changes on the worksheet.)
P.S. I strongly suggest that you use an Excel formula instead (such as the one in Scott's answer) - why reinvent the wheel when Excel already provides the functionality?
I'm not by my PC so can't test it, but you could try this
Sub FindValue()
Dim myRow As Range
' Sets Range of 5 columns to search in by column
Set rng = Intersect(Range("A:E"),ActiveSheet.UsedRange)
' searches through count of rows
For each myRow in rng.Rows
Cells(myRow.Row, "H").Value = myRow.Cells.SpecialCells(xlCellTypeConstants).Cells(1)
Next
End Sub
This is some VBA code I've written for Excel. I'm trying to match entries in Sheet1 with those in Sheet2. The structure of both sheets is as follows:
DATE | ID |
----- ----
Date1 ID1
Date2 ID2...
In my code, I loop through the rows of the first sheet, and set the values from each particular row as part of my MATCH() query, in hopes of finding these same values in the second sheet. When I do, I want MATCH() to return the row index it finds these values in, so I can use that same row to input further information from the first sheet. This query uses multiple criteria, as indicated by both the value and searchRange variables (I'm trying to use the multiple criteria via concatenation method, as seen in this article).
The problem is, I consistently get a WorksheetFunction.Match could not be used error. When I used one single criteria (the ID), the function worked. When I tried to use multiple ones, it failed, even though I followed the instructions seen in the previously linked article. Any suggestions or ideas to fix this would be appreciated.
Sub runComparison(Sheet1 As String, Sheet2 As String)
Dim rowCount As Variant, columnCount As Variant, information As Variant
Dim counter As Integer
Dim value As String, searchRange As String
Sheets(Sheet2).Select
'Array of the number of rows in both sheets
rowCount = Array(Sheets(Sheet1).Cells(Rows.count, "A").End(xlUp).row, Sheets(Sheet2).Cells(Rows.count, "A").End(xlUp).row)
'Array of the number of columns in both sheets
columnCount = Array(Sheets(Sheet1).Cells(1, Columns.count).End(xlToLeft).Column, Sheets(Sheet2).Cells(1, Columns.count).End(xlToLeft).Column)
'The range in which we will look for the date and the ID
searchRange = CStr(Range(Cells(2, 1), Cells(rowCount(1), 1)).Address & "&" & Range(Cells(2, 2), Cells(rowCount(1), 2)).Address)
counter = 2
Do Until counter = rowCount(0)
'Sets the search term equal to the current cell in Sheet1
value = Sheets(Sheet1).Cells(counter, 1) & "&" & Sheets(Sheet2).Cells(counter, 2)
' Attempts to set the cell in the 8th column in the same row in which the search term is found equal to a certain value from the search term's row
Cells(WorksheetFunction.Match(value, searchRange, 0), 8) = Sheets(Sheet1).Cells(counter, columnCount(0)).value
counter = counter + 1
Loop
End Sub
Edit: Here's some sample input
value = '7/14/2014&ESTUOUW1046465464'
searchRange = '$A2:$A298&$B2:B298'
UPDATED
Thanks for clarifying in comments. I removed my original answer as it pertains only to the regular "Match" function, and I see the reference/example and understand what you're trying to do now which involves an array formula.
Let's try this using Application.Evaluate which will avoid the need to put this formula in a cell. Using the example data from MS, I did this which seems to work:
Sub test()
Dim value As String
Dim srcRange As String
value = "D2&E2"
srchRange = "$A$2:$A$5&$B$2:$B$5"
Debug.Print Application.Evaluate("=MATCH(" & value & "," & srchRange & ",0)")
End Sub
Applying that in your code, I think would be like below. YOu will still want to Dim matchVal as Variant to hold the result of the formula evaluation, I think. Then do this:
Do Until counter = rowCount(0)
'Sets the search term equal to the current cell in Sheet1
value = Sheets(Sheet1).Cells(counter, 1) & "&" & Sheets(Sheet2).Cells(counter, 2)
'## Assign the result of the Match function to a variable
matchVal = Application.Evaluate("=MATCH(" & value & "," & searchRange & ",0)")
'## Check for errors, and handle as needed:
If IsError(matchVal) Then
'modify as needed, this highlight the cell with the non-matched value
' you might omit this line and simply ignore it, or you could
' display a MsgBox prompt, etc.
Sheets(Sheet1).Cells(counter, columnCount(0)).Interior.ColorIndex = 6
Else:
Cells(matchVal, 8) = Sheets(Sheet1).Cells(counter, columnCount(0)).value
End If
counter = counter + 1
Loop