VBA, For Each Application Match, object required - excel

Having an issue looping through application match.
I get an error Object required on line K.Offset(0, 1).Copy FV.Offset(2, 0)
The code should
1) loop through CS range,
2) where CS matches in range FV,
3) input the cells from CS Offset(0,1) into FV 2 columns over, Offset(2,0).
Here is my full code:
Sub n()
Dim FV As Variant
Dim CS As Variant
Dim K As Variant
FV = Sheets("NEW").Range("A28:A34").Value
CS = Sheets("CS").Range("A1:L1").Value
For Each K In CS
If Not IsError(Application.Match(CS, FV, 0)) Then
K.Offset(0, 1).Copy FV.Offset(2, 0)
Else:
End If
Next K
End Sub

You could use a pure VBA function like:
Sub CopyMatchingValues()
Dim FV As Range
Dim CS As Range
Dim cellFV As Range
Dim cellCS As Range
Set FV = Sheets("NEW").Range("A28:A34")
Set CS = Sheets("CS").Range("A1:L1")
For Each cellCS In CS.Cells
For Each cellFV In FV.Cells
If cellFV.Value = cellCS.Value Then
cellFV.Offset(2, 0).Value = cellCS.Offset(0, 1).Value
End If
Next
Next
End Sub

What a Match feat. the FirstMatch Issue
Option Explicit
Sub XMatch()
Const FirstMatch As Boolean = True
Dim FV As Variant ' Search Array (Vertical)
Dim CS As Variant ' Source Array (Horizontal)
Dim K As Variant ' Target Array (Vertical)
Dim iFV As Integer ' Search Array Rows Counter
Dim iCS As Integer ' Source Array Columns Counter
' Paste ranges into arrays.
FV = Sheets("NEW").Range("A28:A34").Value ' Search Array = Search Range
CS = Sheets("CS").Range("A1:L2").Value ' Source Array = Source Range
' The Target Array is the same size as the Search Array.
ReDim K(1 To UBound(FV), 1 To 1)
' ReDim K(LBound(FV, 1) To UBound(FV, 1), LBound(FV, 2) To UBound(FV, 2))
' Loop through first and only COLUMN of first dimension of Search Array.
For iFV = 1 To UBound(FV)
' For iFV = LBound(FV, 1) To UBound(FV, 1)
' Loop through first ROW of second dimension of Source Array.
For iCS = 1 To UBound(CS, 2)
' For iCS = LBound(CS, 2) To UBound(CS, 2)
If FV(iFV, 1) = CS(1, iCS) Then
' Match is found, read from second ROW of the second dimension of Source
' Array and write to first and only COLUMN of first dimension of Target
' Array.
K(iFV, 1) = CS(2, iCS)
' Check True/False
If FirstMatch Then
' When FirstMatch True, stop searching.
Exit For
' Else
' When FirstMatch False, try to find another match to use as result.
End If
' Else
' Match is not found.
End If
Next
Next
' Paste Target Array into Target Range, which is two columns to the right of
' Search Range.
Sheets("NEW").Range("A28:A34").Offset(0, 2) = K ' Target Range = Target Array
End Sub

Related

Assigning values of one dynamic array through a loop to another one with changes (VBA)

