EXCEL VBA - Similar function to excel formula Value - excel

I'm new to VBA so I got a problem trying to convert some string number to a value number for a IF validation.
The problem is. I have Column "A" filled with something like this:
E/B: Houses 01
E/B: Houses 02
E/B: Building/New Villa
E/B: Building/Bella Casa
E/B: Houses 03
So, in my code, I want it to fill the "B" Column with the last 2 numbers if there's numbers or the name if there isn't.
Set Rng = Range("A8:A" & Range("I" & Rows.Count).End(xlUp).Row)
For Each celula In Rng.SpecialCells(xlCellTypeVisible)
Select Case True
Case IsNumeric(Right(celula, 2)) = True
celula.Offset(0, 1).Value = Right((celula), 3)
Case Else
celula.Offset(0, 1).Value = Mid(celula, InStr(4, celula, "/") + 1, Len(celula))
End Select
Next celula
But the Case IsNumeric(Right(celula, 2)) = True is never True. It do not capture the 2 last numbers. But it works alright with the names and returns "New Villa" and "Bella Casa". And I think the problem is that Right(celula, 2) is never a number, even if theres a number.
I want to know if theres a way to convert "Right(celula, 2)" in a Value, just like the =Value formula in excel, to do the validation.
OBS: Before trying this way, I had a excel formula applied to those cells and it worked, but I want to try this way for learning purposes.
Thanks

I think the for each loop itself works as expected.
However, this
Set Rng = Range("A8:A" & Range("I" & Rows.Count).End(xlUp).Row)
is probably where the problem lies. You have to make sure you've got the correct Range. You can verify by debugging: Debug.print Rng.Address.

Sub t()
Dim rng As Range
With ActiveSheet
Set rng = .Range("a1:a" & .Range("a" & .Rows.Count).End(xlUp).Row)
For Each cell In rng.Cells
If Val(Right(cell.Value, 2)) = 0 Then
cell.Offset(0, 1) = Mid(cell.Value, InStrRev(cell.Value, "/") + 1, Len(cell))
Else
cell.Offset(0, 1) = Val(Right(cell.Value, 2))
End If
Next cell
End With
End Sub

Related

Compare all column values against individual cells in Excel

I'd like to find if any row in Column C matches any cells in Column A or B and print out 'yes' or 'no' in an adjacent cell if it does match. The match might not be exact, because an ID may be written as '12401' but the match in the column may be like 'cf[12401]', with the ID enclosed in brackets.
This is an example of what I might find in the table. The values in A and B columns originally came from another table but I'm trying to find all instances of where they might exist in the third column.
Excel Example:
If possible, I'd like to list the values themselves that matched in the column. But that part would be a nice extra while the other part is more important because there are around 6000 values in the middle column so it would take days by hand.
I've tried different things like this:
=IF(COUNTIF(C2,"*" & A6 & "*" ), "Yes", "No")
or
=IF(COUNTIF(C2,"*" & Length & "*" ), "Yes", "No")
these work for individual words or cells, but trying to check all the values in that column against the cell will return no. I've tried variations of SUMPRODUCT and others that I've found, but haven't been able to get something that works for multiple values.
Is there some function in Excel that will allow me to do this? Or maybe a way in VBA?
Here is some UDF you could use.
Dim MyArr As Variant, X As Double, LR As Double
Option Explicit
Public Function MatchID(RNG As Range) As String
With ActiveWorkbook.Sheets(RNG.Parent.Name)
LR = .Cells(Rows.Count, 1).End(xlUp).Row
MyArr = Application.Transpose(.Range(.Cells(2, 1), .Cells(LR, 1)))
For X = LBound(MyArr) To UBound(MyArr)
If InStr(1, RNG.Value, MyArr(X), vbTextCompare) > 0 Then
If MatchID = "" Then
MatchID = MyArr(X)
Else
MatchID = MatchID & ", " & MyArr(X)
End If
End If
Next X
End With
End Function
Public Function MatchCFNAME(RNG As Range) As String
With ActiveWorkbook.Sheets(RNG.Parent.Name)
LR = .Cells(Rows.Count, 1).End(xlUp).Row
MyArr = Application.Transpose(.Range(.Cells(2, 2), .Cells(LR, 2)))
For X = LBound(MyArr) To UBound(MyArr)
If InStr(1, RNG.Value, MyArr(X), vbTextCompare) > 0 Then
If MatchCFNAME = "" Then
MatchCFNAME = MyArr(X)
Else
MatchCFNAME = MatchCFNAME & ", " & MyArr(X)
End If
End If
Next X
End With
End Function
In D2 Ijust used =IF(F2<>"","YES","") and dragged it sideways and down.

