Excel VBA - Finding the beginning and end of coloured rows - excel

I am trying to create a code in Excel VBA, to locate the beginning (Cell Address) and the end (Cell Address) of coloured rows in a table. The table is a timeline(Horizontal axis- Dates, Vertical axis - General Text). The coloured rows all do not start in the first column, but start in different columns.
Any help?

How's this?
Sub findColoredRows()
Dim startCol As Integer, endCol As Integer, o As Integer
Dim ws As Worksheet
Dim i As Integer, k As Integer
Dim startRow As Long, endRow As Long
Dim cellColor As String, noColor As String
Dim cel As Range
noColor = -4142 ' this is the color index of NO coloring
k = 3
Set ws = ActiveSheet
With ws
startRow = .Cells(1, 3).End(xlDown).Row
startCol = .Cells(1, 3).Column
Do While startRow > 100 ' I assume your table starts before row 100. So, if there's no data before row 100, check next column
k = k + 1
startRow = .Cells(1, k).End(xlDown).Row
startCol = k
Loop
'Now, we have our starting row - get end row.
endRow = .Cells(startRow, k).End(xlDown).Row
endCol = .Cells(startRow, startCol).End(xlToRight).Column
Debug.Print "Start row: " & startRow & ", start column: " & startCol
' How many non colored cells is there in our range?
Dim noColorCells As Integer
For Each cel In .Range(.Cells(startRow, startCol), .Cells(endRow, endCol))
If cel.Interior.ColorIndex = noColor Then
noColorCells = noColorCells + 1
End If
Next cel
Debug.Print "There are " & noColorCells & " non colored cells."
.Cells(startRow - 1, endCol + 2).Value = "Start Date"
.Cells(startRow - 1, endCol + 3).Value = "End Date"
'reDim the array to fit the colored cells
ReDim tDates(1 To noColorCells + 1)
i = 1 'index starts at 1, so set this to 1
For k = startRow To endRow
For o = startCol To endCol
If .Cells(k, o).Interior.ColorIndex = noColor And .Cells(k, endCol + 2) = "" Then
.Cells(k, endCol + 2).Value = .Cells(k, o).Value
ElseIf .Cells(k, o).Interior.ColorIndex = noColor And .Cells(k, endCol + 2) Then
i = i + i
.Cells(k, endCol + 3).Value = .Cells(k, o).Value
End If
' i = i + 1
Next o
i = i + 1
Next k
End With
MsgBox ("Done!")
End Sub
This sub will find the addresses of any colored cells. If you can explain more what you mean by "locate the beginning and the end of coloured rows in a table." I can tweak this. Can you post an image of a sample table maybe?
Edit: Per discussion below, try this in case there's not always data in the table, but you want the columns of the colored cells:
Sub findColoredBGCells()
Dim startRow As Integer, endRow As Integer, i As Integer, k As Integer, startCol As Integer, endCol As Integer
Dim cellColor As String, noColor As String
Dim ws As Worksheet
Set ws = ActiveSheet
noColor = -4142
With ws
'Get the starting row
startRow = .Cells(1, 1).End(xlDown).Row
endRow = .Cells(startRow, 1).End(xlDown).Row
' Since we know where the names start and end (less ONE for the "Names" part), let's count how many names we have
Dim noNames As Integer
noNames = endRow - startRow
If Not IsEmpty(.Cells(1, 1)) Then ' Get the first used column with data
startCol = 1
ElseIf IsEmpty(.Cells(1, 1)) Then
startCol = .Cells(1, 1).End(xlToRight).Column
End If
endCol = .Cells(1, startCol).End(xlToRight).Column
'Now we have our range, let's use it to loop for blank cells, and add those to an array
Dim coloredCells() As Variant
ReDim coloredCells(1 To noNames, 2)
Dim rng As Range, cel As Range
Set rng = .Range(.Cells(startRow, startCol), .Cells(endRow, endCol))
'rng.Select
'Now, count how many cells are not blank background
Dim cnt As Integer, celRow As Integer, lastCelRow As Integer
i = 1
lastCelRow = 2
For Each cel In rng
cel.Select
celRow = cel.Row
If cel.Row <> lastCelRow Then 'This is so we can change the first dimension in the array
k = k + 1
coloredCells(k, 0) = .Cells(cel.Row, 1).Value
i = 1
' i = i + 1
End If
If cel.Interior.ColorIndex <> noColor Then
cnt = cnt + 1
If i > 2 Then i = 2 'Since it's only two dimensions we need, only go up to '1'
' ReDim Preserve coloredCells(noNames, i) 'resize the array to hold the new column
coloredCells(k, i) = .Cells(1, cel.Column).Value
i = i + 1
End If
lastCelRow = celRow
Next cel
For k = 1 To UBound(coloredCells)
Debug.Print coloredCells(k, 0) & " Start Date: " & coloredCells(k, 1) & ", end date: " & coloredCells(k, 2) & "."
.Cells(2 + k, 2).Value = coloredCells(k, 1)
.Cells(2 + k, 3).Value = coloredCells(k, 2)
Next k
End With
MsgBox ("Done!")
End Sub