I'm new to the VBA programming language so I'm asking for some help.
I'm trying to automatize building a waterfall chart in Excel using VBA. Usually I did everything manually and it often took quite a while when data changed. So I decided to use VBA to fasten the process.
To create a waterfall chart, I need to create additional series of data. I'm trying to do it by using arrays and loops.
For one, I need to create an array which consists of absolute values of the initial array (range). But I run into an error "Subscript out of range" and can't figure out what the problem is. In Python, which I know better, I guess, there wouldn't be such a problem.
Here's my code:
Sub CreateWaterfall()
'*************************************************************************
Dim i As Integer
'*************************************************************************
' Turn a range into an array
Dim FigureArrayLength As Integer
FigureArrayLength = Range("B3", Range("B3").End(xlToRight)).Count
Dim FiguresArr() As Variant
ReDim FiguresArr(FigureArrayLength)
FiguresArr = Range("B3", Range("B3").End(xlToRight))
'*************************************************************************
' Build another array based on FiguresArr, but making all the values positive
Dim AuxiliaryFiguresArr() As Variant
ReDim AuxiliaryFiguresArr(FigureArrayLength)
For i = 1 To FigureArrayLength
AuxiliaryFiguresArr(i) = Abs(FiguresArr(i))
Next i
End Sub
What Excel doesn't like is this line, which gets highlighted in yellow when I press the 'Debug' button:
AuxiliaryFiguresArr(i) = Abs(FiguresArr(i))
What could the problem be?
Absolute Values of a Row to an Array
Sub ArrAbsRowTEST()
' Reference the worksheet ('ws').
Dim ws As Worksheet: Set ws = ActiveSheet ' improve!
' Reference the one-row range ('rrg') (a pretty risky way).
Dim rrg As Range: Set rrg = ws.Range("B3", ws.Range("B3").End(xlToRight))
' Using the 'ArrAbsRow' function (on the range),
' write the converted values to an array ('Arr').
Dim Arr() As Variant: Arr = ArrAbsRow(rrg)
' Continue, e.g.:
Debug.Print "The array contains the following numbers:"
Debug.Print Join(Arr, vbLf)
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the absolute values of the values from the first row
' of a range ('rrg') in a 1D one-based array.
' Remarks: It is assumed that the first row of the range
' contains numbers only.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function ArrAbsRow( _
ByVal rrg As Range) _
As Variant
' Write the values from the first row of the range
' to a 2D one-based one-row array ('rData').
Dim rData() As Variant
Dim cCount As Long
With rrg.Rows(1)
cCount = .Columns.Count
If cCount = 1 Then ' one cell
ReDim rData(1 To 1, 1 To 1): rData(1, 1) = .Value
Else ' multiple cells
rData = .Value
End If
End With
' Write the absolute values of the values from the 2D array
' to the resulting 1D one-based array ('Arr').
Dim Arr() As Variant: ReDim Arr(1 To cCount)
Dim c As Long
For c = 1 To cCount
Arr(c) = Abs(rData(1, c))
Next c
' Assign the 1D array to the result.
ArrAbsRow = Arr
End Function
I tested the below and returned to this page and then saw the solution from VBasic2008; so thought I'd add my answer too.
When I first did this, I assumed that the range derived array would be one dimensional too. I realised my mistake, when I added the array as a watch and was then able to see its dimensions.
Option Explicit
Private Sub CreateWaterfall()
'*************************************************************************
Dim i As Integer
Dim WS As Worksheet
Set WS = ThisWorkbook.Sheets("Sheet1")
'*************************************************************************
' Turn a range into an array
Dim FiguresArr As Variant
FiguresArr = WS.Range("B3", WS.Range("B3").End(xlToRight))
'*************************************************************************
' Build another array based on FiguresArr, but making all the values positive
ReDim AuxiliaryFiguresArr(0, 0) As Variant
AuxiliaryFiguresArr(0, 0) = 0
For i = 1 To UBound(FiguresArr, 2)
Call AddEntry(AuxiliaryFiguresArr, Abs(FiguresArr(1, i)))
Next i
End Sub
The procedure below is called by the code above
Public Sub AddEntry(aList As Variant, aEntry As Variant)
'
' build array for later copy onto sheet
'
Dim i%
Dim aEntry2 As Variant
If VarType(aEntry) = vbDouble Or VarType(aEntry) = vbInteger Then
aEntry2 = Array(aEntry)
Else
aEntry2 = aEntry
End If
If aList(0, 0) <> 0 Then
ReDim Preserve aList(0 To UBound(aEntry2), 0 To UBound(aList, 2) + 1)
End If
For i = 0 To UBound(aEntry2)
aList(i, UBound(aList, 2)) = aEntry2(i)
Next
End Sub

I am trying to find all numbers between a given range