Using InStr or Left/Right to check variables within reverse loop

At work I have a repetitive task of going through a list of account activity and changes where I have to delete blank spaces and lines that are not necessary for the maintenance I perform. For 80% of the these I am able to work a for each loop that is pretty inelegant but effective.
Example:
For Each c In ActiveSheet.UsedRange
If InStr(1, c.Value, SubString7) = 1 Then ' find earn lines and remove
c.EntireRow.Offset(1).Delete
c.EntireRow.Clear
c.EntireRow.Offset(-1).Delete
End If
Next
The substring is the descriptive title line for each type of transaction. The one I am having trouble with is variable, while the others are not. It can be 9 lines long or 6 lines long, and could also be positive or negative but each possibility comes with the same title line.
Based on everything I could find to try to figure it out, I need to use a loop, moving from bottom to top. I cannot get it to trigger with either InStr, nor left/right.
This is a cut down version of what I am trying now:
lr = Range("A" & Rows.Count).End(xlUp).Row
For rowcounter = lr To 0 Step -1
If VBA.Strings.Left(Cells(rowcounter).Value, 11) Like "Earn Manual" Then
If VBA.Strings.Left(Cells(rowcounter + 5).Value, 1) = "-" Then
If VBA.Strings.Left(Cells(rowcounter + 6).Value, 3) = "AVG" Then
Cells(rowcounter).EntireRow.Offset(5).Delete 'this, several more times with different offsets for the required lines
Else
Cells(rowcounter).EntireRow.Offset(5).Delete 'different ones, finalizing removals on the negative value items
End if
Else
If VBA.Strings.Left(Cells(rowcounter + 6).Value, 3) = "AVG" Then
Cells(rowcounter).EntireRow.Offset(5).Delete 'again, but with different offsets
Else 'There is one line for these that I have to split into two lines, not sure if this will even work as I cannot get it to trigger
Cells(rowcounter).EntireRow.Offset(8).Delete
Cells(rowcounter).EntireRow.Offset(7).Delete
Cells(rowcounter + 4).Value = VBA.Strings.Right(Cells(rowcounter + 3).Value, 25)
Cells(rowcounter + 3).Value = VBA.Strings.Left(Cells(rowcounter + 3).Value, 13)
End if
End If
End If
Next Rowcounter
I had originally had that first If line as:
If InStr(1, Cells(rowcounter).Value, SubString8) = 1 Then
I tried switching to Left() and Like but still no dice.
Attempting to provide sample of input/output
sample data:
Goal output from column A:
Retained Data
Update again, new and improved code that is still failing:
Next
For i = 1 To ActiveSheet.Range("A" & ActiveSheet.Rows.Count).End(xlUp).Row
If ws.Range("A" & i) Like "Earn Manual*" Then
If ws.c("A" & i + 5) Like "-*" Then
If ws.c("A" & i + 6) Like "Avg*" Then
Set Deleteme = c.Range("A" & i, "A" & i + 8) ' shows AVG, negative value
Else
Set Deleteme = c.Range("A" & i, "A" & i + 5) ' no AVG, negative value
End If
Else
If ws.c("A" & i + 6) Like "Avg*" Then
Set Deleteme = c.Range("A" & i, "A" & i + 3)
Set Deleteme = c.Range("A" & i + 5)
Else
Set Deleteme = c.Range("A" & i, "A" & i + 3)
Set Deleteme = c.Range("A" & i + 5)
End If
End If
Else
Set Deleteme = Union(Deleteme, ws.Range("A" & i))
End If
Next A
There is no way that I can get this 100% correct because it was based of the OP's new and improve code, which has some flaws in its logic. My goal was to simply the overall syntax to make it easier to get right.
The problem with deletion with offset values is that the values move on you. My solution is to Union all rows to be deleted and delete them then after the loop is done. This is not only more efficient but it allows us to loop from top to bottom. This makes the code it much easier to follow.
When Union ranges in this way, you must first test to see if the target range to be deleted is Nothing. If the target range is Nothing, we Set it to the new range else we Union the two ranges. I wrote a subroutine UnionRange(), so that we would not have to repeat this process each time we needed to do a Union.
With blocks, Range.Offset() and Range.Resize() were used to simply the syntax. I feel like this is cleaner than concatenating addresses inside of a range (e.g. Range("A" & i + 5) and Range("A" & i, "A" & i + 8)).
Sub CleanUp()
With ThisWorkbook.Worksheets("Sheet1")
Dim r As Long
Dim rUnion As Range
For r = 1 To .Cells(.Rows.Count, 1).End(xlUp).Row
With .Cells(r, 1)
If .Value = "" Then
UnionRange rUnion, .Offset(0)
ElseIf .Value Like "Earn Manual*" Then
If .Offset(6).Value Like "Avg*" Then ' shows AVG, negative value
UnionRange rUnion, .Offset(8)
Else ' no AVG, negative value
UnionRange rUnion, .Offset(5)
End If
Else
'This can't be right
If .Offset(6).Value Like "Avg*" Then 'If Like "Avg*" Then Delete These Cells
UnionRange rUnion, .Resize(3)
UnionRange rUnion, .Offset(5)
Else 'Hell If Not Like "Avg*" Then Delete The Same Cells Anyway
UnionRange rUnion, .Resize(3)
UnionRange rUnion, .Offset(5)
End If
End If
End With
Next
End With
If Not rUnion Is Nothing Then
Application.ScreenUpdating = False
rUnion.EntireRow.Delete
End If
End Sub
Sub UnionRange(ByRef rUnion As Range, ByRef Cell As Range)
If rUnion Is Nothing Then
Set rUnion = Cell
Else
Set rUnion = Union(rUnion, Cell)
End If
End Sub

