I have a macro which compares the first 20 characters of strings in two columns, when the customer type is "O" and gives the results. But I need to compare these two columns and if 80% of the strings match, i need to get the result as "ok" else "check". Can someone help me with correcting my code. Thanks
Sub Macro1()
'
'Match Organization names only the first 20 characters
'
'
Dim sht As Worksheet
Dim LR As Long
Dim i As Long
Dim str As String, str1 As String
Set sht = ActiveWorkbook.Worksheets("ORD_CS")
LR = sht.UsedRange.Rows.Count
With sht
For i = 8 To LR
If CStr(.Range("Q" & i).Value) = "O" Then
str = Left(.Range("S" & i).Value, 20)
str1 = Left(.Range("U" & i).Value, 20)
If str = str1 Then
Range("V" & i).Value = "ok"
Else
Range("V" & i).Value = "check"
End If
End If
Next i
End With
End Sub
Maybe use len() and multiply by .8
Sub Button1_Click()
Dim LstRw As Long, Rng As Range, sh As Worksheet, c As Range
Set sh = Sheets("ORD_CS")
With sh
LstRw = .Cells(.Rows.Count, "S").End(xlUp).Row
Set Rng = .Range("S2:S" & LstRw)
For Each c In Rng.Cells
If InStr(1, c.Offset(, 2), Left(c, Len(c) * 0.8)) Then
c.Offset(, 3) = "Yep"
Else: c.Offset(, 3) = "Nope"
End If
Next c
End With
End Sub
Compare column s or t whichever string is smaller.
You can count the string characters to find out which one is smaller.
Sub Button1_Click()
Dim LstRw As Long, Rng As Range, sh As Worksheet, c As Range
Set sh = Sheets("ORD_CS")
With sh
LstRw = .Cells(.Rows.Count, "S").End(xlUp).Row
Set Rng = .Range("S2:S" & LstRw)
For Each c In Rng.Cells
x = IIf(Len(c) < Len(c.Offset(, 1)), 0, 1)
If InStr(1, .Cells(c.Row, "U"), Left(c.Offset(, x), Len(c.Offset(, x)) * 0.8)) Then
.Cells(c.Row, "V") = "Yep"
Else: .Cells(c.Row, "V") = "Nope"
End If
Next c
End With
End Sub
Just keep track of the number of hits and divide that by the total rows you are looking at:
Sub Macro1()
'
'Match Organization names only the first 20 characters
'
'
Dim sht As Worksheet
Dim LR As Long
Dim i As Long
Dim str As String, str1 As String
Dim totalRows as Long, Dim matchRows as Long
Set sht = ActiveWorkbook.Worksheets("ORD_CS")
LR = sht.UsedRange.Rows.Count
totalRows = LR-8
With sht
For i = 8 To LR
If CStr(.Range("Q" & i).Value) = "O" Then
str = Left(.Range("S" & i).Value, 20)
str1 = Left(.Range("U" & i).Value, 20)
If str = str1 Then
Range("V" & i).Value = "ok"
matchRows = matchRows + 1
Else
Range("V" & i).Value = "check"
End If
End If
Next i
End With
'heres ther percentage of hits:
if matchRows/totalRows > .8 Then
msgbox "OK"
else
msgbox "Check"
End if
End Sub
If it's not 80% of the total matching rows you are looking for, but rather comparing to strings to get a number of how aproximately matched they are, you could implement the Levenshtein distance function and do your compare using that. See here for a VBA function that will do that which should be easy to implement in your code
Related
I have values in column B (green, blue, white....) and I want to count them and the result must appear in column A in the following format (green01, green02, green03...., blue01, blue02, blue03, blue04...., white01, white 02...).
The result must look like in this photo
I have searched the net for a macro, but I didn't find one to fit my needs.
THX
No VBA needed, in A1:
=B1&TEXT(COUNTIF(B$1:B1,B1),"00")
Try the next code, please:
Sub testCountSortColors()
Dim sh As Worksheet, lastRow As Long, i As Long, c As Long
Set sh = ActiveSheet
lastRow = sh.Range("B" & Rows.count).End(xlUp).Row
sh.Range("B1:B" & lastRow).Sort key1:=sh.Range("B1"), order1:=xlAscending, Header:=xlYes
For i = 2 To lastRow
If sh.Range("B" & i).value <> sh.Range("B" & i - 1).value Then
c = 1
Else
c = c + 1
End If
sh.Range("A" & i).value = sh.Range("B" & i).value & Format(c, "00")
sh.Range("A" & i).Font.Color = sh.Range("B" & i).Font.Color
Next
End Sub
I thought you maybe have column headers...
A Unique Count
Adjust the values in the constants section.
Option Explicit
Sub countUnique()
Const SourceColumn As Variant = 2 ' e.g. 2 or "B"
Const TargetColumn As Variant = 1 ' e.g. 1 or "A"
Const FirstRow As Long = 1
Dim rng As Range
Dim dict As Object
Dim Key As Variant
Dim Source As Variant, Target As Variant
Dim i As Long, UB As Long
Dim CurrString As String
Set rng = Columns(SourceColumn).Find(What:="*", _
LookIn:=xlFormulas, SearchDirection:=xlPrevious)
If rng Is Nothing Then GoTo exitProcedure
If rng.Row < FirstRow Then GoTo exitProcedure
Source = Range(Cells(FirstRow, SourceColumn), rng)
Set rng = Nothing
UB = UBound(Source)
Set dict = CreateObject("Scripting.Dictionary")
For i = 1 To UB
If Source(i, 1) <> "" Then
dict(Source(i, 1)) = dict(Source(i, 1)) + 1
End If
Next i
ReDim Target(1 To UB, 1 To 1)
For i = UB To 1 Step -1
CurrString = Source(i, 1)
If CurrString <> "" Then
Target(i, 1) = CurrString & Format(dict(CurrString), "00")
dict(CurrString) = dict(CurrString) - 1
End If
Next i
With Cells(FirstRow, TargetColumn)
.Resize(Rows.Count - FirstRow + 1).ClearContents
.Resize(UB) = Target
End With
MsgBox "Operation finished successfully."
exitProcedure:
End Sub
My end goal is to replace about 200,000 =Offset formulas in an Excel sheet with the appropriate direct cell reference with VBA. For example, I have =Offset(Sheet1!A1,Sheet2!B3,Sheet2!G5). B3 in sheet2 contains the number 2 and G5 in sheet2 contains the number 3. The offset formula pulls the number in sheet1 that is 2 rows and 3 columns (C3) away from A1. There are 200,000 of these formulas in the sheet and I would like to use VBA to change every one to =Sheet1!C3 in the example above. Clearly every direct cell reference is different - they're not all C3.
I have the following code right now but it replaces with a hardcoded cell number, which I would like to change to be dynamic.
My code is below:
Sub FindReplaceAll()
Dim sht As Worksheet
Dim cell As Range
Dim fnd As Variant
Dim rplc As Variant
fnd = "Offset*"
rplc = "Sheet1!C3"
For Each sht In ActiveWorkbook.Worksheets
sht.Cells.Replace what:=fnd, Replacement:=rplc, LookAt:=xlPart, SearchOrder:=xlByRows, MatchCase:=False, SearchFormat:=False, ReplaceFormat:=False
Next sht
End Sub
The solution is tried only with simplest OFFSET formula. For coverting more complex offset formula more tweaking may be needed.
Option Explicit
Sub test()
Dim Xformula As String, Yformula As String
Dim Xref As String, XRow As String, XCol As String
Dim YRow As Long, YCol As Long
Dim ZRow As Long, ZCol As Long
Dim Zsht As String, ZColStr As String
Dim Ws As Worksheet, Cel As Range
Dim tm As Double, Cnt As Long
tm = Timer
Set Ws = ThisWorkbook.ActiveSheet
Cnt = 0
For Each Cel In Ws.UsedRange.Cells
If Mid(Cel.Formula, 2, 6) = "OFFSET" Then
On Error Resume Next
Xformula = Cel.Formula
Xformula = Replace(Xformula, "=OFFSET(", "")
Xformula = Left(Xformula, Len(Xformula) - 1)
Xref = Split(Xformula, ",")(0)
'Debug.Print Xref, Xformula, Cel.Address
XRow = Split(Xformula, ",")(1)
XCol = Split(Xformula, ",")(2)
YRow = Evaluate(XRow)
YCol = Evaluate(XCol)
If InStr(1, Xref, "!") > 0 Then
Zsht = Split(Xref, "!")(0) & "!"
Else
Zsht = ""
End If
ZRow = Range(Xref).Row + YRow
ZCol = Range(Xref).Column + YCol
ZColStr = Split(Cells(1, ZCol).Address, "$")(1)
Zsht = "=" & Zsht & ZColStr & ZRow
'The cells contain #REF or could not be converted would me marked Red
If Err <> 0 Then
Cel.Interior.Color = vbRed
Err.Clear
On Error GoTo 0
Else
Cel.Formula = Zsht
Cnt = Cnt + 1
End If
End If
Next
Debug.Print Timer - tm & " Seconds taken to convert " & Cnt & " formulas "
End Sub
Since code is tested with around 1000 offset formula only takes 3 sec. For working with 200 K formula it may be needed to add standard techniques like
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
Application.ScreenUpdating = False
But since i don't personally prefer it, Another option is to tweak the code to work on selected range only and select a limited range in the sheet at a time and execute.
May try on trial workbook / Worksheet only and feedback.
Edit: Adding Array based solution for faster performance, It could be made somehow more faster by using For Each XVariant in Arr and by eliminating Union(ErrRng,... only if there is no need to mark error cells. It takes around 90 sec (70 sec to calculate and 20 more seconds to replace) to change 300 K Offset formula.
Option Explicit
Sub test()
Dim Xformula As String, Yformula As String
Dim Xref As String, XRow As String, XCol As String
Dim YRow As Long, YCol As Long
Dim ZRow As Long, ZCol As Long
Dim Zsht As String, ZColStr As String
Dim Ws As Worksheet, ErrRng As Range, Xcel As Variant
Dim tm As Double, Cnt As Long, Arr As Variant
Dim Rw As Long, Col As Long, RngRowOffset As Long, RngColOffset As Long
tm = Timer
Set Ws = ThisWorkbook.ActiveSheet
Cnt = 0
Arr = Ws.UsedRange.Formula
RngRowOffset = Ws.UsedRange(1, 1).Row - 1
RngColOffset = Ws.UsedRange(1, 1).Column - 1
'Debug.Print RngRowOffset, RngColOffset
For Rw = 1 To UBound(Arr, 1)
For Col = 1 To UBound(Arr, 2)
Xcel = Arr(Rw, Col)
If Mid(Xcel, 2, 6) = "OFFSET" Then
On Error Resume Next
Xformula = Xcel
Xformula = Replace(Xformula, "=OFFSET(", "")
Xformula = Left(Xformula, Len(Xformula) - 1)
Xref = Split(Xformula, ",")(0)
'Debug.Print Xref, Xformula, Cel.Address
XRow = Split(Xformula, ",")(1)
XCol = Split(Xformula, ",")(2)
YRow = Evaluate(XRow)
YCol = Evaluate(XCol)
If InStr(1, Xref, "!") > 0 Then
Zsht = Split(Xref, "!")(0) & "!"
Else
Zsht = ""
End If
ZRow = Range(Xref).Row + YRow
ZCol = Range(Xref).Column + YCol
ZColStr = Split(Cells(1, ZCol).Address, "$")(1)
Zsht = "=" & Zsht & ZColStr & ZRow
'The cells containg #REF or could not be converted would me marked Red
If Err <> 0 Then
If ErrRng Is Nothing Then
Set ErrRng = Cells(Rw + RngRowOffset, Col + RngColOffset)
Else
Set ErrRng = Union(ErrRng, Cells(Rw + RngRowOffset, Col + RngColOffset))
End If
Err.Clear
On Error GoTo 0
Else
Arr(Rw, Col) = Zsht
Cnt = Cnt + 1
End If
End If
Next
Next
Debug.Print Timer - tm & " Seconds taken to Calculate " & Cnt & " formulas "
Ws.UsedRange.Formula = Arr
Debug.Print Timer - tm & " Seconds taken to Repalce formulas "
ErrRng.Interior.Color = vbRed
Debug.Print Timer - tm & " Seconds taken to mark error cells "
End Sub
I adapted code I found online.
It finds the string "car" in column A and returns the rows as an array
It assigns a variable to the length of the array (how many matches it found)
It assigns a variable to generate a random number between 0 and the length of the array
It then prints a random matching row's value into K3
Dim myArray() As Variant
Dim x As Long, y As Long
Dim msg As String
With ActiveSheet.Range("A1:A" & ActiveSheet.Range("A" & Rows.Count).End(xlUp).Row)
Set c = .find("Car", LookIn:=xlValues)
If Not c Is Nothing Then
firstAddress = c.Address
Do
ReDim Preserve myArray(y)
myArray(y) = c.Row
y = y + 1
Set c = .findNext(c)
If c Is Nothing Then
GoTo DoneFinding
End If
Loop While c.Address <> firstAddress
End If
DoneFinding:
End With
For x = LBound(myArray) To UBound(myArray)
msg = msg & myArray(x) & " "
Next x
ArrayLen = UBound(myArray) - LBound(myArray)
random_index = WorksheetFunction.RandBetween(0, ArrayLen)
MsgBox myArray(random_index)
Dim test As String
test = "B" & myArray(random_index)
Range("K3").Value = Range(test)
Example
I'm struggling with adapting the find code to allow for multiple criteria. So in my example, it finds "Car". What if I want to find matches that had "Car" in column A and "Red" in column D?
I tried
With ActiveSheet.Range("A1:A" & "D1:D" & ActiveSheet.Range("A" & Rows.Count).End(xlUp).Row & ActiveSheet.Range("D" & Rows.Count).End(xlUp).Row)
Set c = .find("Car", "Red", LookIn:=xlValues)
I get type mismatch on the Set line.
In case it is confusing, it currently looks for a string e.g. "Car" but I will eventually link this to the variable which will be assigned to a data validation list. So if the user chooses "car" from a drop down list, this is what it will search for.
Maybe Advancde Filter is something that fit your needs:
Example Code
Option Explicit
Public Sub FilterData()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("YourSheetName")
Dim LastRow As Long
LastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
Dim CriteriaRange As Range
Set CriteriaRange = ws.Range("A1", "E2")
Dim DataRange As Range
Set DataRange = ws.Range("A4", "E" & LastRow)
DataRange.AdvancedFilter Action:=xlFilterInPlace, CriteriaRange:=CriteriaRange, Unique:=False
End Sub
Public Sub ShowAll()
On Error Resume Next
ActiveSheet.ShowAllData
On Error GoTo 0
End Sub
Edit according comment:
You can use the advanced filter and then loop through the filter results:
Option Explicit
Public CurrentRow As Long
Public Sub FilterData()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("YourSheetName")
Dim LastRow As Long
LastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
Dim CriteriaRange As Range
Set CriteriaRange = ws.Range("A1", "E2")
Dim DataRange As Range
Set DataRange = ws.Range("A4", "E" & LastRow)
DataRange.AdvancedFilter Action:=xlFilterInPlace, CriteriaRange:=CriteriaRange, Unique:=False
End Sub
Public Sub ShowAll()
On Error Resume Next
ActiveSheet.ShowAllData
CurrentRow = 1
On Error GoTo 0
End Sub
Public Sub GetNextResult()
FilterData
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("YourSheetName")
Dim LastRow As Long
LastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
Dim DataRange As Range
Set DataRange = ws.Range("A4", "E" & LastRow)
Dim FilteredData As Range
Set FilteredData = DataRange.Resize(ColumnSize:=1).SpecialCells(xlCellTypeVisible)
If CurrentRow + 1 > FilteredData.Cells.Count Then
CurrentRow = 1
End If
CurrentRow = CurrentRow + 1
Dim i As Long
Dim Cell As Variant
For Each Cell In FilteredData
i = i + 1
If i = CurrentRow Then
Cell.EntireRow.Select
'or
'MsgBox Cell.Value & vbCrLf & Cell.Offset(0, 1) & vbCrLf & Cell.Offset(0, 2) & vbCrLf & Cell.Offset(0, 3) & vbCrLf & Cell.Offset(0, 4)
End If
Next Cell
End Sub
I want to remove duplicates based on the text in Column I and sum the values in Column C, the data in the other columns doesn't matter.
I do not want a pivot table and I am aware they are the preferred option for this type of thing.
An example of what I'd like to achieve:
I found VBA code and tried to modify it. It doesn't delete all the lines.
Sub Sum_and_Dedupe()
With Worksheets("data")
'deal with the block of data radiating out from A1
With .Cells(1, 1).CurrentRegion
'step off the header and make one column wider
With .Resize(.Rows.Count - 1, .Columns.Count + 1).Offset(1, 0)
.Columns(.Columns.Count).Formula = "=sumifs(c:c, i:i, i2)"
.Columns(3) = .Columns(.Columns.Count).Value
.Columns(.Columns.Count).Delete
End With
'remove duplicates
.RemoveDuplicates Columns:=Array(9), Header:=xlYes
End With
.UsedRange
End With
End Sub
This should be an answer to your question.
However, code might require adaptation if the range in which you look becomes very long.
Option Explicit
Sub test()
Dim wb As Workbook
Dim ws As Worksheet
Dim LastRow As Long, LastCol As Long, a As Double, i As Long
Dim Rng As Range
Dim Cell As Variant, Estimate As Variant
Set wb = ThisWorkbook
Set ws = wb.Sheets(1)
LastRow = ws.Cells(ws.Rows.Count, "I").End(xlUp).Row
LastCol = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
Set Rng = ws.Range(ws.Cells(2, 9), ws.Cells(LastRow, 9))
For Each Cell In Rng
i = 0
a = 0
For Each Estimate In Rng
If Estimate.Value = Cell.Value Then
i = i + 1 'Count nr of intances
a = a + ws.Cells(Estimate.Row, 3).Value 'sum booking value
If i > 1 Then
ws.Rows(Estimate.Row).Delete
i = 1
LastRow = LastRow - 1
End If
End If
Next Estimate
ws.Cells(Cell.Row, 3).Value = a 'Enter sum in booked this week
Next Cell
End Sub
You'll either need to change your current sheet name to data, or change the first two lines of this code to fit your needs. sh = the data sheet that you showed us. osh = an output sheet that this code will generate. Note also if column C or I move you can update the positions easily by changing colBooked and colEstimate. If you have more than a thousand unique estimate entries then make the array number larger than 999.
Sub summariseEstimates()
Dim sh As String: sh = "data"
Dim osh As String: osh = "summary"
Dim colBooked As Integer: colBooked = 3
Dim colEstimate As Integer: colEstimate = 9
Dim myArray(999) As String
Dim shCheck As Worksheet
Dim output As Worksheet
Dim lastRow As Long
Dim a As Integer: a = 0
Dim b As Integer
Dim r As Long 'row anchor
Dim i As Integer 'sheets
'Build summary array:
With Worksheets(sh)
lastRow = .Cells.Find("*", searchorder:=xlByRows, searchdirection:=xlPrevious).Row
For r = 2 To lastRow
If r = 2 Then 'first entry
myArray(a) = .Cells(r, colEstimate) & "," & .Cells(r, colBooked)
Else
For b = 0 To a
If VBA.LCase(VBA.Replace(.Cells(r, colEstimate), " ", "")) = VBA.LCase(VBA.Replace(VBA.Split(myArray(b), ",")(0), " ", "")) Then 'match
myArray(b) = VBA.Split(myArray(b), ",")(0) & "," & VBA.Split(myArray(b), ",")(1) + .Cells(r, colBooked)
Exit For
End If
Next b
If b = a + 1 Then 'completed loop = no match, create new array item:
a = a + 1
myArray(a) = .Cells(r, colEstimate) & "," & .Cells(r, colBooked)
End If
End If
Next r
End With
'Create summary sheet:
On Error Resume Next
Set shCheck = Worksheets(osh)
If Err.Number <> 0 Then
On Error GoTo 0
Set output = Worksheets.Add(After:=Worksheets(sh))
output.Name = osh
Err.Clear
Else
On Error GoTo 0
If MsgBox("*" & osh & "* sheet already exists. Proceed to delete and recreate?", vbOKCancel, "Summary") = vbCancel Then
Exit Sub
Else
Application.DisplayAlerts = False
Worksheets(osh).Delete
Set output = Worksheets.Add(After:=Worksheets(sh))
output.Name = osh
End If
End If
'Output to summary sheet:
With Worksheets(osh)
.Cells(1, 1).Value = "ESTIMATE"
.Cells(1, 2).Value = "BOOKED THIS WEEK"
For b = 0 To a
.Cells(b + 2, 1).Value = VBA.Split(myArray(b), ",")(0)
.Cells(b + 2, 2).Value = VBA.Split(myArray(b), ",")(1)
Next b
.Columns("A:B").AutoFit
End With
End Sub
I am new to VBA and was trying to write a macro to check duplicates among a column. I have values in columns from A to Z with varying last row number, some may be 5 while some may be 10. Is there any way to check if duplicate value exist among a column and then print "duplicate" on the first row (I dont have any values in the first row for all the columns). I need this for varying last row and last column number.
You can try:
Option Explicit
Public Sub Get_Unique_Count_Paste_Array()
Dim Ob As Object
Dim rng As Range
Dim i As Long
Dim str As String
Dim LR As Long
Dim Item As Variant
With Worksheets("Sheet1")
For i = 1 To 26
Set Ob = CreateObject("scripting.dictionary")
LR = .Cells(.Rows.Count, i).End(xlUp).Row
For Each rng In .Range(Cells(2, i), Cells(LR, i))
str = Trim(rng.Value)
If Len(str) > 0 Then
Ob(str) = Ob(str) + 1
End If
Next rng
For Each Item In Ob.keys
If .Cells(1, i).Value = "" Then
.Cells(1, i).Value = Item
ElseIf .Cells(1, i).Value <> "" Then
.Cells(1, i).Value = .Cells(1, i).Value & ", " & Item
End If
Next Item
Next i
End With
End Sub
Edited Version:
Option Explicit
Public Sub Get_Unique_Count_Paste_Array()
Dim Ob As Object
Dim rng As Range
Dim i As Long
Dim str As String
Dim LR As Long
Dim Item As Variant
With Worksheets("Sheet1")
For i = 1 To 26
Set Ob = CreateObject("scripting.dictionary")
LR = .Cells(.Rows.Count, i).End(xlUp).Row
For Each rng In .Range(Cells(2, i), Cells(LR, i))
str = Trim(rng.Value)
If Len(str) > 0 Then
Ob(str) = Ob(str) + 1
End If
Next rng
For Each Item In Ob.keys
If .Cells(1, i).Value = "" And Ob(Item) > 1 Then
.Cells(1, i).Value = "Duplicate"
Exit For
End If
Next Item
Next i
End With
End Sub
A slight alteration of #error 1004's idea
Private d As Scripting.Dictionary
Private s As String
Function Get_Dupe_Summary(rngInput As Excel.Range) as string
Dim c As Excel.Range
Set d = New Scripting.Dictionary
For Each c In rngInput.Cells
If d.Exists(c.Value) Then
Get_Dupe_Summary = Get_Dupe_Summary & _
IIf(Len(Get_Dupe_Summary) > 0, ",", "") & _
"Dupe : " & c & " on row " & c.Row
Else
d.Add c.Value, 1
End If
Next c
End Function