I have to be able to give two inputs a beginning and an ending number in a range. I have to be able to extrapolate all values in between and including the beginning and ending numbers. I have gotten as far as making a loop that gets stuck :) I also have defined the Beginning number and ending number in some input boxes and successfully put the beginning number in A1.
The numbers in my case are always unknown a person will always be able to tell what the beginning and ending numbers are but they will always change.
Sub FindNum()
Dim iLowVal As Integer
Dim iHighVal As Integer
iLowVal = InputBox("Beginning Range")
iHighVal = InputBox("Ending Range")
Range("A1").Value = iLowVal
Do Until iLowVal = iHighVal
Range("A1" & i) = iLowVal + 1
Loop
End Sub
Start now to learn how to use variant arrays. It is quicker to use them and bulk assign to the sheet than to loop the sheet.
When using Arrays it is quicker to use For Loops.
Sub FindNum()
Dim iLowVal As Long
Dim iHighVal As Long
iLowVal = InputBox("Beginning Range")
iHighVal = InputBox("Ending Range")
Dim itNum As Long
itNum = iHighVal - iLowVal + 1
Dim arr As Variant
ReDim arr(1 To itNum)
Dim k As Long
k = iLowVal
Dim i As Long
For i = 1 To itNum
arr(i) = k
k = k + 1
Next i
ActiveSheet.Range("A1").Resize(itNum).Value = Application.Transpose(arr)
End Sub
Write an Array of Integers (For...Next)
This is just a basic code with a few more options. Play with it (i.e. modify "A1", "A", (1, 0)) to see its behavior so you can improve on it by googling, SOing, asking another question... etc.
Option Explicit
Sub FindNumForNext()
' Input data.
Dim nStart As Long: nStart = InputBox(Prompt:="Start Value", _
Title:="Write an Array of Integers", Default:=1)
Dim nEnd As Long: nEnd = InputBox("End Value")
' Check out 'Application.InputBox' as a better way to input data.
' Determine the order (asc or desc).
Dim nStep As Long
If nStart <= nEnd Then ' ascending
nStep = 1
Else ' descending
nStep = -1
End If
' Create a reference to the destination worksheet.
Dim dws As Worksheet: Set dws = ActiveSheet ' the one you're looking at
' Instead of the previous line, it is safer (better) to determine
' the exact worksheet where this will be used, e.g.:
'Dim dwb As Workbook: Set dwb = ThisWorkbook ' workbook containing this code
'Dim dws As Worksheet: Set dws = wb.Worksheets("Sheet1") ' tab name
' Create a reference to the first destination cell.
Dim dCell As Range: Set dCell = dws.Range("A1")
' Clear possible previous data.
dws.Columns("A").Clear
Dim n As Long ' Values (Numbers) Counter (For Next Control Variable)
' Loop from start to end...
For n = nStart To nEnd Step nStep
' Write the current number to the current destination cell.
dCell.Value = n
' Create a reference to the next destination cell.
Set dCell = dCell.Offset(1, 0)
' 1 means one cell down
' 0 means zero cells to the right
Next n ' next value (number)
End Sub
If you dispose of MS 365 you might use the new Sequence() function as WorksheetFunction avoiding loops completely (see section b)):
Syntax
=SEQUENCE(rows,[columns],[start],[step])
You calculate the needed number of rows by substracting the lower start value from the higher ending value plus +1. The columns no equals 1, the start value is userdefined, the step is +1 by default.
This results in a vertical 2-dim array which can be written to any target (see section c)).
Option Explicit
Sub WriteSequence()
'a) definitions
Dim lo As Long, hi As Long
lo = InputBox("Beginning Range")
hi = InputBox("Ending Range")
'b) get sequence as vertical 2-dim array
Dim seq
seq = WorksheetFunction.Sequence(hi - lo + 1, 1, lo)
'c) write to any target
Sheet1.Range("A1").Resize(hi - lo + 1, 1) = seq
End Sub

Is there a way to AND across a row of a 2D Array?

Using VBA, I would like to AND across each row in a 2D array and star the result in separate 1D array without ANDing a single pair the ANDing the result with the next item in that row.
FYI This is my first time using 2D arrays so sorry if there is an obvious solution.
For example if the data in my sheet looked like this (the actual range is much larger):
I would like to do the equlavant of an excel formula: =AND(B2:D2) then =AND(B3:D3), etc...
I have code that sets everything up but I don't know how to proceed except to loop across each element of a row, store the result then loop across the next, etc, etc. I'm hoping the there is a much better (more efficient) way to proceed.
Here is my code so far
Sub Exceptions()
' Setup worksheet
Dim wks As Worksheet
Set wks = cnTest
' Find last row of range
Dim LastRow As Long
LastRow = Find_LastRow(wks) 'Functionthat returns last row
' load range into array
Dim MyArray As Variant
MyArray = wks.Range("B2:D8")
' Setup 1D Result array
Dim Results As Variant
Results = wks.Range("A2:A8")
Dim i As Long
For i = 1 To LastRow
' Perform AND function on each row of the array
' then place result in 1D array (Results())
' If this were a formul: =AND(B2:D2)
'
' Is there way to "AND" across a row in and array or
' must I "AND" MyArray(1,1) with MyArray(1,2) then AND
' that result with MyArray(1,3)
Next i
End Sub
Thank you
Try this.
Sub Exceptions()
' Setup worksheet
' load range into array
Dim MyArray As Variant
MyArray = ActiveSheet.Range("B2:D8")
' Setup 1D Result array
Dim Results As Variant
Results = ActiveSheet.Range("A2:A8")
Dim i As Long
Dim X As Long
For i = 1 To UBound(MyArray, 1)
Results(i, 1) = "True"
For X = 1 To UBound(MyArray, 2)
If MyArray(i, X) = False Then
Results(i, 1) = "False"
Exit For
End If
Next X
Next i
End Sub
Try,
Sub test()
Dim vR()
Dim rngDB As Range, rng As Range
Dim i As Long, r As Long
Set rngDB = Range("b2:b8")
r = rngDB.Rows.Count
ReDim vR(1 To r)
For Each rng In rngDB
i = i + 1
vR(i) = WorksheetFunction.And(rng.Resize(1, 3))
Next rng
Range("a2").Resize(r) = WorksheetFunction.Transpose(vR)
End Sub
In the formula bar, type:
=IF(-PRODUCT(IF(A1,-1,0),IF(C1,-1,0)),TRUE,FALSE)
(if the data is in columns A and C), and drag down.
Because, as everyone knows, A AND B = AB if A and B are Boolean variables (and watch the minus in front of the PRODUCT).

