I have written a code in which I am trying to use two different formulas with a set of conditions like if we take RUZ currency into consideration. where we have tenors between (SW- 1Y), the formula should be =1/(1/R208C[-5]+RC12/10000) and for the rest of the tenors (2Y, 3Y,5Y) the formula should be =1*RC[-5]. this condition is only applicable on RUZ ccy, for the rest, one formula per ccy(currency) will be used for all their respective tenors.
the formula is placed in column P,
tenors are placed in column B
Sub Get_vpl()
' Define Constants.
Const wsName As String = "DS"
Const FirstRow As Long = 5
Const srcCol As String = "A"
Const tgtCol As String = "P"
Dim Criteria As Variant
Dim Formulas As Variant
Criteria = Array("RUB", "TRY", "TWD", "UAH", "UYU", "VND") ' add more...
Formulas = Array( "=1/(1/R208C[-5]+RC12/10000)", "=1*RC[-5]", "=1/(1/R232C[-5]+RC12/1)", "=1*RC[-5]", "=1*RC[-5]", "=1*RC[-5]") ' add more...
' Define the Source Column Range.
' Define workbook.
Dim wb As Workbook
Set wb = ThisWorkbook
' Define worksheet.
Dim ws As Worksheet
Set ws = wb.Worksheets(wsName)
' Calculate Last Non-Empty Row.
Dim LastRow As Long
LastRow = ws.Cells(ws.Rows.Count, srcCol).End(xlUp).Row
' Define Source Column Range.
Dim rng As Range
Set rng = ws.Range(ws.Cells(FirstRow, srcCol), ws.Cells(LastRow, srcCol))
' Prepare to write to Target Column Range.
' Calculate Column Offset.
Dim ColOffset As Long
ColOffset = ws.Columns(tgtCol).Column - ws.Columns(srcCol).Column
' Declare variables.
Dim CurPos As Variant ' Current Position
Dim cel As Range ' Current Cell Range
' Write formulas to Target Column Range.
Application.ScreenUpdating = False
' Iterate the cell ranges in Source Range.
For Each cel In rng.Cells
' Check if Current Cell Range in Source Column Range is not empty.
If Not IsEmpty(cel) Then
' Try to find the value in Current Cell Range in Criteria Array
' and write the position to Current Position
CurPos = Application.Match(cel, Criteria, 0)
' Check if value in Current Cell Range has been found
' in Criteria Array.
If Not IsError(CurPos) Then
' Write formula from Formulas Array to current Target Cell
' Range, using Current Position in Criteria Array.
cel.Offset(, ColOffset).Formula = _
Application.Index(Formulas, CurPos)
End If
End If
Next cel
Application.ScreenUpdating = True
End Sub
I have done more than intended to your code because I had so much difficulty understanding what you need. However, I'm rather pleased with the result and hope you will be, too. Note that I never ran the code and it may, therefore, contain minor bugs or typos which I shall be happy to rectify if you point them out.
Option Explicit
Enum Nws ' worksheet navigation
NwsFirstRow = 5
NwsCcy = 1 ' Columns: A = Currency
NwsTenor ' B = Tenor
NwsTarget = 16 ' P = Target
End Enum
Sub Get_vpl()
' 116
' Define Constants.
Const wsName As String = "DS"
' Declare variables.
Dim Wb As Workbook
Dim Ws As Worksheet
Dim CcyIdx As Integer ' return value from CurrencyIndex()
Dim R As Long ' loop counter: rows
Set Wb = ThisWorkbook
Set Ws = Wb.Worksheets(wsName)
Application.ScreenUpdating = False
With Ws
' this syntax is easier because you need the row number R
For R = NwsFirstRow To .Cells(.Rows.Count, NwsCcy).End(xlUp).Row
CcyIdx = CurrencyIndex(.Cells(R, NwsCcy).Value)
If CcyIdx >= 0 Then
.Cells(R, NwsTarget).Formula = ChooseFormula(CcyIdx, .Cells(R, NwsTenor).Value)
End If
Next R
End With
Application.ScreenUpdating = True
End Sub
Private Function ChooseFormula(ByVal CcyIdx As Integer, _
ByVal Tenor As String) As String
' 116
' return the formula specified by Idx or Formula(0)
Dim Idx As Integer
Dim Formula(2) As String
' the advantage of the syntax you chose is that the array
' is dimensioned automatically.
' Here the advantage is clarity.
Formula(0) = "=1*RC[-5]"
Formula(1) = "=1/(1/R208C[-5]+RC12/10000)"
Formula(2) = "=1/(1/R232C[-5]+RC12/1)"
If CcyIdx = 0 Then
If InStr("1Y,2Y,3Y,5Y", Tenor) Then Idx = 1
End If
ChooseFormula = Formula(Idx)
End Function
Private Function CurrencyIndex(ByVal Currcy As String) As Integer
' 116
' return -1 if not found or blank
Dim Ccy() As String ' list of currencies
Dim i As Integer
' I added "RUZ" in position 0 (change to suit and match in ChooseFormula())
' this syntax uses less space but doesn't support MATCH()
Ccy = Split("RUZ RUB TRY TWD UAH UYU VND") ' add more...
If Len(Trim(Currcy)) Then
For i = UBound(Ccy) To 0 Step -1
If StrComp(Currcy, Ccy(i), vbTextCompare) = 0 Then Exit For
Next i
Else
i = -1
End If
CurrencyIndex = i
End Function
I found your Criteria rather useless in this context. Perhaps that's why I gave it a task. The function CurrencyIndex() returns the index number of the current currency and uses this number thereafter in place of the actual currency code. For this purpose I added "RUZ" to your array. I have it in first position but any other number will do as well.
Please look at the function ChooseFormula(). It seems you have only 3 formulas. I assigned the index 0 to the most common one and made that the default. For the rest of it, the CcyIdx is passed to the function as an argument and if that index = 0 it identifies "RUZ" and gives it special treatment. I'm not sure that the treatment I assigned is 100% correct or workable but I think the code is simple and you should be able to modify it as required. Observe that the function won't ever return Formula(2) in its present state but you can modify it easily to accommodate all kinds of conditions and many more possible formulas. Let me know if you need any help with that.
Related
I filtered out some of my data using the Autofilter function. As a result, the filtered data consists of a non-contiguous range of cells.
Consequently, for example, when I use the CountIfs function to count the number of 03-In Analysis from Column C that belong to 07-customer noticed from column A, the CountIfs function counts the unfiltered data.
Filtered Data
When I use SpecialCells(xlCellTypeVisible), I get an error due to the non-contiguous range of cells.
Dim sh, ws As Worksheet
Dim count
Dim range1, range2 As Range
Set range1 = ws.Range("A2:A297")
Set range2 = ws.Range("C2:C297")
count = WorksheetFunction.CountIfs(range1, "07-customer noticed", range2, "03-In Analysis")
sh.Range("A1") = count
Arrays work faster for me than worksheet functions.
I tried and tested the code below and it works for me.
Option Explicit
Private Sub Test()
Dim sRange$
Dim count&, iLastUsedRow&, iRow&
Dim aData As Variant
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("B")
With ws
'last used row of data on this sheet
iLastUsedRow = .Range("A" & Rows.count - 1).End(xlUp).Row
'cells containing data
sRange = "A2:C" & iLastUsedRow
'transferring data to array
aData = .Range(sRange)
End With
For iRow = 1 To UBound(aData)
If Range_IsVisibleInWindow(ws.Range("A" & iRow + 1)) Then
If aData(iRow, 1) = "07-customer noticed" And aData(iRow, 3) = "03-In Analysis" Then
count = count + 1
End If
End If
Next
End Sub
I copied this function from here and upvoted their answer. You may want to thank them too in this way, if this works for you?
Function Range_IsVisibleInWindow(ByVal target As Excel.Range) As Boolean
' Returns TRUE if any cell in TARGET (Range) is visible in the Excel window.
'
' Visible means (1) not hidden, (2) does not have row height or column width of
' zero, (3) the view is scrolled so that the Range can be seen by the user at
' that moment.
'
' A partially visible cell will also return TRUE.
If target Is Nothing Then
' Parameter is invalid. Raise error.
Err.Raise 3672, _
"Range_IsVisibleInWindow()", _
"Invalid parameter in procedure 'Range_IsVisible'."
Else
' Parameter is valid. Check if the Range is visible.
Dim visibleWinLarge As Excel.Range
Dim visibleWinActual As Excel.Range
On Error Resume Next
Set visibleWinLarge = Excel.ActiveWindow.VisibleRange ' active window range -INCLUDING- areas with zero column width/height
Set visibleWinActual = visibleWinLarge.SpecialCells(xlCellTypeVisible) ' active window range -EXCLUDING- areas with zero column width/height
Range_IsVisibleInWindow = Not Intersect(target, visibleWinActual) Is Nothing ' returns TRUE if at least one cell in TARGET is currently visible on screen
On Error GoTo 0
End If
End Function
I am trying to search down a column of an excel sheet for identical text which is an argument of the function.
Function getRow(callerID As String) As Integer
Dim CalcRow As Integer
Dim CurrRow As Integer
Dim CurrCol As Integer
Dim SearchSheet As Worksheet
'Define variables
Set SearchSheet = ThisWorkbook.Worksheets("Calculations")
Set CellSearch = SearchSheet.Cells(CurrRow,CurrCol)
CalcRow = 2
CurrRow = 2
CurrCol = 16
Do Until CellSearch.Value = ""
If callerID = CellSearch.Value Then
Exit Do
Else
CurrRow = CurrRow + 1
CalcRow = CalcRow + 1
End If
Loop
'set return value
getRow = CalcRow
End Function
It keeps saying this is an error: Set CellSearch = SearchSheet.Cells(CurrRow,CurrCol) when I try to refer to it as a range.
I've tried referring to the range in other ways --I just want to increment the row by 1 until each cell in that column with a value is searched.
I'm very new to VBA so I've had some trouble with referring to cells without using ActiveCell. I don't want to use ActiveCell for this.
Get the Worksheet Row of the First Occurrence of a String in a Column
To allow to find other data types (Numbers, Dates, Booleans...) you only have to change
callerID As Variant
Note that Application.Match is case-insensitive i.e. MYSTRING = mystring. Also, it is handled differently than WorksheetFunction.Match i.e. its result can be tested with IsError or IsNumeric while the WorksheetFunction version will raise an error if no match is found.
Range.Resize Property
Keep in mind that the Range.Find method is unreliable if the worksheet is filtered.
The Code
Option Explicit
Function getRow(callerID As String) As Long
' Define the First Cell
With ThisWorkbook.Worksheets("Calculations").Range("P2")
' Calculate the Row Offset which is utilized with resize
' and when writing the result.
Dim RowOffset As Long: RowOffset = .Row - 1
' Declare a range variable.
Dim rg As Range
' Attempt to define the Last Non-Empty Cell.
Set rg = .Resize(.Worksheet.Rows.Count - RowOffset) _
.Find("*", , xlFormulas, , , xlPrevious)
' Validate the Last Non-Empty Cell.
If Not rg Is Nothing Then
' Define the Column Range, the range from the First Cell
' to the Last Non-Empty Cell in the worksheet column.
Set rg = .Resize(rg.Row - RowOffset)
' Attempt to find the Index (position) of the Caller ID
' in the Column Range.
Dim cIndex As Variant: cIndex = Application.Match(callerID, rg, 0)
' Validate the Index i.e. check if the Caller ID was found.
If IsNumeric(cIndex) Then
' Write the result. Note that the Index is the position
' in the Column Range, so to return the position (row)
' in the worksheet, the Row Offset has to be added.
getRow = cIndex + RowOffset
'Else ' Caller ID was not found (cIndex is an error value).
End If
'Else ' The range from the First Cell to the bottom-most cell
' of the worksheet column is empty.
End If
End With
End Function
I have two excels Book1.xlsm and Book2.xlsx. Book1 will have certain values like alpha, beta, gamma etc. (no repetition) in column A. And Book2 will have multiple occurrence of Book1 values like beta, beta, beta, alpha, alpha, gamma, gamma, gamma, gamma, gamma etc. The values in Book2 may not be alphabetically sorted but same values will be grouped together. Book2 values will be also in column A.
I have a macro designed in Book1.xlsm that should iterate over each value in Book1 column A and find the first row id where same value is present in Book2 column A. This row id should be then copied in corresponding column B of Book1.
This is how my macro code looks like. When I run, it fails with Run Time error '1004': Application-defined or object-defined error
Option Explicit
Sub Get_Data()
Dim wb1 As Worksheet
Dim wb2 As Worksheet
Dim wb2row As Integer
Dim i As Integer
Dim j As Integer
Const A = "A"
Const B = "B"
Set wb1 = Workbooks("Book1.xlsm").Worksheets("Sheet1")
Set wb2 = Workbooks("Book2.xlsx").Worksheets("Sheet1")
'Both For loop start from row id 2.
For i = 2 To wb1.Range("A2", wb1.Range("A2").End(xlDown)).Rows.Count
For j = 2 To wb2.Range("A2", wb2.Range("A2").End(xlDown)).Rows.Count
wb2row = Application.WorksheetFunction.Match(wb1.Cells(i, A), Range(wb2.Cells(j, A)), 0)
wb1.Cells(i, B).Copy (wb2.Cells(j, A))
Exit For ' j loop
Next j
Next i
End Sub
You can make excel do the work for you. Try this (tested)
Sub Get_Data()
With Workbooks("Book1.xlsm").Sheets("Sheet1")
With .Range(.Range("B2"), .Range("A" & Rows.Count).End(xlUp).Offset(0, 1))
.Formula2 = "=IFERROR(MATCH(A2,[Book2.xlsx]Sheet1!$A:$A,0),"""")"
.Value2 = .Value2
End With
End With
End Sub
Match Criteria, Return Row
Option Explicit
Sub Get_Data()
' Source
Const srcFirst As Long = 2
Const srcCol As String = "A"
' Destination
Const dstFirst As Long = 2
Const dstCol As String = "A"
Const resCol As String = "B"
' Source
Dim src As Worksheet
Set src = Workbooks("Book2.xlsx").Worksheets("Sheet1")
Dim rng As Range
Set rng = src.Range(src.Cells(srcFirst, srcCol), _
src.Cells(src.Rows.Count, srcCol).End(xlUp))
Dim RowOffset As Long
RowOffset = srcFirst - 1
' Destination
' 'ThisWorkbook' - the workbook containing this code.
Dim dst As Worksheet
Set dst = ThisWorkbook.Worksheets("Sheet1")
Dim srcRow As Variant ' It could be an error value, hence 'Variant'.
Dim i As Long
For i = 2 To dst.Cells(dst.Rows.Count, dstCol).End(xlUp).Row
srcRow = Application.Match(dst.Cells(i, dstCol), rng, 0)
If Not IsError(srcRow) Then
' This will write the row.
' If you need index, then remove 'RowOffset'.
dst.Cells(i, resCol).Value = srcRow + RowOffset
Else
' no match found, e.g.:
'dst.Cells(i, resCol).Value = ""
End If
Next i
End Sub
The second parameter of match function must be a range not a single cell.
I am trying to copy data from one worksheet to another based on the column-name. In the source worksheet, the data starts at A1. In the destination worksheet, the data should be pasted at row A11 and not A1. If I used EntireColumn.Copy I get an error about the source and destination copy area not being the same. I came across the UsedRange property but I am unbale to apply it to my scenario
For Each columnName In allColumns
'call a function to get the column to copy
If columnToCopy > 0 Then
columnName.Offset(1, 0).EntireColumn.Copy Destination:=ws2.Cells(11, columnToCopy)
End If
Next
In the above snippet, In dont want to use 'EntireColumn'. I only want the columns that have data. The variable columnName is for example 'Person ID'
What is the best way to do this?
Thanks.
This would be a typical approach:
For Each ColumnName In allColumns
If columnToCopy > 0 Then
With ColumnName.Parent
.Range(ColumnName.Offset(1, 0), .Cells(.Rows.Count, ColumnName.Column).End(xlUp)).Copy _
Destination:=ws2.Cells(11, columnToCopy)
End With
End If
Next
Assumes allColumns is a collection of single-cell ranges/column headers.
Copy/Paste Column
There is not enough information to give an accurate answer so here is a scenario you might consider studying.
The Code
Option Explicit
Sub TESTdetermineColumnNumber()
' Define constants. Should be more.
' Define Criteria.
Const Criteria As String = "Total"
' Define Header Row.
Const hRow As Long = 1
' Define Copy Range (Column Range)
' Define Source Worksheet.
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Sheet1")
' Define Header Row Range.
Dim RowRange As Range
Set RowRange = ws.Rows(hRow)
' Determine Column Number.
Dim ColumnNumber As Long
ColumnNumber = determineColumnNumber(RowRange, Criteria)
' Validate Column Number.
If ColumnNumber = 0 Then
Exit Sub
End If
' Determine Last Row.
Dim LastRow As Long
LastRow = ws.Cells(ws.Rows.Count, ColumnNumber).End(xlUp).Row
' Define First Data Row Number.
Dim FirstRow As Long
FirstRow = hRow + 1
' Define Column Range.
Dim ColumnRange As Range
Set ColumnRange = ws.Cells(FirstRow, ColumnNumber) _
.Resize(LastRow - FirstRow + 1)
' Define Paste Range.
' Define Destination Worksheet.
Dim ws2 As Worksheet
Set ws2 = ThisWorkbook.Worksheets("Sheet2")
' Define Destination Column.
Dim columnToCopy As Long
columnToCopy = 2
' Define Paste Range.
Dim PasteRange As Range
Set PasteRange = ws2.Cells(11, columnToCopy)
' Copy/Paste.
' Copy values, formulas and formats.
ColumnRange.Copy Destination:=PasteRange
' It is more efficient if you need only values to use the following:
PasteRange.Resize(ColumnRange.Rows.Count).Value = ColumnRange.Value
End Sub
Function determineColumnNumber(RowRange As Range, _
Criteria As String) _
As Long
Dim Temp As Variant
Temp = Application.Match(Criteria, RowRange, 0)
If Not IsError(Temp) Then
determineColumnNumber = Temp
End If
End Function
Dear stack overflow community:
To be brief, the goal of this program is to allow user to input text in Cell C53 and for the program to find matching text in a string in contained in each row within Column A, then return the text in column B on the same row if found (otherwise, return "Use your best judgement".)
I've successfully created the VBA code to find a matching text in a specific row in Column A and return the value in the same row in column B. However, it only works on one row hardcoded into the code. I need to adjust it to loop through a range of rows in column A because there may be matching text in other rows.
My code currently looks like this:
Sub Test_2()
Dim SearchString, SearchText
SearchKey = Range("A1")
SearchNote = Range("C53")
If InStr(SearchNote, SearchKey) > 0 Then
Range("C59").Value = Range("B1").Value
Else
Range("C59").Value = "Please use your best judgement."
End If
End Sub
Hence, if A1 contains "limit", and I type into C53 "client wants to upgrade limit", it will return to C59 the text in B1 because it was found.
The only addition I have been trying to make is nesting what I currently have into a loop to check other rows in Column A. For example, if A1 was "cheque" and A2 was "limit", my current code would only check A1 and not find a match resulting in the prompt "Please use your best judgement." It should be able to check A1, A2, A3 ... A50 ...
I've been having difficulties translating this to code in VBA, and was hoping for some assistance.
Find Word in Sentence
The 1st code goes into a standard module e.g. Module1. Only run the 1st procedure which is calling the 2nd procedure when needed.
Adjust the constants as you see fit. If this is used in one worksheet only then you have to change srcName and tgtName to the same string.
To automate this, copy the second short code to the sheet module (e.g. Sheet1) worksheet where the Answer and Question Cells are. Then you run nothing, it's automatic.
Standard Module e.g. Module1
Option Explicit
Public Const queCell As String = "C53" ' Question Cell
Sub writeAnswer()
' Data
Const srcName As String = "Sheet1" ' Source Worksheet Name
Const srcFirstRow As Long = 1 ' Source First Row Number
Const srcLastRowCol As String = "A" ' Source Last Row Column ID
Dim Cols As Variant: Cols = Array("A", "B") ' Source Column IDs
' Target
Const tgtName As String = "Sheet1" ' Target Worksheet Name
Const ansCell As String = "C59" ' Answer Cell
' Other
Const msg As String = "Please use your best judgement." ' Not Found Message
Dim wb As Workbook: Set wb = ThisWorkbook ' The workbook with this code.
' Define column range.
Dim src As Worksheet: Set src = wb.Worksheets(srcName)
Dim rng As Range
Set rng = src.Columns(srcLastRowCol).Find("*", , xlValues, , , xlPrevious)
If rng Is Nothing Then Exit Sub
If rng.Row < srcFirstRow Then Exit Sub
Set rng = src.Range(src.Cells(srcFirstRow, srcLastRowCol), rng)
' Write values from column range to jagged array (Data(0) & Data(1)).
Dim ubc As Long: ubc = UBound(Cols)
Dim Data As Variant: ReDim Data(ubc)
Dim j As Long
For j = 0 To ubc
getRange(Data(j), rng.Offset(, src.Columns(Cols(j)).Column _
- src.Columns(srcLastRowCol).Column))
If IsEmpty(Data) Then Exit Sub
Next
' Search Data(0) Array for string contained in Question Cell
' and write result from Data(1) Array to Answer Cell.
Dim tgt As Worksheet: Set tgt = wb.Worksheets(tgtName)
Dim Sentence As String: Sentence = tgt.Range(queCell).Value
Dim i As Long
For i = 1 To UBound(Data(0))
If Sentence = "" Then Exit For
If Trim(Data(0)(i, 1)) <> "" Then
If InStr(1, Sentence, Trim(Data(0)(i, 1)), vbTextCompare) > 0 Then
tgt.Range(ansCell).Value = Data(1)(i, 1)
Exit Sub
End If
End If
Next i
' If string not found, write Not Found Message to Answer Cell.
tgt.Range(ansCell).Value = msg
End Sub
' Writes the values of a range to a 2D one-based array.
Sub getRange(ByRef Data As Variant, DataRange As Range)
Data = Empty
If DataRange Is Nothing Then Exit Sub
If DataRange.Rows.Count > 1 Or DataRange.Columns.Count > 1 Then
Data = DataRange.Value
Else
ReDim Data(1 To 1, 1 To 1): Data(1, 1) = DataRange.Value
End If
End Sub
Sheet Module e.g. Sheet1
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Me.Range(queCell), Target) Is Nothing Then
writeAnswer
End If
End Sub