I'm trying to create a macro using Excel 2007 for some data I've collected. What I need the macro do to is, search a column and find a certain number of consecutive zero's (60) and if there is 60 consecutive zero's delete them. Any advice or help would be really appreciated!
Is this what you are trying?
LOGIC:
Filter the range on the criteria
Store the address on the visible cells in a variable
Remove "$" which Excel automatically puts in the address
Check if the visible cell address is like "2:2" or "2:2,5:64"
Find the difference between the start row and end row
If difference is >= say 60 then clear contents.
CODE
Option Explicit
Sub Sample()
Dim ws As Worksheet
Dim lRow As Long, times As Long, Col As Long, i As Long
Dim rRange As Range
Dim addr As String, MyArray() As String, tmpAr() As String, num As String
'~~> Change these as applicable
Set ws = ThisWorkbook.Sheets("Sheet1") '<~~ Sheet1
Col = 1 '<~~ Col A
num = "0" '<~~ Number to replace
times = 60 '<~~ Consecutive Cells with Numbers
'~~> Don't change anything below this
With ws
lRow = .Range(ReturnName(Col) & .Rows.Count).End(xlUp).Row
Set rRange = .Range(ReturnName(Col) & "1:" & ReturnName(Col) & lRow)
'~~> Remove any filters
.AutoFilterMode = False
'~~> Filter, offset(to exclude headers)
With rRange
.AutoFilter Field:=1, Criteria1:="=" & num
'~~> get the visible cells address
addr = .Offset(1, 0).SpecialCells(xlCellTypeVisible).EntireRow.Address
End With
'~~> Remove any filters
.AutoFilterMode = False
addr = Replace(addr, "$", "")
'~~> Check if addr has multiple ranges
If InStr(1, addr, ",") Then
MyArray = Split(addr, ",")
'~~> get individual ranges
For i = LBound(MyArray) To UBound(MyArray)
tmpAr = Split(MyArray(i), ":")
'~~> If difference is >= times then clear contents
If Val(Trim(tmpAr(1))) - Val(Trim(tmpAr(0))) >= times - 1 Then
.Range(ReturnName(Col) & Trim(tmpAr(0)) & ":" & _
ReturnName(Col) & Trim(tmpAr(1))).ClearContents
End If
Next i
Else
tmpAr = Split(addr, ":")
If Val(Trim(tmpAr(1))) - Val(Trim(tmpAr(0))) >= times - 1 Then
.Range(ReturnName(Col) & Trim(tmpAr(0)) & ":" & _
ReturnName(Col) & Trim(tmpAr(1))).ClearContents
End If
End If
End With
End Sub
'~~~> Function to retrieve Col Names from Col Numbers
Function ReturnName(ByVal numb As Long) As String
ReturnName = Split(Cells(, numb).Address, "$")(1)
End Function
Though I have a feeling you are going to change the requirements after you run this...
Select all the cells you want to look at, then run this code:
Option Explicit
Sub deleteConsecutiveZeros()
Dim rng As Excel.Range
Dim countZeros As Long
Dim lastCellRow As Long
Dim iCurrentRow As Long
Set rng = Selection
lastCellRow = rng.Cells.SpecialCells(xlCellTypeLastCell).Row
For iCurrentRow = lastCellRow To 1 Step -1
If (countZeros >= 60) Then
ActiveSheet.Range(rng.Cells(iCurrentRow + 59, 1).Address, rng.Cells(iCurrentRow, 1).Address).EntireRow.Delete
countZeros = 0
End If
If (rng.Cells(iCurrentRow, 1).Value = 0 And rng.Cells(iCurrentRow, 1).Text <> vbNullString) Then
countZeros = countZeros + 1
Else
countZeros = 0
End If
Next
End Sub
Related
would anyone be able to help?
I am trying to write VBA to highlight if the cell has 2 different values. It seems to highlight all including the same name appear twice. Thanks for any help!
Sub CountTwoOrMoreDifferent()
Dim myRange As Long
myRange = Cells(Rows.Count, "A").End(xlUp).Row
Range("A2:A" & myRange).Select
For Each AnimalName In Selection
AnimalNameMoreThan2 = AnimalName.Value
If InStr(AnimalNameMoreThan2, "Cat") + _
InStr(AnimalNameMoreThan2, "Dog") + _
InStr(AnimalNameMoreThan2, "Cow") _
+ InStr(AnimalNameMoreThan2, "Chicken") + _
InStr(AnimalNameMoreThan2, "Snake") + _
InStr(AnimalNameMoreThan2, "Tums") + _
InStr(AnimalNameMoreThan2, "Drop") > 1 Then
AnimalName.Interior.Color = vbRed
End If
Next AnimalName
End Sub
Data in column A
Sample Data
You can use this code.
It is split into two parts
a sub - which does the check per cell.
a function that checks if there is a duplicate within an array.
It returns true in case there is at least one dup.
Public Sub highlightDuplicateValues()
'get Range to check
Dim lastRow As Long, rgToCheck As Range
With ActiveSheet
lastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
Set rgToCheck = .Range("A2:A" & lastRow) 'no need to select!
End With
Dim c As Range, arrValuesInCell As Variant
Dim i As Long
For Each c In rgToCheck.Cells
'get an array of values/animals in cell
arrValuesInCell = Split(c.Value, ";")
'now check for each value if it has a dup - if yes color red and exit check
For i = LBound(arrValuesInCell) To UBound(arrValuesInCell)
If hasDupInArray(arrValuesInCell, i) = True Then
c.Interior.Color = vbRed
Exit For
End If
Next
Next
End Sub
Private Function hasDupInArray(arrValues As Variant, checkI As Long) As Boolean
'only values after the checkI-value are checked.
'Assumption: previous values have been checked beforehand
Dim varValueToCheck As Variant
varValueToCheck = arrValues(checkI)
Dim i As Long
For i = checkI + 1 To UBound(arrValues)
If arrValues(i) = varValueToCheck Then
hasDupInArray = True
Exit For
End If
Next
End Function
I have a column "D" in my spreadsheet that contains a list of software to install. The list is very long and I only want a few applications to install. Here are a few examples:
Row2: License-E3; Minitab 17; Minitab 18; Proficy Historian 7.0; ;
Row3: License-E3; Attachmate Reflection for UNIX and OpenVMS 14.0; Perceptive Content Desktop Client;
Row4: License-E1; Avaya one-X® Communicator; PipelineBillingInterfaceSystemClient-V2_0; ; SAP-GUI-3Apps; Minitab 18
So, in the first example, I want column D row 2 to just say :
License-E3,Minitab 18
Row 3 to say : License-E3,Reflection
And 4 to say : License-E1,Minitab 18
The rows are auto filtered based on the User Id column, which is Column A in this sheet.
The commented section is basically what I want to do.
Here is my code so far:
Sub FilterSoftware()
Dim cl As Range, rng As Range, Lastrow As Integer, sSoft() As String, i As Long
Dim vSoft As Variant, sNew As String, j As Long, sNewSoft() As String
vSoft = Array("License-E3", "License-E1", "Reflection", "Minitab 18", "RSIGuard", "Java")
Dim Ws As Worksheet: Set Ws = Sheet1
With Ws
Lastrow = .Range("D" & .Rows.Count).End(xlUp).Row
End With
Set rng = Range("D2:D" & Lastrow)
For Each cl In rng.SpecialCells(xlCellTypeVisible)
sSoft = Split(cl, ";")
For i = LBound(sSoft) To UBound(sSoft)
If Not sSoft(i) = " " Then
For j = LBound(vSoft) To UBound(vSoft)
sNewSoft = Split(vSoft(j), " ")
Debug.Print Trim$(sSoft(i))
Debug.Print Trim$(vSoft(j))
'if sSoft(i) contains any words from vSoft(j)
'Join vSoft(j) with comma delimiter until full
'and overwrite in column D
Next j
End If
Next i
Next cl
End Sub
Please, use the next adapted code. It will return in the next column, only for testing reason. If it returns what you need, you can change cl.Offset(0, 1).Value = Join(sNew, ",") with cl.Value = Join(sNew, ","):
Sub FilterSoftware()
Dim cl As Range, rng As Range, Lastrow As Long, sSoft
Dim vSoft, sNew, i As Long, j As Long, t As Long
vSoft = Array("License-E3", "License-E1", "Reflection", "Minitab 18", "RSIGuard", "Java")
Dim Ws As Worksheet: Set Ws = ActiveSheet ' Sheet1
Lastrow = Ws.Range("D" & Ws.rows.count).End(xlUp).row
Set rng = Range("D2:D" & Lastrow)
ReDim sNew(UBound(vSoft)) 'redim the array to a dimension to be sure it will include all occurrences
For Each cl In rng.SpecialCells(xlCellTypeVisible)
sSoft = Split(cl, ";")
For i = LBound(sSoft) To UBound(sSoft)
If Not sSoft(i) = "" Then 'for cases of two consecutive ";"
For j = LBound(vSoft) To UBound(vSoft)
If InStr(1, sSoft(i), vSoft(j), vbTextCompare) > 0 Then
sNew(t) = vSoft(j): t = t + 1: Exit For
End If
Next j
End If
Next i
If t > 0 Then
ReDim Preserve sNew(t - 1) 'keep only the array filled elements
cl.Offset(0, 1).Value = Join(sNew, ",") 'put the value in the next column (for testing reason)
ReDim sNew(UBound(vSoft)): t = 0 'reinitialize the variables
End If
Next cl
End Sub
Im not sure exactly how to explain this in a google search so im not sure if anyone else has asked this.
I have a vba function that takes a range and turns it into a string of comma separated values.
It works like a charm.
Now i want it to only output the first 41 entries, switch down a row and output the next 41 entries in the range.
I cant quite wrap my head around it, it feels like a simple loop but i cant quite get there.
I found the csvrange macro online somewhere :)
Function csvRange(myRange As Range)
Dim csvRangeOutput
Dim entry As Variant
For Each entry In myRange
If Not IsEmpty(entry.Value) Then
csvRangeOutput = csvRangeOutput & entry.Value & ","
End If
Next
csvRange = Left(csvRangeOutput, Len(csvRangeOutput) - 1)
End Function
Input range would look like this
Desired output would look like this, one string located in column B each group of 41 values separated on a row, offsetting 1 down each time the function hits the next nr 42.
Something like this:
Option Explicit
Public Sub test()
Debug.Print csvRange(Selection, 41)
End Sub
Public Function csvRange(ByVal myRange As Range, ByVal Columns As Long) As String
Dim csvRangeOutput
Dim iCol As Long
Dim Entry As Variant
For Each Entry In myRange
If Not IsEmpty(Entry.Value) Then
iCol = iCol + 1
csvRangeOutput = csvRangeOutput & Entry.Value
If iCol = Columns Then
csvRangeOutput = csvRangeOutput & vbCrLf
iCol = 0
Else
csvRangeOutput = csvRangeOutput & ","
End If
End If
Next
csvRange = Left$(csvRangeOutput, Len(csvRangeOutput) - 1)
End Function
will turn this data
into comma separated values with 41 columns
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41
42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82
83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123
124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140
Alternative
Public Sub Convert()
Const ColCount As Long = 41
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Sheet1")
Dim LastRow As Long
LastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
Dim iRow As Long
For iRow = 1 To LastRow Step ColCount
ws.Cells(iRow \ ColCount + 1, "B").Value = "'" & Join((WorksheetFunction.Transpose(ws.Range("A" & iRow).Resize(RowSize:=IIf(iRow + ColCount - 1 > LastRow, WorksheetFunction.Max(LastRow Mod ColCount, 2), ColCount)).Value)), ",")
Next iRow
End Sub
Please, test the next code. It will do what (I understood) you need, for as many records you have in column A:A. It should be fast, using arrays and working in memory. The single iteration is for the necessary number of range slices:
Private Sub testStringCSVArray()
Dim sh As Worksheet, arr, nrSlices As Long, LastRow As Long, rngF As Range
Dim rngStart As Range, i As Long, k As Long, h As Long, arrFin
Set sh = ActiveSheet
LastRow = sh.Range("A1").End(xlDown).row
LastRow = sh.Range("A" & rows.count).End(xlUp).row 'last row of A:A
arr = sh.Range("A1:A" & LastRow).Value 'put the range in an array
nrSlices = UBound(arr) \ 41 'determine the number of necessary slices
ReDim arrFin(nrSlices + 1)
Set rngStart = sh.Range("B" & UBound(arr) + 2) 'set the cell where the result to be returned
For i = 1 To nrSlices + 1
arrFin(h) = CStr(Join(Application.Transpose(Application.Index(arr, _
Evaluate("row(" & k + 1 & ":" & IIf(i <= nrSlices, 41 + k, UBound(arr)) & ")"), 1)), ","))
k = k + 41: h = h + 1
Next i
'Format the range where the processed data will be returned and drop the processed data array:
With rngStart.Resize(h, 1)
.NumberFormat = "#"
.Value = WorksheetFunction.Transpose(arrFin)
End With
End Sub
In order to avoid deleting of the already processed data, in case of whishing to run the code twice or more times, the processed data will be returned in column B:B, two rows down from the last cell in column A:A. If after testing, the code proves to be reliable and no need to run it one more time, Set rngStart = sh.Range("B" & UBound(arr) + 2) can be modified in Set rngStart = sh.Range("A" & UBound(arr) + 2).
Without preliminary formatting as text the area where the data will be dropped, Excel changes the NumberFormat in "scientific", when the comma delimited string contains (only) numbers of three digits each. It looks to consider the comma as a thousands separator...
I would like to change the color of certain text in the cells based on the values in another cells. I have tried using conditional formatting but it does not work since I only wanted to change the color of particular words in the cells. I have googled a few VBA codes as well but still could not find the right one. Is there any VBA Code to enable this?
As shown in the example below (see image), I want to highlight ONLY the dates in Column B and C that match the dates in Column G. The day should remain the same.
For information, the values in Column B and C are formatted as text and the values in G are formatted as date.
Before
and this is basically what I wish for.
After
I have modified code appropriately as per your requirement in the comment.
Sub Change_Text_Color()
Dim Find_Text, Cell, Cell_in_Col_G, LastCell_inColG As Range
Dim StartChar, CharLen, LastUsedRow_inRange, LastUsedRow_inColB, _
LastUsedRow_inColC As Integer
LastUsedRow_inColB = Sheet1.Cells(Rows.count, "B").End(xlUp).Row
LastUsedRow_inColC = Sheet1.Cells(Rows.count, "C").End(xlUp).Row
LastUsedRow_inRange = Application.WorksheetFunction. _
Max(LastUsedRow_inColB, LastUsedRow_inColC)
Set LastCell_inColG = Sheet1.Cells(Rows.count, "G").End(xlUp)
For Each Cell In Range(Sheet1.Cells(2, 2), Cells(LastUsedRow_inRange, 3))
For Each Cell_in_Col_G In Range(Sheet1.Cells(2, 7), LastCell_inColG)
CharLen = Len(Cell_in_Col_G.Text)
Set Find_Text = Cell.Find(what:=Cell_in_Col_G.Text)
If Not Find_Text Is Nothing Then
StartChar = InStr(Cell.Value, Cell_in_Col_G.Text)
With Cell.Characters(StartChar, CharLen)
.Font.Color = RGB(0, 255, 0)
End With
End If
Next
Next
End Sub
Please let me know your feedback on it.
Use Characters:
With Range("a1")
.Characters(Start:=1, Length:=4).Font.Color=0
.Characters(Start:=5, Length:=10.Font.Color=255
End With
colours the first four letters black and the next ten in red.
Ref:
https://learn.microsoft.com/en-us/office/vba/api/excel.characters
I find filtering works well in these scenarios. Assuming that the format of your sheet is as it is in your sample sheets, try the code below:
Sub MarkDatesInCells()
Dim oWS As Worksheet: Set oWS = ThisWorkbook.Worksheets("Sheet3") '<- Change to the sheet name
Dim iLRToHighlight As Long, iStartChar As Long, iC As Long, iLR As Long
Dim oHighlightRng As Range, oUpdateRng As Range, oRng As Range
Dim sColName As String
' Turn off updating
Application.ScreenUpdating = False
Application.EnableEvents = False
With oWS
' Clear autofilter if exists
If .AutoFilterMode Then .AutoFilterMode = False
' Loop through all values specified in column G
iLRToHighlight = .Range("G" & .Rows.Count).End(xlUp).Row
For Each oHighlightRng In .Range("G2:G" & iLRToHighlight)
' Loop through column B and C
For iC = 2 To 3
' Set autofilter based on the value in column G
.UsedRange.AutoFilter iC, "=*" & oHighlightRng.Value
' Loop through all visible rows
iLR = .Cells(.Rows.Count, iC).End(xlUp).Row
If iLR > 1 Then
sColName = Left(Replace(.Cells(1, iC).Address, "$", ""), 1)
Set oUpdateRng = .Range(sColName & "2:" & sColName & iLR).SpecialCells(xlCellTypeVisible)
' Update each cell text
For Each oRng In oUpdateRng
iStartChar = InStr(1, oRng.Value, "- ", vbTextCompare) + 2
oRng.Characters(Start:=iStartChar, Length:=Len(oHighlightRng.Value)).Font.Color = 255
Next
End If
.AutoFilterMode = False
Next
Next
End With
' Turn on updating
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub
EDIT
Based on your requirement to have this solution for a sheet with a table connected to a database, try the below code. I don't have a database that I can test the below code on so you might have to tinker with it a bit to get it right (i.e. the text that is highlight)
Sub MarkDatesInCellsInATable()
Dim oWS As Worksheet: Set oWS = ThisWorkbook.Worksheets("Sheet4") '<- Change to the sheet name
Dim iLRToHighlight As Long, iStartChar As Long, iC As Long, iLR As Long
Dim oHighlightRng As Range, oUpdateRng As Range, oRng As Range
Dim sColName As String
Dim oTable As ListObject: Set oTable = oWS.ListObjects("Table_ExceptionDetails.accdb") '<- Change to the table name
Application.ScreenUpdating = False
Application.EnableEvents = False
With oWS
' Reset autofilter
oTable.Range.AutoFilter
' Loop through all values specified in column G
iLRToHighlight = .Range("G" & .Rows.Count).End(xlUp).Row
For Each oHighlightRng In .Range("G2:G" & iLRToHighlight)
' Loop through column B and C
For iC = 2 To 3
' Set autofilter based on the value in column G
oTable.Range.AutoFilter iC, "=*" & oHighlightRng.Value & "*"
' Loop through all visible rows
iLR = .Cells(.Rows.Count, iC).End(xlUp).Row
If iLR > 1 Then
sColName = Left(Replace(.Cells(1, iC).Address, "$", ""), 1)
Set oUpdateRng = .Range(sColName & "2:" & sColName & iLR).SpecialCells(xlCellTypeVisible)
' Update each cell text
For Each oRng In oUpdateRng
iStartChar = InStr(1, oRng.Value, "- ", vbTextCompare) + 2
oRng.Characters(Start:=iStartChar, Length:=Len(oHighlightRng.Value)).Font.Color = 255
Next
End If
oTable.Range.AutoFilter
Next
Next
End With
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub
basically I need to split a cell that has a few values, seperated by a comma into more cells. Then i need to create the exact amount of the cells under the new cells to be able to transpose this range later to have a new table.
In the picture you can see an example of what I have and what I need. I needed to anonymyze the data. Also I have hundreds of rows that need to changed like the 2 in the example.
Ths is my current code:
Sub texttocolumns()
Dim rng As Range
Dim x As Integer
x = ActiveSheet.UsedRange.Rows.Count
For i = x - 2 To 1
Cells(2 + i, 8).texttocolumns _
Destination:=Cells(2 + i, 9), _
Comma:=True
k = Application.WorksheetFunction.CountA("A" & "2 + i"" & "":" & "AT1")
Cells(2 + i, 1).Rows(k).Insert
Next i
End Sub
I can't find my mistake at the moment, could someone please help me out? thanks!
Since the output result is posted to a different location the expensive task of inserting rows can be avoided.
Try this procedure, which also avoids working with the source range by generating from it two Arrays:
An array containing the fixed fields
An array containing the field that needs to be split
The Procedure:
Sub Range_Split_A_Field()
Dim wsTrg As Worksheet, rgOutput As Range
Dim aFld_1To5 As Variant, aFld_6 As Variant
Dim aFld As Variant
Dim lRow As Long, L As Long
lRow = 3
Set wsTrg = ThisWorkbook.Sheets("Sht(2)")
Application.Goto wsTrg.Cells(1), 1
With wsTrg.Cells(lRow, 1).CurrentRegion
Set rgOutput = .Rows(1).Offset(0, 10)
.Rows(1).Copy
rgOutput.PasteSpecial
Application.CutCopyMode = False
aFld_1To5 = .Offset(1, 0).Resize(-1 + .Rows.Count, 5).Value2
aFld_6 = .Offset(1, 5).Resize(-1 + .Rows.Count, 1).Value2
End With
lRow = 1
For L = 1 To UBound(aFld_1To5)
aFld = aFld_6(L, 1)
If aFld = vbNullString Then
rgOutput.Offset(lRow).Resize(1, 5).Value = WorksheetFunction.Index(aFld_1To5, L, 0)
rgOutput.Offset(lRow, 5).Resize(1, 1).Value = aFld
lRow = 1 + lRow
Else
aFld = Split(aFld, Chr(44))
aFld = WorksheetFunction.Transpose(aFld)
rgOutput.Offset(lRow).Resize(UBound(aFld), 5).Value = WorksheetFunction.Index(aFld_1To5, L, 0)
rgOutput.Offset(lRow, 5).Resize(UBound(aFld), 1).Value = aFld
lRow = lRow + UBound(aFld)
End If: Next
End Sub
Please see the following pages for a better understanding of the resources used:
Application.Goto Method (Excel)
With Statement
Range Object (Excel)
Chr Function
UBound Function
WorksheetFunction Object (Excel)
Would something like this work:
'A1 = A,B,C,D,E,F,G
'A2 = 1,2,3,4,5,6,7
'A3 = A!B!C!D!E!F!G
'Test procedure will result in:
'A - G in cells A1:A7
'1,2,3,4,5,6,7 in cell A8.
'A - G in cells A9:A15
Sub Test()
TextToColumns Sheet1.Range("A1")
TextToColumns Sheet1.Range("A9"), "!"
End Sub
Public Sub TextToColumns(Target As Range, Optional Delimiter As String = ",")
Dim rng As Range
Dim lCount As Long
Dim x As Long
'How many delimiters in target string?
lCount = Len(Target) - Len(Replace(Target, Delimiter, ""))
'Add the blank rows.
For x = 1 To lCount + 1
Target.Offset(1).Insert Shift:=xlDown, CopyOrigin:=xlFormatFromLeftOrAbove
Next x
'Split the string.
Target.TextToColumns Target, xlDelimited, xlTextQualifierNone, , , , , , True, Delimiter
'Use TRANSPOSE formula to paste to rows and then remove formula.
With Target.Offset(1).Resize(lCount + 1, 1)
.FormulaArray = "=TRANSPOSE(R" & Target.Row & "C:R" & Target.Row & "C" & lCount + 1 & ")"
.Value = .Value
End With
'Delete the original text string.
Target.EntireRow.Delete
End Sub
Edit:
To use from the Macro dialog box you could add this small procedure:
Public Sub Test()
Dim y As Long
y = ActiveSheet.UsedRange.Rows.Count
With ActiveSheet
For y = 5 To 1 Step -1
TextToColumns .Cells(y, 1)
Next y
End With
End Sub
Note: ActiveSheet.UsedRange.Rows.Count is a terrible way to find the last row.
See this thread: Error in finding last used cell in VBA