VBA to check if multiple values fall within multiple ranges - excel

I have a list of about 2000 values in column A in Excel, and then a list of the start and end of value ranges in the next two columns. The range values don't correspond to the values in the first column. I want to check, for every value in column A, whether the value falls within ANY of the ranges listed in columns B and C.
So for example, in the image below, see whether A2 falls within B2-C2, B3-C3, OR B4-C4. Then the same again for A3 and A4. For each of these I want true/false to be entered in column D. The true/false value would correspond to the values in column A.
I've been trying to do this in VBA but I'm not totally confident with getting it to search the ranges. Current code is below.
Sub CheckRg()
Dim wk As Worksheet, frow As Long, i As Long
Set wk = Sheet1
frow = wk.Range("A" & Rows.Count).End(xlUp).Row
For i = 2 To frow
If wk.Range("A" & i).Value >= wk.Range("B:B").Value And wk.Range("A" & i).Value <= wk.Range("C:C").Value Then
wk.Range("D" & i).Value = "TRUE"
Else
wk.Range("D" & i).Value = "FALSE"
End If
Next i
End Sub

This formula should do the trick without VBA:
=COUNTIFS($B:$B,"<="&A2,$C:$C,">="&A2)<>0
You can use it in your code like this:
Sub CheckRg()
Dim wk As Worksheet, frow As Long, i As Long
Set wk = Sheet1
frow = wk.Range("A" & Rows.Count).End(xlUp).Row
For i = 2 To frow
With Excel.WorksheetFunction
wk.Range("D" & i).Value = .CountIfs(wk.Range("B:B"), Evaluate("""<=""" & "&A" & i), wk.Range("C:C"), Evaluate(""">=""" & "&A" & i)) <> 0
End With
Next i
End Sub

An Inefficient Double Loop
A better way to go is presented in the solution by Evil Blue Monkey.
You need to check each cell in column A against each cell pair of columns B and C which requires a second loop that slows down the operation when thousands of rows are expected.
Here's an example of how you could go about that.
Sub CheckRg()
Dim ws As Worksheet: Set ws = Sheet1
Dim lRow As Long: lRow = ws.Range("A" & ws.Rows.Count).End(xlUp).Row
Application.ScreenUpdating = False
Dim i As Long
Dim j As Long
Dim MatchFound As Boolean
For i = 2 To lRow
For j = 2 To lRow
If ws.Range("A" & i).Value >= ws.Range("B" & j).Value _
And ws.Range("A" & i).Value <= ws.Range("C" & j).Value Then
MatchFound = True
Exit For
End If
Next j
If MatchFound Then
ws.Range("D" & i).Value = True
MatchFound = False
Else
ws.Range("D" & i).Value = False
End If
Next i
Application.ScreenUpdating = True
MsgBox "Range checked.", vbInformation
End Sub

Related

Comparing Datasets in Excel

I have two sets of monthly data, and I'm trying to determine how this data changes each month.
For example:
Column A is a list of identifiers
Column B is a score (1 through 5) for each identifier
Both column A and column B change month-to-month.
How can I determine the number of identifiers in each 1-5 bucket that are the same and different in month t+1 vs month t=0?
Thanks!
Please, try the next code:
Sub ScoresCompare()
Dim sh0 As Worksheet, sh As Worksheet, lastRow As Long
Dim iCount As Long, i As Long, j As Long
Set sh0 = ActiveSheet 'use here the reference one
Set sh = Workbooks("To be checked").Worksheets(1) 'use here the sheet of the workbook to be checked
lastRow = sh0.Range("A" & Rows.count).End(xlUp).Row
For i = 2 To lastRow
For j = 2 To lastRow
If sh0.Range("A" & i).Value = sh.Range("A" & j).Value Then
If sh0.Range("B" & i).Value <> sh.Range("B" & j).Value Then
iCount = iCount + 1: Exit For
End If
End If
Next j
Next i
MsgBox "There are " & iCount & " differences. " & _
lastRow - 1 - iCount & " are unchanged..."
End Sub

How to insert a specific value at the beginning of text in a Column using Excel VBA?