Adding freshly created formula into new module

I've just created a brand new macro. Took function down below from internet (all credits goes to trumpexcel.com), code down below
Function CONCATENATEMULTIPLE(Ref As Range, Separator As String) As String
Dim Cell As Range
Dim Result As String
For Each Cell In Ref
Result = Result & Cell.Value & Separator
Next Cell
CONCATENATEMULTIPLE = Left(Result, Len(Result) - 1)
End Function
Then I proceed to extract data from various columns and into the one (my table is 20 rows x 10 columns)
Sub conact_data()
Dim i As Integer
For i = 2 To Cells(Rows.Count, "A").End(xlUp).Row
Cells(i, "M").Value = Cells(i, "A").Value & " " & _
Cells(i, "B").Value & " / " & Cells(i, "D").Value & "; "
Next i
End Sub
Thanks to that I've got combined data from column A, B and D, so its 20 rows. All I want to do now is to concatenate data from M2:M21 using CONCATENATEMULTIPLE function therefore I try various approach (I want this huge line in P2 cell) like :
Cells(2, 16).Value = CONCATENATEMULTIPLE (M2:M21, " ")
or
Range("P2") = "CONCATENATEMULTIPLE (M2:M21, " ")"
I don't really know how to apply that
Secondly, I'd like withdraw the Cells(i, "B").Value as percentage. Can I do that in one line like Cells(i, "B").NumberFormat="0.00%".Value (which is not working for me obviously) else I need to copy column B into another column with number format and then combine the new column, properly formatted instead of column B?
Thanks in advance
Percent format: Range("B" & i).NumberFormat = "0.00%"
CONCATENATEMULTIPLE
In VBA, CHR(32) = " "
In Excel, CHAR(32) = " "
With that being said...
'Value
Range("P2").Value = CONCATENATEMULTIPLE(Range("M2:M21"), CHR(32))
'Formula
Range("P2").Formula = "=CONCATENATEMULTIPLE(M2:M21, CHAR(32))"
You should really qualify all of your ranges with a worksheet
Say your workbook has 10 sheets. When you say Range("P2"), how do we (VBE) know what sheet you mean? Objects need to be properly qualified. Sometimes this is not a huge issue, but when you are working across multiple sheets, not qualifying ranges can lead to some unexpected results.
You can qualify with a worksheet a few ways.
Directly: ThisWorkbook.Sheets("Sheet1").Range("P2").Copy
Or use a variable like so
Dim ws as Worksheet: Set ws = ThisWorkbook.Sheets("Sheet1")
ws.Range("P2").Copy
Now there is no room for ambiguity (potential errors) as to the exact location of Range("P2")
First of all, remove your ConcatenateMultiple() code, and instead use Excel worksheet function CONCAT(), which takes a range and a delimiter as parameters.
Here is how you can handle the percentage issue and supply a default for non-numeric items. I've also cleaned up the way you reference your data range.
Sub concat_data()
Dim rngRow As Range, vResult As Variant
Const DEFAULT = 0 'Can also be set to a text value, eg. "Missing"
For Each rngRow In [A2].CurrentRegion.Rows
If IsNumeric(rngRow.Cells(, 4)) Then vResult = rngRow.Cells(, 4) * 100 & "%" Else vResult = DEFAULT
Range("M" & rngRow.Row) = rngRow.Cells(, 1) & rngRow.Cells(, 2) & "/" & vResult & ";"
Next
[M2].End(xlDown).Offset(1).Formula = "=CONCAT(M2:M" & [M2].End(xlDown).Row & ",TRUE,"" "")"
End Sub
I'm not a fan of hard-coding range references, like the [A2] or Range("M"), but will leave that for another time.