Related

Horizontal Loop

I'm trying to write a macro that does the following:
Loop through each row in table1 starting with 1 to 50
Loop through each column of the respective row starting with 2 to 100
Concatenate for each row in column 1 all the single fields in column 2 to 100 with a "|" inbetween.
See my code below. I get an error for .Range.Cells(lRow, 1) = .Range.Cells(lRow, 1) & "|" & .Range.Cells(lRow, lCol)
Option Explicit
Sub horizontal_loop()
Dim lRow, lCol As Long
With Worksheets("table1")
For lRow = 1 To 50
For lCol = 2 To 100
.Range.Cells(lRow, 1) = .Range.Cells(lRow, 1) & "|" & .Range.Cells(lRow, lCol)
Next lCol
Next lRow
End With
End Sub
There is no need to use Range if you are using Cells.
.Cells(lRow, 1) = .Cells(lRow, 1) & "|" & .Cells(lRow, lCol)
Alternatively with single loop
Sub horizontal_loop()
Const STARTROW = 1
Const ENDROW = 50
Const STARTCOL = 2
Const ENDCOL = 100
Dim lRow As Long, ar, wsf As WorksheetFunction
Set wsf = WorksheetFunction
With Worksheets("table1")
For lRow = STARTROW To ENDROW
ar = .Cells(lRow, STARTCOL).Resize(, ENDCOL - STARTCOL + 1)
ar = wsf.Transpose(wsf.Transpose(ar))
.Cells(lRow, 1) = Join(ar, "|")
Next lRow
End With
End Sub

Max score not getting correct results

Sub GetMaxl()
Dim nMonths As Integer, nRegions As Integer
Dim iRow As Integer, iCol As Integer
Dim regionMax As Double, monthMax As Double
Dim maxScore As Integer
With wsSales.Range("A3")
nMonths = Range(.Offset(0, 1), .Offset(0, 1).End(xlToRight)) _
.Columns.Count
nRegions = Range(.Offset(1, 0), .Offset(1, 0).End(xlDown)) _
.Rows.Count
.Offset(0, nMonths + 1) = "MaxScore"
.Offset(nRegions + 1, 0) = "MaxScore"
For iRow = 1 To nRegions
regionMax = 0
For iCol = 1 To nMonths
regionMax = WorksheetFunction.Max(Range(.Offset(iRow, iCol), .End(xlToRight)))
Debug.Print , regionMax
Next iCol
.Offset(iRow, nMonths + 1) = regionMax
Next iRow
For iCol = 1 To nMonths
monthMax = 0
For iRow = 1 To nRegions
monthMax = WorksheetFunction.Max(Range(.Offset(iRow, iCol), .End(xlDown)))
Next iRow
.Offset(nRegions + 1, iCol) = monthMax
Next iCol
End With
End Sub
I am trying to get the max score against columns and rows in a loop but I am not getting correct results.
It can be done easier and faster
Option Explicit
Sub GetMaxl()
Dim Rng As Range, rng_total_right As Range, rng_total_down As Range
Set Rng = wsSales.Range("A3").CurrentRegion
'get the ranges for max column and row at right and down
Set rng_total_right = Intersect(Rng.Offset(, 1), wsSales.Columns(Rng.Column + Rng.Columns.Count))
Set rng_total_down = Intersect(Rng.Offset(1, 0), wsSales.Rows(Rng.Row + Rng.Rows.Count))
With rng_total_right ' max column
.NumberFormat = "0"
.FormulaR1C1 = "=MAX(RC[-" & Rng.Columns.Count - 1 & "]:RC[-1])" ' insert formulas and calculate them
.Value = .Value 'replace formulas by values
.Cells(1) = "MaxScore" 'make header
End With
With rng_total_down 'max row
.NumberFormat = "0"
.FormulaR1C1 = "=MAX(R[-" & Rng.Rows.Count - 1 & "]C:R[-1]C)"
.Value = .Value
.Cells(1) = "MaxScore"
End With
End Sub