I'm trying to insert this character "-" at the beginning of each text in column F.
My F column looks like this:
BP850
BP851
BT100
GP160
GP161
I tried this code:
Option Explicit
Sub test()
Dim LastRow As Long, i As Long
Dim str As String
'Change sheet if needed
With ThisWorkbook.Worksheets("Sheet1")
'Find the last row of column F
LastRow = .Cells(.Rows.Count, "F").End(xlUp).Row
'Loop column F
For i = 1 To LastRow
'Replace the first occurance
str = Replace(.Range("F" & i).Value, "", "-", 1, 1)
.Range("F" & i).Value = str
Next i
End With
End Sub
I expect:
-BP850
-BP851
-BT100
-GP160
-GP161
As an alternative, you could try to utilize .Evaluate. This prevents the need for any loop:
Option Explicit
Sub test()
Dim LastRow As Long
Dim rng As Range
'Change sheet if needed
With ThisWorkbook.Worksheets("Sheet1")
'Find the last row of column F
LastRow = .Cells(.Rows.Count, "F").End(xlUp).Row
Set rng = .Range("F1:F" & LastRow)
rng.Value = .Evaluate("""'-""&" & rng.Address)
End With
End Sub
Instead of using replace, just concatenate "-" before str
Sub test()
Dim LastRow As Long, i As Long
Dim str As String
'Change sheet if needed
With ThisWorkbook.Worksheets("Sheet1")
'Find the last row of column F
LastRow = .Cells(.Rows.Count, "F").End(xlUp).Row
'Loop column F
For i = 1 To LastRow
'Replace the first occurance
str = "-" & .Range("F" & i).Value '<== EDIT
.Range("F" & i).Value = str
Next i
End With
End Sub
Your str = Replace(.Range("F" & i).Value, "", "-", 1, 1) has problem. Change it to following:
For i = 1 To LastRow
.Range("F" & i).Value = "-" & .Range("F" & i).Value
Next i
No need to use str variable here.
See if following approach helps.
It will also check
Cell is not blank
Cell doesn't begin with "-"
which should help in case of accidental rerun of the macro!
Option Explicit
Sub test2()
Dim LastRow As Long, i As Long
'Change sheet if needed
Application.ScreenUpdating = False
With ThisWorkbook.Worksheets("Sheet1")
'Find the last row of column F
LastRow = .Cells(.Rows.Count, "F").End(xlUp).Row
'Loop column F
For i = 1 To LastRow
If Len(.Range("F" & i).Value) > 0 And Left(.Range("F" & i).Value, 1) <> "-" Then _
.Range("F" & i).Value = "-" & .Range("F" & i).Value
Next i
End With
Application.ScreenUpdating = True
End Sub
try this, ie. add "'-" in front of the value. Note the single quote before the -
For i = 1 To lastRow
If .Range("F" & i).Value <> "" Then
If Left(.Range("F" & i).Value, 1) <> "-" Then
.Range("F" & i).Value = "'-" & .Range("F" & i).Value
End If
End If
Next i
Option Explicit
Sub test()
Dim LastRow As Long, i As Long
Dim AdditionString As String, CellString As String, CompleteString As String
AdditionString = "'-"
With ThisWorkbook.Worksheets("Sheet1")
LastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
For i = 1 To LastRow
CellString = .Range("A" & i).Value
CompleteString = AdditionString & CellString
.Range("A" & i).Value = CompleteString
Next i
End With
End Sub

Excel Macro - Fetching the values of one column based on the values from other column

I need a macro to write the row values present in column A if there is a value present in column B .
For example :
Column A Column B
Arjun
Arun 12
For the above example, I need a macro which can write "Arun 12" in Sheet2 of the work book with the Headers "Name" and "Hours".Before this the macro should clear the data present in Sheet two completely.
This will copy the all rows of columns A and B from Sheet1 to Sheet2 if B is not a Null string. And also will add the headers "Name" and "Hours".
Option Explicit 'requires that every variable has to be defined before use, e.g. with a Dim statement.
Sub DoStuff_GoodPractice()
Dim lastRowSrc As Long, lastRowDest As Long, i As Long 'declare row counts as Long so all rows can be used
Dim shtSource As Worksheet, shtDestination As Worksheet
Set shtSource = ThisWorkbook.Worksheets("Sheet1") 'full qualified identification of the worksheets
Set shtDestination = ThisWorkbook.Sheets("Sheet2")
lastRowSrc = shtSource.Range("A" & shtSource.Rows.Count).End(xlUp).Row 'determine the last used row
'clear destination sheet and write headers:
shtDestination.Cells.Clear
shtDestination.Range("A1").Value = "Name"
shtDestination.Range("B1").Value = "Hours"
lastRowDest = 1 'start with row 1 as destination
For i = 1 To lastRowSrc 'loop through all used rows
If shtSource.Range("A" & i).Value <> vbNullString And _
shtSource.Range("B" & i).Value <> vbNullString Then 'check if cells are not a null string
shtSource.Range("A" & i & ":B" & i).Copy Destination:=shtDestination.Range("A" & lastRowDest + 1) 'copy current row
lastRowDest = lastRowDest + 1 'jump to the last used row in destination
End If
Next i
End Sub
This should accomplish what you're after.
Sub DoStuff()
Dim lastRow As integer, lastRowSheet2 As integer, i As Integer
Dim sheet1 As WorkSheet, sheet2 As Worksheet
Set sheet1 = Sheets("Sheet1")
Set sheet2 = Sheets("Sheet2")
lastRow = sheet1.Range("A" & Rows.Count).End(xlUp).Row
sheet2.Cells.Clear
For i = 1 To lastRow
If sheet1.Range("A" & i).Value <> "" And sheet1.Range("B" & i).Value <> "" then
lastRowSheet2 = sheet2.Range("A" & Rows.Count).End(xlUp).Row
sheet1.Range("A" & i & ":B" & i).Copy Destination:= sheet2.Range("A" & lastRowSheet2 + 1)
End If
Next i
End Sub

Subtotal formula with unknown amount of rows in a column when the range is based on a text in another column

I have a subtotal column that starts in cell I11 but I don't know the amount of rows as it will change each time. At the end of the column are multiply functions based on the subtotal.
I am trying to create a macro that will count the amount of rows starting at I11 and will go until the value in column F equals "Value of Work Done"
This is what I have so far:
Sub Total_Amount()
'New_Entry Macro
Dim PPC1 As Worksheet
Dim rowNo As Integer
rowNo = ActiveCell.Row
If PPC1.Range("F11:F").Value = "VALUE OF WORK DONE" Then
With Range("I" & Rows.Count).End(xlUp)
.Offset(1).Formula = "=SUM(I11:" & .Address(0, 0) & ")"
End With
End If
End Sub
The answer still comes up at the end of the column instead of beside "Value of Work Done"
Sub Total_Amount()
'New_Entry Macro
Dim ws As Excel.Worksheet
Dim lRow As Long
Dim lLast As Long
Set ws = Worksheets("PPC 1")
Row = 11
Do While lRow <= ws.UsedRange.Rows.Count
If ws.Range("F" & lRow).Value = "Value of Work Done" Then
lLast = lRow
Exit Do
End If
lRow = lRow + 1
Loop
ws.Range("I" & lRow + 1).Formula = Application.Sum("I11:I" & lLast)
End Sub
Dim ws as excel.worksheet
Dim lRow As Long
Dim lLast as Long
Set ws = Worksheets("PPC 1")
lRow = 11
Do While lRow <= ws.UsedRange.Rows.count
If ws.Range("F" & lRow).Value = "Value of Work Done" then
lLast = lRow
Exit do
End if
lRow = lRow + 1
Loop
Then write the formula to whatever column you want
ws.Range("I" & lLast).Formula = Application.Sum("I11:I" & lLast)

excel programatically merge all rows with equal values in column z

I'm running a dataimport macro, and I want to merge all rows in a dataset that have equal values in column x, and then I want to get a row that represents the average of group x[y] x being the column, and y being the value of the column x for that particular grouping.
Is there a simple function to do this, or must I create an extensive loop with a lot of spare cycles?
Explicitation:
So my dataset looks like
A | B | C
1 2 4
1 2 5
2 7 3
2 5 1
3 2 1
1 5 6
Now I want to merge rows by column A value, so all A's with equal value get the rest of their rows averaged so I would get somethin that looked like:
A | B | C
1 3 5
2 6 2
3 2 1
So far I've been trying to loop over the possible values of column A (1 to 10) manually by this function, but it keeps crashing excel, and I can't figure out why, I must have an endless loop somewhere in this function:
Function MergeRows(sheet, column, value)
Dim LastRow
Dim LastCol
Dim numRowsMerged
Dim totalValue
numRowsMerged = 1
LastRow = sheet.UsedRange.Rows.Count
LastCol = sheet.UsedRange.Columns.Count
With Application.WorksheetFunction
For iRow = LastRow - 1 To 1 Step -1
'enter loop if the cell value matches what we're trying to merge
Do While Cells(iRow, column) = value
For iCol = 1 To LastCol
'skip the column that we're going to use as merging value, and skip the column if it contains 3 (ikke relevant)
If Not (iCol = column) And Not (Cells(iRow, iCol) = 3) Then
Cells(iRow, iCol) = (Cells(iRow, iCol) * numRowsMerged + Cells(iRow + 1, iCol)) / (numRowsMerged + 1)
End If
Next iCol
'delete the row merged
Rows(iRow + 1).Delete
Loop
'add one to the total number of rows merged
numRowsMerged = numRowsMerged + 1
Next iRow
End With
End Function
solution
I ended up creating a range that I would gradually extend using Union, like this:
Function GetRowRange(sheet, column, value) As range
Dim LastRow
Dim LastCol
Dim numRowsMerged
Dim totalValue
Dim rowRng As range
Dim tempRng As range
Dim sheetRange As range
numRowsMerged = 1
Set sheetRange = sheet.UsedRange
LastRow = sheet.UsedRange.Rows.Count
LastCol = sheet.UsedRange.Columns.Count
With Application.WorksheetFunction
For iRow = 1 To LastRow Step 1
'enter loop if the cell value matches what we're trying to merge
If (sheetRange.Cells(iRow, column) = value) Then
Set tempRng = range(sheetRange.Cells(iRow, 1), sheetRange.Cells(iRow, LastCol))
If (rowRng Is Nothing) Then
Set rowRng = tempRng
Else
Set rowRng = Union(rowRng, tempRng)
End If
End If
'add one to the total number of rows merged
numRowsMerged = numRowsMerged + 1
Next iRow
End With
Set GetRowRange = rowRng
End Function
Is this what you are trying? Since you wanted VBA code, I have not used Pivots but used a simpler option; formulas to calculate your average.
Option Explicit
Sub Sample()
Dim col As New Collection
Dim wsI As Worksheet, wsO As Worksheet
Dim wsIlRow As Long, wsOlRow As Long, r As Long, i As Long
Dim itm
'~~> Chnage this to the relevant sheets
Set wsI = ThisWorkbook.Sheets("Sheet1")
Set wsO = ThisWorkbook.Sheets("Sheet2")
'~~> Work with the input sheet
With wsI
wsIlRow = .Range("A" & .Rows.Count).End(xlUp).Row
'~~> get unique values from Col A
For i = 1 To wsIlRow
On Error Resume Next
col.Add .Range("A" & i).Value, """" & .Range("A" & i).Value & """"
On Error GoTo 0
Next i
End With
r = 1
'~~> Write unique values to Col A
With wsO
For Each itm In col
.Cells(r, 1).Value = itm
r = r + 1
Next
wsOlRow = .Range("A" & .Rows.Count).End(xlUp).Row
'~~> Use a simple formula to find the average
For i = 1 To wsOlRow
.Range("B" & i).Value = Application.Evaluate("=AVERAGE(IF(" & wsI.Name & _
"!A1:A" & wsIlRow & "=" & .Range("A" & i).Value & _
"," & wsI.Name & "!B1:B" & wsIlRow & "))")
.Range("C" & i).Value = Application.Evaluate("=AVERAGE(IF(" & wsI.Name & _
"!A1:A" & wsIlRow & "=" & .Range("A" & i).Value & _
"," & wsI.Name & "!C1:C" & wsIlRow & "))")
Next i
End With
End Sub
SCREENSHOT
This is easy to do with a pivot table.
Here's a picture of the end result.

Resources