VBA return dynamic array and assign to variable

Return dynamic array from function VBA got me part of the way on this problem. I realized I should know size prior to invoking the function.
Function GetHeadersFromRange(DataRange As Range, Size As Integer) As Variant
Dim Column As Integer
Dim Headers As Variant
ReDim Headers(0 To Size)
For Column = 1 To DataRange.Columns.Count
Headers(Column) = DataRange(1, Column).Value
Next
GetHeadersFromRange = Headers
End Function
Sub TestGetHeadersFromRange()
Application.DisplayAlerts = False
Set wb = ThisWorkbook
Set TestSheet = wb.Sheets.Add()
TestSheet.Range("A1").Value = "my_header"
TestSheet.Range("A2").Value = "val"
Dim DataRange As Range: Set DataRange = TestSheet.Range("A1:A2")
Dim Size As Integer: Size = DataRange.Columns.Count
Dim Result As Variant
' Gets type mismatch
Set Result = GetHeadersFromRange(DataRange, Size)
End Sub
Not entirely sure what to do here. I need to use this function in multiple places which is why it is a function in the first place.
Edit: Clarify problem
Set Result = GetHeadersFromRange(...) gets a type mismatch.
Header Function
Improvement
Your error occurs because you are using Set (used for objects)
on an array.
A more efficient (faster) way than looping through a range is looping
through an array.
When you copy a range to a variant (possibly array), if the range
contains one cell, the variant will contain one value only. But if
the range contains multiple cells, it will be an array, whose size is
returned with UBound. Therefore there is no need for a Size argument.
IsArray is used to determine if a variant is an array. In our case we can check if the number of columns (elements) is greater than 1 instead.
Option Explicit
Function GetHeadersFromRange(DataRange As Range) As Variant
Dim vntR As Variant ' Range Variant
Dim vntH As Variant ' Header Array
Dim Noe As Long ' Number of Elements
Dim j As Long ' Range Array Column Counter,
' Header Array Element Counter
With DataRange
' Calculate Number of Elements.
Noe = .Columns.Count
' Calculate Header Range.
' Copy Header Range to Range Variant.
vntR = .Resize(1, Noe)
' Note: Range Variant (vntR) is a 2D 1-based 1-row array only if
' DataRange contains more than one column. Otherwise it is
' a variant containing one value.
End With
'' Check if Range Variant is an array.
'If IsArray(vntR) Then
' Check if Number of Elements is greater than 1.
If Noe > 1 Then
' Resize 1D 0-based Header Array to number of columns (2) in Range
' Array minus 1 (0-based).
ReDim vntH(Noe - 1)
' Loop through columns of Range Array.
For j = 1 To Noe
' Write value at first row (1) and current column (j) of Range
' Array to current element (j-1) of Header Array.
vntH(j - 1) = vntR(1, j)
Next
Else
' Resize 1D 0-based Header Array to one element only (0).
ReDim vntH(0)
' Write Range Variant value to only element of Header Array.
vntH(0) = vntR
End If
GetHeadersFromRange = vntH
End Function
Sub TestGetHeadersFromRange()
Dim TestSheet As Worksheet ' Source Worksheet
Dim DataRange As Range ' Data Range
Dim Result As Variant ' Result Variant (possibly Array)
Dim i As Long ' Result Array Element Counter
' Add a new worksheet (Source Worksheet).
' Create a reference to the newly added Source Worksheet.
Set TestSheet = ThisWorkbook.Sheets.Add()
' In Source Worksheet
With TestSheet
' Add some values.
.Range("A1").Value = "my_header"
.Range("A2").Value = "val"
.Range("B1").Value = "my_header2"
.Range("B2").Value = "val2"
End With
' Test 1:
Debug.Print "Test1:"
' Create a reference to DataRange.
Set DataRange = TestSheet.Range("A1:A2")
' Write Data Range to 1D 0-based Result Array.
Result = GetHeadersFromRange(DataRange)
' Loop through elements of Result Array.
For i = 0 To UBound(Result)
' Write current element of Result Array to Immediate window.
Debug.Print Result(i)
Next
' Test 2:
Debug.Print "Test2:"
' Create a reference to DataRange.
Set DataRange = TestSheet.Range("A1:B2")
' Write Data Range to 1D 0-based Result Variant.
Result = GetHeadersFromRange(DataRange)
' Loop through elements of Result Array.
For i = 0 To UBound(Result)
' Write current element of Result Array to Immediate window.
Debug.Print Result(i)
Next
End Sub