Auto fill increment number in dynamic data ranges

I am stuck in my vba code and seems I setup a loop wrong. Really appreciate for some advices! Thank you very much!!
Sub code()
Dim lastRow As Long
Dim k As Integer
Dim rowPtr As Long
lastRow = Cells(Rows.Count, 1).End(xlUp).Row
For rowPtr = 2 To lastRow
If Range("A" & rowPtr + 1) <> Range("A" & rowPtr) Then
k = 1
Range("B" & rowPtr) = k
Else
If Range("A" & rowPtr + 1) = Range("A" & rowPtr) Then
Range("B" & rowPtr) = k
End If
k = k + 1
End If
Next
End Sub
Above is my code and now my VBA result is like this:
screenshot
Column C is my ideal result of the code
Rank Reps (Repeating Values)
Adjust the values in the constants section.
Note that Range("A" & rowPtr) is the same as Cells(rowPtr, "A") or Cells(rowPtr, 1), and Range("A" & Rows.Count) is the same as Cells(Rows.Count, "A") or Cells(Rows.Count, 1).
Option Explicit
Sub rankReps()
Const FirstRow As Long = 2
Const sCol As String = "A"
Const dCol As String = "B"
Dim cOffset As Long: cOffset = Columns(dCol).Column - Columns(sCol).Column
Dim LastRow As Long: LastRow = Range(sCol & Rows.Count).End(xlUp).Row
If LastRow < FirstRow Then
MsgBox "No data", vbCritical, "No Data"
Exit Sub
End If
' Write first.
Range(sCol & FirstRow).Offset(, cOffset).Value = 1
' Write remainder.
If LastRow > FirstRow Then
Dim cCell As Range ' Current Cell
Dim r As Long ' Row Counter
Dim rk As Long: rk = 1 ' Rank Counter
For r = FirstRow + 1 To LastRow ' +1: the first is already written
Set cCell = Range(sCol & r)
If cCell.Value = cCell.Offset(-1).Value Then
rk = rk + 1
Else
rk = 1
End If
cCell.Offset(, cOffset).Value = rk
Next r
End If
End Sub
Public Sub UpdateRankings(ByVal ws As Worksheet)
' Adjust as necessary.
Const firstRow As Long = 3
Const colGroupId As Long = 1
Const colRanking As Long = 6
Dim row As Long
With ws
' First value defaults to 1.
row = firstRow
.Cells(row, colRanking).Value = 1
' Remaining rows.
row = row + 1
Do While .Cells(row, colGroupId).Value <> ""
' If group id is the same as the previous row, increment rank.
If .Cells(row, colGroupId).Value = .Cells(row - 1, colGroupId).Value Then
.Cells(row, colRanking).Value = .Cells(row - 1, colRanking).Value + 1
' If group id has changed, reset rank to 1.
Else
.Cells(row, colRanking).Value = 1
End If
' Next row.
row = row + 1
Loop
End With
End Sub
Please, try the next way:
Sub Countcode()
Dim lastRow As Long, k As Long, rowPtr As Long
lastRow = cells(rows.count, 1).End(xlUp).row
k = 1
For rowPtr = 2 To lastRow
If Range("A" & rowPtr) = Range("A" & rowPtr + 1) Then
Range("B" & rowPtr) = k: k = k + 1
Else
If Range("A" & rowPtr) = Range("A" & rowPtr - 1) Then
Range("B" & rowPtr) = k: k=1
Else
k = 1
Range("B" & rowPtr) = k
End If
End If
Next
End Sub
One approach is:
Sub numberIt2()
Dim cl As Range, equal As Integer ' equal initial value is 0
Set cl = Range("A1")
Do While cl <> ""
cl.Offset(0, 1) = equal + 1
Set cl = cl.Offset(1)
equal = IIf(cl = cl.Offset(-1), equal + 1, 0)
Loop
End Sub