VBA divide by 1000 without deleting formula

I am currently using this basic code to divide the value of a cell by 1000:
Sub Divide_by_1000()
Dim cell As Range
For Each cell In Selection
cell = cell / 1000
Next
End Sub
Whilst it works very well for hard-coded numbers, if used on a cell with a formula in it, it removes the formula and outputs a number.
Ideally, I would like the macro to work as it does for a cell with a number in it, but for a cell with a formula I would like it to wrap brackets around the current formula and put a /1000 at the end (i.e. keeping the formula in tact)
I believe there will need to be a test to check if the cell has a formula in it first and apply the code I already have if it doesn't and the code I outlined above if it does.
Any help would be much appreciated.
You can check if the cell has a formula by checking if the first character is a equal sign =
If Left$(cell.Formula, 1) = "=" Then
or even better
If cell.HasFormula Then
and then rewrite the formula extended by ( … )/1000
cell.Formula = "=(" & Right$(cell.Formula, Len(cell.Formula) - 1) & ")/1000"
also I recommend to check if the cell.Value is a number before you divide by 1000
ElseIf IsNumeric(cell.Value) Then
cell.Value = cell.Value / 1000
So you end up with something like
If Left$(cell.Formula, 1) = "=" Then
cell.Formula = "=(" & Right$(cell.Formula, Len(cell.Formula) - 1) & ")/1000"
ElseIf IsNumeric(cell.Value) Then
cell.Value = cell.Value / 1000
End If
Note while this will work for normal formulas, it will crush eg array formulas.
After comments with #PEH:
You can use .HasFormula and .HasArray to test for the formula type
If cell.HasFormula Then
If cell.HasArray Then
cell.Offset(0, 1).FormulaArray = "=(" & Right$(cell.FormulaArray, Len(cell.FormulaArray) - 1) & ")/1000"
Else
cell.Offset(0, 1).Formula = "=(" & Right$(cell.Formula, Len(cell.Formula) - 1) & ")/1000"
End If
ElseIf IsNumeric(cell.Value2) Then
cell.Offset(0, 1).Value2 = cell.Value2 / 1000
End If
A range/cell can be checked for formulas using the HasFormula property e.g.
Dim TheArea as range
Set TheArea = range("some name")
If TheArea.HasFormula then
' All the cells in the range have a formula
End if
alternatively you could use the specialcells property of a range e.g.
For Each Cell In TheArea.SpecialCells(xlCellTypeConstants)
Cell.Value = cell.Value/1000
Next Cell
For Each Cell In TheArea.SpecialCells(xlCellTypeFormulas)
Cell.Formula = "=(" & Right$(Cell.Formula, Len(Cell.Formula) - 1) & ")/1000"
Next Cell
This approach also gives you the opportunity to detect other possible scenarios e.g. xlCellTypeBlanks if they are important to you. The full list of special cells can be found here ...
https://learn.microsoft.com/en-us/office/vba/api/excel.range.specialcells

Creating exact dates in Excel VBA by inputing only the day

In the following picture of an Excel sheet, the heading of the first column, and then of every 7th column after that, contains a month and a year.
I am trying to think of some code which would make entering complete dates under these headings faster. Since the month and the year are already present, I'm thinking there must be a way to enter just the day, and get the whole thing. For example, if "21" were entered in cell A26, "2/21/2015" would result.
Anyone have an idea for how I might get this output?
Edit: Thanks to the helpful replies on this forum, I figured out exactly how to do this. Here is the code for my finished product, in case anyone wants to do something similar:
Private Sub Worksheet_change(ByVal Selection As Range)
Set Sel = Selection
If Sel.Count > 1 Then
Exit Sub
End If
If (Sel.Column - 1) Mod 7 = 0 Or Sel.Column = 1 Then
'In my case, date columns always follow the pattern of 1, 8, 15...
If Sel.Value > 31 Or Sel.Value = "" Then
Exit Sub
Else
Sel.NumberFormat = "General"
Sel.Value = Left(Cells(1, Sel.Column), InStr(Cells(1, Sel.Column), ",") - 1) & " " & _
Sel.Value & Right(Cells(1, Sel.Column), 6)
Selection.NumberFormat = "m/d/yyyy"
End If
End If
End Sub
How about entering the day numbers, selecting the range where these day numbers are entered, and running the below:
Sub Add_month_year()
Dim c As Range
For Each c In Selection
c = Left(Cells(1, c.Column), InStr(Cells(1, c.Column), ",") - 1) & " " & _
c.Value & Right(Cells(1, c.Column), 6)
Next
End Sub
This should return the full dates in date code, which you can then format as you see fit.

Resources