Excel: Click cell to open the data to another sheet

Here's my draft data
Sheet_name: "FIRST"
The data represents the Pass and fail of X & Y fields. If the Area fails when it comes to X it will be mark as F and if it pass, the field X will mark as P. Same procedure to Field Y
And
Sheet_name: "SECOND"
Here's the summary of the Sheet: "FIRST"
It calculates the counts of passes and fails.
Using the idea of Countif Function.
=COUNTIF(FIRST!B2:B5,"P")
=COUNTIF(FIRST!C2:C5,"F")
What I'm trying to do is,
When you try to click the counts of passes and fails. It will redirect you to new sheet where the sheet gives the data who are the areas passed and failed.
Example:
If I click the "3" under the field of Passed
It will give me something like this,
| X |
Area1 | p |
Area2 | p |
Area4 | p |
Sorry, this one is not my project, homework, or exam.
I just need to understand the logic of opening the data when you click a cell.
Cell-Click to Another Sheet
Copy the code into the Sheets("SECOND") sheet code (in VBA
double-click on "SECOND") and rename a sheet as "THIRD".
In sheet THIRD there will be 2 columns with headers AREA and X. The
headers are excluded from ClearContents.
Below the results will be either for Pass or Fail depending on which
cell was 'clicked' (selected) at the moment.
The Code
Option Explicit
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Const cStrPass As String = "A3" ' Pass Cell Range
Const cStrFail As String = "B3" ' Fail Cell Range
If Target = Range(cStrPass) Then
CellClick Range("A3")
End If
If Target = Range(cStrFail) Then
CellClick Range("B3")
End If
End Sub
Sub CellClick(CellRange As Range)
Const cVntName1 As Variant = "FIRST"
Const cVntName3 As Variant = "THIRD"
Dim vntSrc As Variant ' Source Array
Dim vntTgt As Variant ' Target Array
Dim lngLastRow As Long ' Source Last Row
Dim i As Long ' Source Row Counter
Dim k As Long ' Target Row Counter
Dim j As Integer ' Source/Target Column Counter
Dim strPF As String ' PassFail String
' Paste Source Range into Source Array.
With Worksheets(cVntName1)
lngLastRow = .Cells(.Rows.Count, "B").End(xlUp).Row
vntSrc = .Range("A2", .Cells(lngLastRow, "B"))
End With
' Determine PassFail String.
If CellRange.Column = 1 Then
strPF = "P"
Else
strPF = "F"
End If
' Count rows for Target Array
For i = 1 To UBound(vntSrc)
If vntSrc(i, 2) = strPF Then
k = k + 1
End If
Next
' Write data to Target Array
ReDim vntTgt(1 To k, 1 To 2)
k = 0
For i = 1 To UBound(vntSrc)
If vntSrc(i, 2) = strPF Then
k = k + 1
For j = 1 To UBound(vntSrc, 2)
vntTgt(k, j) = vntSrc(i, j)
Next
End If
Next
' Paste Target Array into Target Range.
With Worksheets(cVntName3)
.Range("A2", "B" & .Rows.Count).ClearContents
.Range("A2").Resize(UBound(vntTgt), UBound(vntTgt, 2)) = vntTgt
.Select
End With
End Sub

Resources