How to check for equal values in cells with a for loop?

I want to check if the text value in a cell is the same as in the cell below with a for loop.
If the value in Cell(1) and Cell(2) does not match I want the value from Cell(3) written in Cell(4).
I get an error
"Overflow (Error 6)"
Dim i As Integer
For i = 1 To Rows.Count
If Cells(2 + i,21) = Cells(3 + i,21) Then
i = i + 1
Else
a = Cells(3 + i, 1)
j = j + 1
Cells(228 + j, 3) = a
End If
Next i
End Sub
I have a production output and a timeline from 6 am to 12 am and I want to create a timetable as seen below.
Screenshot:
You could use
Option Explicit
Sub test()
Dim LastRowA As Long, i As Long, j As Long, LastRowW As Long
Dim StartTime As Date, EndTime As Date, strOutPut
j = 0
With ThisWorkbook.Worksheets("Sheet1")
LastRowA = .Cells(.Rows.Count, "A").End(xlUp).Row
For i = 3 To LastRowA
If i > j - 1 Then
StartTime = .Range("A" & i).Value
strOutPut = .Range("U" & i).Value
For j = i + 1 To LastRowA + 1
If strOutPut <> .Range("U" & j).Value Then
EndTime = .Range("A" & j - 1).Value
LastRow = .Cells(.Rows.Count, "W").End(xlUp).Row
.Range("W" & LastRow + 1).Value = StartTime
.Range("X" & LastRow + 1).Value = EndTime
.Range("Y" & LastRow + 1).Value = strOutPut
Exit For
End If
Next j
End If
Next i
End With
End Sub
Result
Here I'm using a dictionary which will store every time for every product comma separated, so later will split that and take the first and last occurrence:
Sub TimeTable()
'Declare an array variable to store the data
'change MySheet for your sheet name
arr = ThisWorkbook.Sheets("MySheet").UsedRange.Value 'this will store the whole worksheet, the used area.
'Declare a dictionary object
Dim Products As Object: Set Products = CreateObject("Scripting.Dictionary")
'Loop through the array
Dim i As Long
For i = 3 To UBound(arr) 'start from row 3 because of your screenshoot
If arr(i, 21) = vbNullString Then GoTo NextRow 'if column U is empty won't add anything
If Not Products.Exists(arr(i, 21)) Then '21 is the column index for column U
Products.Add arr(i, 21), arr(i, 1)
Else
Products(arr(i, 21)) = arr(i, 21) & "," & arr(i, 1)
End If
NextRow:
Next i
Erase arr
'Redim the array to fit your final data, 4 columns and as many rows as products
ReDim arr(1 To Products.Count + 1, 1 To 4)
'Insert the headers
arr(1, 1) = "Time"
arr(1, 4) = "Product / Error"
'Now loop through the dictionary
Dim Key As Variant, MySplit As Variant
i = 2
For Each Key In Products.Keys
MySplit = Split(Products(Key), ",")
arr(i, 1) = MySplit(LBound(MySplit))
arr(i, 2) = "-"
arr(i, 3) = MySplit(UBound(MySplit))
arr(i, 4) = Key
i = i + 1
Next Key
'I don't know where are you going to paste your data, so I'm making a new worksheet at the end of your workbook
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets.Add(After:=ThisWorkbook.Sheets(ThisWorkbook.Sheets.Count))
With ws
.Range("A1").Resize(UBound(arr), UBound(arr, 2)).Value = arr
.Range("A1:C1").Merge
End With
End Sub

