I have an Excel worksheet which includes a text column which stores both text and numbers. I am trying to extract serial numbers, which are 13 digits long, and replace the column content with these serial numbers. I was able to extract the serial numbers but I am stalling on how to replace the cell.value with the serial numbers. Below is my initial approach:
Sub extract_digits()
Dim cell As Range
Dim arr As Variant, arrElem As Variant
Dim final_arr As Variant
With Worksheets("Test_1")
For Each cell In .Range("H5", .Cells(.Rows.Count, "H").End(xlUp))
arr = Split(Replace(cell.Value, " ", " "), " ")
For Each arrElem In arr
If Len(arrElem) = 13 Then MsgBox arrElem
Next arrElem
Next cell
End With
End Sub
The second option (Remove Letters From Strings/Numbers/Cells With User Defined Function) from this site should do the trick:
https://www.extendoffice.com/documents/excel/3244-excel-remove-letters-from-strings-cells-numbers.html
Function below is from above website, it's not mine
Function StripChar(Txt As String) As String
With CreateObject("VBScript.RegExp")
.Global = True
.Pattern = "\D"
StripChar = .Replace(Txt, "")
End With
End Function
How is this working
Is using Regular Expressions (RegEx). The .Pattern means it's looking for any non numeric (ie 0 to 9), it's then using the .Replace function to replace any non numeric with nothing.
How do you use this
Option 1 You can either use it directly in a column next to your column, eg =StripChar(H5) and then just copy that formula down.
Option 2 Alternatively using your code
Sub extract_digits()
Dim cell As Range
Dim arr As Variant, arrElem As Variant
Dim final_arr As Variant
With Worksheets("Test_1")
For Each cell In .Range("H5", .Cells(.Rows.Count, "H").End(xlUp))
cell = StripChar(cell.Value)
Next cell
End With
End Sub
Related
I have a VBA script to drill down into a cell containing a SUMIFS, and then filter the raw data sheet to isolate the rows pertaining. The script works on a cell that has just one SUMIFS.
Some of my cells contain an IF statement with two SUMIFS (depending on what variable the IF is).
I am trying to find a way to first find the relevant SUMIFS in the IF statement, and then use the correct one to then filter.
My code:
Double click script loaded on worksheet to trigger the macro (this is working)
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
FilterBySUMIFs Target.Cells(1)
End Sub
Script loaded to Module for the SUMIFS
Option Explicit
Sub FilterBySUMIFs(r As Range)
Dim v, ctr As Integer
Dim intField As Integer, intPos As Integer
Dim strCrit As String
Dim rngCritRange1 As Range, rngSUM As Range
Dim wksDataSheet As Worksheet
If Not r.Formula Like "*SUMIFS(*" Then Exit Sub
'split formula by comma, strip the right paren
v = Split(Left(r.Formula, Len(r.Formula) - 1), ",")
'the first criteria range is the 2nd element of the array
Set rngCritRange1 = Range(v(LBound(v) + 1))
'use first criteria range to get a reference to the data sheet
With rngCritRange1
Set wksDataSheet = Workbooks(.Parent.Parent.Name).Worksheets(.Parent.Name)
End With
'clear any existing filter, turn filtering on if needed
With wksDataSheet
If .AutoFilterMode And .FilterMode Then
'clear existing autofilter
.ShowAllData
ElseIf Not .AutoFilterMode Then
'display autofilter arrows
rngCritRange1.CurrentRegion.AutoFilter
End If
End With
'set the filters
For ctr = LBound(v) + 1 To UBound(v)
If ctr Mod 2 <> 0 Then
With wksDataSheet
'determine field in case table does not start in column A
intField = .Range(v(ctr)).Column - .AutoFilter.Range.Columns(1).Column + 1
'use evaluate instead of range(v(ctr + 1))
'so both cell-reference and hard-coded criteria are handled.
strCrit = Evaluate(v(ctr + 1))
.Range(v(ctr)).AutoFilter Field:=intField, Criteria1:=strCrit
End With
End If
Next
'strip left paren and everything to left of it,
' get the sum range from first element of array
intPos = InStr(1, v(LBound(v)), "(")
Set rngSUM = Range(Replace(v(LBound(v)), Left(v(LBound(v)), intPos), ""))
'select the SUM range so total displays in status bar
Application.Goto rngSUM
ActiveWindow.ScrollRow = 1
End Sub
Sub KV_FilterBySumIf()
End Sub
My SUMIFS looks as follows:
=IF($C$6="ALL",SUMIFS(IS!Actual_Total,IS!Curr_Bud,H$9,IS!Master_Sub_Account,$C14),SUMIFS(IS!Actual_Total,IS!Curr_Bud,H$9,IS!Master_Sub_Account,$C14,IS!Project_Desc,$C$6))
I came up with a function that can split the IF formula into it's TRUE and FALSE parts and return relevant part based on the expression. So if the expression is true, the function returns the True part of the IF Formula.
The function I have made is not a robust function, and it only works if the given formula is in the structure of "=IF(< expression >, SUMIFS(...), SUMIFS(...))". And it evaluates the expression with the ActiveSheet.
Sub Example()
Dim SumIfsFormula As String
SumIfsFormula = "=IF($C$6=""ALL"",SUMIFS(IS!Actual_Total,IS!Curr_Bud,H$9,IS!Master_Sub_Account,$C14),SUMIFS(IS!Actual_Total,IS!Curr_Bud,H$9,IS!Master_Sub_Account,$C14,IS!Project_Desc,$C$6))"
Debug.Print RelevantSumIfs(SumIfsFormula)
'Output when TRUE : SUMIFS(IS!Actual_Total,IS!Curr_Bud,H$9,IS!Master_Sub_Account,$C14)
'Output when FALSE : SUMIFS(IS!Actual_Total,IS!Curr_Bud,H$9,IS!Master_Sub_Account,$C14,IS!Project_Desc,$C$6)
End Sub
Function RelevantSumIfs(SumIfsFormula As String) As String
Dim IfResult As Boolean
IfResult = Application.Evaluate("=" & Split(Split(SumIfsFormula, "(")(1), ",")(0))
Dim regex As Object
Set regex = CreateObject("VBScript.RegExp")
With regex
.Global = True
.IgnoreCase = True
.Pattern = "SUMIFS\([^)]+\)"
End With
Dim Matches As Object
Set Matches = regex.Execute(SumIfsFormula)
Dim TargetSumIfs
If IfResult Then
TargetSumIfs = Matches(0)
Else
TargetSumIfs = Matches(1)
End If
RelevantSumIfs = TargetSumIfs
End Function
This function could be improved by changing the Regex pattern from explicitly searching for SUMIFS to searching for any function. Something like .Pattern = "[A-Za-z0-9]+\([^)]+\)". But this may also match many other parts of the formula, including the surrounding IF(...). Which is why I left it as .Pattern = "SUMIFS\([^)]+\)"
I am struggling with searching for a string in column and then filling a unique text in the adjacent cell.
For example, I have 3 names and I want to tweak them in adjacent cells as shown below (in reality I have around 20):
A
B
1
name
Desired Format
2
Cat
3
Dog
4
Elephant
The idea is to look for cat in range (A2:A4) and if found, input "17.Catiscool" in the adjacent cell and similarly look for Dog and if found, input "13.Dogisgood" in the adjacent cell to dog.
So far I have this code which just inputs "13.Dogisgood" in B3:
Sub To_be_renamed_as()
'Writing this to determine Correct Name format based on what we have from A3:A25
Dim oldRange As Range
Dim newRange As Range
Dim oldname As Object
Dim newname As String
'Our names start from A3 so I am going to start the range from A3
Set oldReportrange = Range("A3:A25")
Set newReportrange = Range("B3:B25")
For Each oldname In oldRange
If oldname Like "dog" Then
End If
newname = ("13.dogisgood")
oldname.Offset(0, 1) = newname
Next oldname
End Sub
In this instance, it feels like a Select ... Case statement would work best. I've edited your code to show one in use
Sub To_be_renamed_as()
'Writing this to determine Correct Name format based on what we have from A3:A25
Dim oldRange As Range
Dim newRange As Range
Dim oldname As Object
Dim newname As String
'Our names start from A3 so I am going to start the range from A3
Set oldReportrange = Range("A3:A25")
Set newReportrange = Range("B3:B25")
For Each oldname In oldRange
Select Case ucase(oldname.value) 'Make the input value all uppercase for easier comparison
case "DOG"
newname = "13.dogisgood"
case "CAT"
newname = "17.Catiscool"
case else
newname = ""
end select
oldname.Offset(0, 1) = newname
Next oldname
End Sub
Please, try the next code. It should be very fast, even for big ranges, using arrays:
Sub To_be_renamed_as()
Dim sh As Worksheet, arrRep, El, arrEl, rng As Range, arrRng, i As Long
Set sh = ActiveSheet 'use here the sheet you need
arrRep = Split("cat|17.Catiscool,dog|13.Dogisgood,elephant|ElephantIsBig", ",") 'place here your conditions separated by "|"
Set rng = sh.Range("A3:B25"): arrRng = rng.value 'set the range and put it in an array
For Each El In arrRep 'iterate between the arrRep elements
arrEl = Split(El, "|") 'split each element by "|"
For i = 1 To UBound(arrRng) 'iterate between the arrRng rows
If arrRng(i, 1) Like arrEl(0) Then arrRng(i, 2) = arrEl(1) 'when in the first column the search element is found
Next i 'the conditional string is passed in the second array column
Next
rng.value = arrRng 'Drop the processed array into the range, at once
End Sub
You can add in arrRep as many conditions (separated by "|") as you need...
Please, test it and send some feedback.
I've been trying to find/write a macro that opens all hyperlinks contained in a selected range at once. The code I've come across works on only some types of hyperlinks, specifically hyperlinks added through either the right click/Insert>Link/Ctrl+K. The code wont recognise any hyperlinks that are formed using the HYPERLINK() function.
Here's the code I found online:
Sub OpenMultipleLinks()
On Error Resume Next
Set myRange = Application.Selection
Set myRange = Application.InputBox("Range", "OpenMultipleLinks", myRange.Address, Type:=8)
For Each oneLink In myRange.Hyperlinks
oneLink.Follow
Next
End Sub
And here's the formula of a cell that becomes a hyperlink.
=IF($D2="All Charts","",HYPERLINK("http://SubstituteWebsite/ChartId="&$D2&$AF$1,"link"))
Since you do not answer my clarification questions, I will assume that my understanding is correct. So, the following code will work if your formulae containing 'HYPERLINK' formula inside respect the pattern you show us and it should be followed without evaluating if the formula condition is True:
Sub OpenMultipleLinks()
Dim myrange As Range, cel As Range, oneLink
On Error Resume Next
Set myrange = Application.Selection
Set myrange = Application.InputBox("Range", "OpenMultipleLinks", myrange.Address, Type:=8)
For Each oneLink In myrange.Hyperlinks
oneLink.Follow
Next
On Error GoTo 0
For Each cel In myrange
If InStr(cel.Formula, "HYPERLINK") > 0 Then
ActiveWorkbook.FollowHyperlink extractHypFromFormula(ActiveCell.Formula)
End If
Next
End Sub
Function extractHypFromFormula(strForm As String) As String
Dim Hpos As Long, startP As Long, Hlength As Long, strRoot As String
Dim startP2 As Long, cellsAddr As String
Hpos = InStr(strForm, "HYPERLINK") 'it returns position of the first character for "HYPERLINK" string in the formula
If Hpos > 0 Then
startP = Hpos + Len("HYPERLINK") + 2 'it builds the position after which to start searching
'+ 2 because of '(' and "
Hlength = InStr(startP, strForm, """") - startP 'length of the hyperlink fix part (before the strings taken from the two cells value)
strRoot = Mid(strForm, startP, Hlength) 'it returns the hyperlink fix part
startP2 = startP + Len(strRoot) + 2 'next START to return the string keeping the concatenation of the two cells value
cellsAddr = Mid(strForm, startP2, InStr(startP2, strForm, ",") - startP2) 'the string keeping the concatenation of the two cells value
'split the string on "&" separator and use the two elements as range string:
extractHypFromFormula = strRoot & Range(Split(cellsAddr, "&")(0)).value & Range(Split(cellsAddr, "&")(1)).value
End If
End Function
Please, send some feedback after testing it...
You need to parse/evaluate the "hyperlink" formula first. Assuming all your links are in col A this will do what you want:
Sub link()
Dim arr, arr2, j As Long
arr = Sheet1.Range("A1").CurrentRegion.Formula2 'get all in an array
For j = 1 To UBound(arr)
If Left(arr(j, 1), 3) = "=HY" Then 'check if it's a formula
arr(j, 1) = Evaluate(Split(Mid(arr(j, 1), 2), ",")(0) & ")") 'split the url from the rest, evaluate and replace in array
End If
ActiveWorkbook.FollowHyperlink Address:=arr(j, 1), NewWindow:=True 'open in default browser
Next j
End Sub
Best of luck,
ceci
I use comma as decimal separator, but sometimes I receive files where values are below a set limit, and then the file uses point as "<2.5". Sometimes there is one digit before the decimal separator, and sometimes there are two digits.
I need to be able to replace the point with a comma in cells with begin with the "less than" symbol, but retain the actual numbers, so that "<2.5" is replaced with "<2,5" and "<10.0" is replaced with "<10,0". This needs to be done in Excel VBA.
I can't do a general search for "." and replace with ",", since there are places where I need to keep the point as it is.
Anyone have an idea of how to achieve this?
Approach via Replace function
You could read in data to a datafield array, replace the mentioned "<" data via Replace function and write them back in one statement by the following code. - Of course it's possible to use RegEx, too as mentioned in above comment.
Notes
a) I assume you are using data in column A:A via Set rng = ws.Range("A1:A" & n); this can easily changed to any other range.
b) Assigning values to a variant datafield array automatically creates a one based 2-dim array, which you address in case of one column only e.g. via v(1,1), v(2,1), v(3,1) etc. to v(n,1).
Example Code
Option Explicit
Sub replaceLowerThan()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("MySheet") ' << Change to your sheet name
Dim n As Long, i As Long
Dim rng As Range
Dim v
' get last row number and define data range
n = ws.Range("A" & ws.Rows.Count).End(xlUp).Row
Set rng = ws.Range("A1:A" & n)
' write data to 1-based 2-dim datafield array
v = rng.Value2
' replace "<..." values
For i = 1 To n
If Not IsError(v(i, 1)) Then ' omit cells with errors like #DIV/0!
If v(i, 1) Like "<*" Then v(i, 1) = Replace(v(i, 1), ".", ",")
End If
Next i
' write values back
rng.Value2 = v
End Sub
This worked:
Dim strPattern As String: strPattern = "(<[0-9]+)[\.]"
Dim strReplace As String: strReplace = "$1,"
Dim myreplace As Long
Dim strInput As String
Dim Myrange As Range
Set RegEx = CreateObject("VBScript.RegExp")
Set Myrange = ActiveSheet.UsedRange
For Each cell In Myrange
If strPattern <> "" Then
strInput = cell.Value
With RegEx
.Global = True
.MultiLine = True
.IgnoreCase = False
.Pattern = strPattern
End With
If RegEx.Test(strInput) Then
cell.Value = (RegEx.Replace(strInput, strReplace))
End If
End If
Next
In my Excel sheet, I have VBA code to detect the last non-empty cell in Column A and add incremental serial number value in that cell (in below example cell A6 value should be SN104).
This processing is limited only to Column A, and in this image example first non-empty last cell is at A6, sometimes it can be after 100 cells or 1000 cells.
Is there any simple way to handle this scenario?
Public Function GetLastCell(ByVal startRng as Range) as Range
With startRng
Set GetLastCell = IIf(.Offset(1).Value = "", .Offset(0), .End(xlDown))
End With
End Function
For your example, you can define a Range variable rng, and call the above function in this way:
Dim rng as Range
Set rng = GetLastCell( Range("A1") )
Then rng is referring to the last cell of Column A
Something like
Dim lngLastUsedRow as Integer
lngLastUsedRow = Range("A65536").End(xlUp).Row
Dim lngFirstEmptyRow as Integer
lngFirstEmptyRow = Range("A65536").End(xlUp).Offset(1,0)
// do your increment
newValue = Cint(Mid(CurrentWorkSheet.Range("A" + lngLastUsedRow).Value,2)) + 1
CurrentWorkSheet.Range("A" & lngFirstEmptyRow).Value = "SN" + newValue
I don't have excel on me, I can't test it right now. But this should get you started.
Something like this which
Find the true last used cell in any Excel version, and handles a blank result
Parses the string in the last non-blank cell (handling any length of alpha then numeric)to update the next blank cell
Sub GetTrueLastCell()
Dim rng1 As Range
Dim objRegex As Object
Dim strFirst As String
Set rng1 = Columns("A").Find("*", [a1], xlFormulas)
If Not rng1 Is Nothing Then
Set objRegex = CreateObject("vbscript.regexp")
With objRegex
.Pattern = "^(.+?[^\d])(\d+)$"
If .test(rng1.Value) Then
strFirst = .Replace(rng1.Value, "$1")
rng1.Value = strFirst & (Val(Right$(rng1.Value, Len(rng1.Value) - Len(strFirst)) + 1))
End If
End With
Else
MsgBox "Data range is blank"
End If
End Sub
Assumptions:
Next cell in list is empty
Serial N's only have three digits after 'SN' string (i.e., if it reaches 1000, earlier ones don't need padding, like '0100'
-
Dim rAll As Range, rLast As Range, rNext As Range, iNextSN As Integer
Set rAll = Intersect(Sheet1.Cells(1).CurrentRegion, Sheet1.Columns(1)) ' Column 'A' can be contiguous with others
Set rLast = rAll.Cells(rAll.Cells.Count) ' Last cell in current list
Set rNext = rLast.Offset(1) ' Next cell below current list
iNextSN = CInt(Right(rLast.Value, 3)) ' Get value of last serial N
rNext.Value = "SN" & iNextSN + 1 ' Assemble next SN with increment
-