Excel VBA, capture first "start" value and last "end" value per group

Using Excel VBA, I'm trying to capture the first value in a column "Start" and the last value in a column "End", per group.
Data is already sorted.
Example:
I want to capture the first value for Start_open and the last value for Start_end per company.
So for Company A code should put B2 in Start_Open and put C5 in Start_end.
Capturing the last value works fine using this code:
Sub First_last()
Dim i, j As Integer
Dim LastRow, LastCol As Long
LastRow = Cells(Rows.Count, "A").End(xlUp).Row
LastCol = Cells(1, Columns.Count).End(xlToLeft).Column
For i = 2 To LastRow
If Cells(i + 1, "A").Value <> Cells(i, "A").Value Then
MsgBox i
Cells(j + 2, "E").Value = Cells(i, "C").Value
j = j + 1
End If
Next
End Sub
What I'm struggling with is capturing Start_open per group.
I think I need to use above condition and use a counter to capture Start_open per group but I can't find the right code.
Please advise, thanks!
You can use variables a and b to find the start and end of each section:
Dim a as Long, b as Long, i as Long, lr as Long
lr = cells(rows.count,1).end(xlup).row
For i = 2 to lr
If cells(i,1).value = cells(i+1,1).value then
If a = 0 then
a = i + 1
End If
Else
If a > 0 AND b = 0 then
b = i + 1
End If
End If
If b > 0 AND a > 0 Then
'perform max(range(cells(a,2),cells(b,2))), etc.
a = 0 'resets for next grouping
b = 0 'resets for next grouping
End If
Next i
a = 0
b = 0
To add another method into the mix.
Sub x()
Dim r As Range, oDic As Object, r1 As Range, r2 As Range, r3 As Range, v(), i As Long
Set oDic = CreateObject("Scripting.Dictionary")
Set r = Range("A2", Range("A" & Rows.Count).End(xlUp))
ReDim v(1 To r.Count, 1 To 3)
For Each r3 In r
If Not oDic.Exists(r3.Text) Then
Set r1 = r.Find(What:=r3, After:=r(r.Count), LookAt:=xlWhole, _
SearchDirection:=xlNext, MatchCase:=False, SearchFormat:=False)
Set r2 = r.Find(r3, r(1), , , , xlPrevious)
i = i + 1
v(i, 1) = r3
v(i, 2) = r1.Offset(, 1).Value
v(i, 3) = r2.Offset(, 2).Value
oDic.Add r3.Text, Nothing
End If
Next r3
Range("D2").Resize(oDic.Count, 3) = v
End Sub
This will do what you want:
Sub First_Last()
With ActiveSheet
Dim LastRow As Long
LastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
Dim numUnique
numUnique = .Evaluate("SUM(1/COUNTIF(A:A,A2:A" & LastRow & "))")
Dim outarr As Variant
ReDim outarr(1 To numUnique, 1 To 2)
Dim clmc As Variant
clmc = .Range(.Cells(1, 3), .Cells(LastRow, 3)).Value
Dim clmb As Variant
clmb = .Range(.Cells(1, 2), .Cells(LastRow, 2)).Value
Dim j As Long
j = 1
Dim i As Long
For i = 2 To LastRow
outarr(j, 1) = clmb(i, 1)
Dim k As Long
k = .Evaluate("AGGREGATE(14,6,ROW(A2:A" & LastRow & ")/(A2:A" & LastRow & " = " & .Cells(i, 1).Address & "),1)")
outarr(j, 2) = clmc(k, 1)
j = j + 1
i = k
Next i
.Range("D2").Resize(UBound(outarr, 1), UBound(outarr, 2)).Value = outarr
End With
End Sub

Resources