I have a spreadsheet with lines connecting places. Each place has a corresponding number, and is placed in a region (area).
I want a list Node with corresponding Name, and Area. Since some data is missing, I make the assumption that a line going from PARIS will also end up in PARIS.
From To From To AreaF AreaT
51191 51190 BARUM OVERL PARIS PARIS
51191 60000 BARUM BARDU PARIS 0
51059 51074 FOLLO DYRLO #N/A #N/A
51059 51070 FOLLO DYRLO #N/A BERG
51059 50795 FOLLO NYSTU #N/A #N/A
51059 59001 FOLLO VEVEL #N/A #N/A
51059 50362 FOLLO MYRVO #N/A #N/A
51059 50363 FOLLO MYRVO #N/A #N/A
51059 50812 FOLLO NORDB #N/A #N/A
What I want:
Node Name Area
50362 MYRVO BERG
50363 MYRVO BERG
50795 NYSTU BERG
50812 NORDB BERG
51059 FOLLO BERG
51070 DYRLO BERG
51074 DYRLO BERG
51190 OVERL PARIS
51191 BARUM PARIS
59001 VEVEL BERG
60000 BARDU PARIS
Any tips as to how this can be done in Excel? Any useful functions that might come in handy?
The best logic I can come up with is:
(E.g. For row 3)
Check if AreaF contains a valid Area name, not #N/A or 0 (False)
Check if AreaT contains a valid Area name (False)
Check if other rows where column A is 51059 contain valie Area names (True, row 4)
Use that Area in the new list
My problem is mainly point 3. I can't figure out what functions etc. I must use to accomplish this.
This seems to work for point 1 and 2:
=IF(ISNA(F2);IF(ISNA(G2);$M$2;IF(G2=0;$M$2;G2));IF(F2=0;IF(ISNA(G2);$M$2;IF(G2=0;$M$2;G2));F2))
Thanks!
Here is a VBA method that loops through the range and essentially is doing the eval with brut force.
I'm sure it can be cleaned up and made more efficient. Should get you started though.
Sub NodeList()
Dim sheet As Worksheet
Set sheet = ActiveWorkbook.Sheets("Sheet1")
'First Column
Dim rngA As Range
Set rngA = [A2:A10]
Dim datA As Variant
datA = rngA
Dim i As Long
Dim j As Long
'Results
Dim myarray()
ReDim myarray(100, 100)
Dim datR As Variant
Dim store As Boolean
Dim duplicate As Boolean
store = False
duplicate = False
Dim cntr As Integer
cntr = 0
'Range Column loop
For i = LBound(datA, 1) To UBound(datA, 1)
'Find first result
If IsEmpty(myarray(0, 0)) Then
'Is Col E valid?
If Not IsError(rngA(i, 5)) Then
If rngA(i, 5) <> 0 Or rngA(i, 5) <> "#N/A" Or Not IsEmpty(rngA(i, 5)) Then
'Col E is valid
store = True
col = 5
End If
End If
'Is Col F valid?
If store = False And Not IsError(rngA(i, 6)) Then
If rngA(i, 6) <> 0 Or rngA(i, 6) <> "#N/A" Or Not IsEmpty(rngA(i, 6)) Then
'Col E is valid
store = True
col = 6
End If
End If
'Store value to results
If store = True Then
myarray(0, 0) = rngA(i, 1)
myarray(0, 1) = rngA(i, col)
store = False
End If
Else
'Results has at least one value check for duplicate
'Loop thru results
For k = LBound(myarray) To UBound(myarray)
If datA(i, 1) = myarray(k, 0) Then
' duplicate found
duplicate = True
Exit For
End If
Next
If duplicate = False Then
'validate data
If Not IsError(rngA(i, 5)) Then
If rngA(i, 5) <> 0 Or rngA(i, 5) <> "#N/A" Or Not IsEmpty(rngA(i, 5)) Then
'Col E is valid
store = True
col = 5
End If
End If
'Is Col F valid?
If store = False And Not IsError(rngA(i, 6)) Then
If rngA(i, 6) <> 0 Or rngA(i, 6) <> "#N/A" Or Not IsEmpty(rngA(i, 6)) Then
'Col E is valid
store = True
col = 6
End If
End If
'Store value to results
If store = True Then
cntr = cntr + 1
myarray(cntr, 0) = rngA(i, 1)
myarray(cntr, 1) = rngA(i, col)
store = False
End If
End If
duplicate = False
End If
Next
Dim rngB As Range
Set rngB = [B2:B10]
datA = rngB
'Range Column loop
For i = LBound(datA, 1) To UBound(datA, 1)
'Find first result
If IsEmpty(myarray(0, 0)) Then
'Is Col E valid?
If Not IsError(rngA(i, 5)) Then
If rngA(i, 5) <> 0 Or rngA(i, 5) <> "#N/A" Or Not IsEmpty(rngA(i, 5)) Then
'Col E is valid
store = True
col = 5
End If
End If
'Is Col F valid?
If store = False And Not IsError(rngA(i, 6)) Then
If rngA(i, 6) <> 0 Or rngA(i, 6) <> "#N/A" Or Not IsEmpty(rngA(i, 6)) Then
'Col E is valid
store = True
col = 6
End If
End If
'Store value to results
If store = True Then
myarray(0, 0) = rngA(i, 2)
myarray(0, 1) = rngA(i, col)
store = False
End If
Else
'Results has at least one value check for duplicate
'Loop thru results
For k = LBound(myarray) To UBound(myarray)
If datA(i, 1) = myarray(k, 0) Then
' duplicate found
duplicate = True
Exit For
End If
Next
If duplicate = False Then
'validate data
If Not IsError(rngA(i, 5)) Then
If rngA(i, 5) <> 0 Or rngA(i, 5) <> "#N/A" Or Not IsEmpty(rngA(i, 5)) Then
'Col E is valid
store = True
col = 5
End If
End If
'Is Col F valid?
If store = False And Not IsError(rngA(i, 6)) Then
If rngA(i, 6) <> 0 Or rngA(i, 6) <> "#N/A" Or Not IsEmpty(rngA(i, 6)) Then
'Col E is valid
store = True
col = 6
End If
End If
If store = False Then
'Both are invalid
'look in col 'A' and reloop thru value to find another match
For p = LBound(myarray) To UBound(myarray)
If rngA(i, 1) = myarray(p, 0) Then
cntr = cntr + 1
myarray(cntr, 0) = rngA(i, 2)
myarray(cntr, 1) = myarray(p, 1)
store = False
Exit For
End If
Next
End If
'Store value to results
If store = True Then
cntr = cntr + 1
myarray(cntr, 0) = rngA(i, 2)
myarray(cntr, 1) = rngA(i, col)
store = False
End If
End If
duplicate = False
End If
Next
For i = LBound(myarray) To UBound(myarray)
Range("H" & i + 1).Value = myarray(i, 0)
Range("I" & i + 1).Value = myarray(i, 1)
Next
End Sub
Output looks like this:
I didn't add in the name but you can do that by modifying the array.
For the first question, "Any tips as to how this can be done in Excel? Any useful functions that might come in handy?":
Your logic would work fine. However, instead of a long formula in each cell, you may want to consider coding this in VBA. The format would be something similar to:
Go through all nodes
Loop through the nodes with a For...Next loop.
If the node hasn't been seen yet, add it to a list.
Use the Range.Find method, for example, to check if the node has been found already. (See here for a good discussion on .Find vs. COUNTIF, etc.)
Do your calculations on that node.
Check if Area T or Area F contains a valid name.
Use that area for the node.
To answer your second question about what functions could be used for point 3: For something not using VBA, you might consider the VLOOKUP function, as well as COUNTIF, as good functions to keep in mind. But again, See here for a good discussion on .Find vs. COUNTIF, etc.
Related
I'm trying to clean up raw data exported from an online database.
There can be up to five columns. If all cells in a row have a value of 0, I want to delete that row.
When the user exports the data, they can choose to exclude columns, and the columns can be in any order.
For example, if the data contains only two of the possible five columns, I want to check just those two for 0s.
Could do a a big loop looking at every row and seeing if all 5 columns in that row are blank
Sub test()
Dim sh As Worksheet
Set sh = ThisWorkbook.Sheets("sheetname")
Dim LastRow As Integer
LastRow = sh.UsedRange.Rows.Count - 1
For i = 1 To LastRow
If (sh.Cells(i, 1).Value = "" And sh.Cells(i, 2).Value = "" And sh.Cells(i, 3).Value = "" And _
sh.Cells(i, 4).Value = "" And sh.Cells(i, 5).Value = "") Then
sh.Cells(i, 1).EntireRow.Delete
i = i - 1
Dim newLastRow As Integer
newLastRow = sh.UsedRange.Rows.Count - 1
If i = newLastRow Then
Exit For
End If
End If
Next i
MsgBox ("Done")
End Sub
#kyle campbell, thank you for your input! It didn't quite get me there, but it did get my wheels turning. Here is the solution I came up with, if anyone's curious:
I set a variable to represent the column number for each of the 5 possible columns using Range.Find. If the Find came up with nothing, I set the variable to 49, since the maximum number of columns this report can have is 48.
Then I did a nested If to test if the value in each cell was either 0 or null (because if the column number is 49, there won't be any data there). If all Ifs were true, I deleted the row. I also added a counter and message box, just to make sure this worked.
Sub DeleteRows()
Dim O As Long
Dim E As Long
Dim H As Long
Dim B As Long
Dim P As Long
lRow = Range("A1").CurrentRegion.Rows.Count
If Range("1:1").Find("SUM(OBLIGATIONS)") Is Nothing Then
O = 49
Else
O = Range("1:1").Find("SUM(OBLIGATIONS)").Column
End If
If Range("1:1").Find("SUM(EXPENDITURES)") Is Nothing Then
E = 49
Else
E = Range("1:1").Find("SUM(EXPENDITURES)").Column
End If
If Range("1:1").Find("SUM(HOURS)") Is Nothing Then
H = 49
Else
H = Range("1:1").Find("SUM(HOURS)").Column
End If
If Range("1:1").Find("SUM(BUDGET_RESOURCES)") Is Nothing Then
B = 49
Else
B = Range("1:1").Find("SUM(BUDGET_RESOURCES)").Column
End If
If Range("1:1").Find("SUM(PRIOR_YEAR_RECOVERY)") Is Nothing Then
P = 49
Else
P = Range("1:1").Find("SUM(PRIOR_YEAR_RECOVERY)").Column
End If
Dim j As Integer
j = 0
For i = lRow To 2 Step -1
If Cells(i, O) = 0 Or Cells(i, O) = "" Then
If Cells(i, E) = 0 Or Cells(i, E) = "" Then
If Cells(i, H) = 0 Or Cells(i, H) = "" Then
If Cells(i, B) = 0 Or Cells(i, B) = "" Then
If Cells(i, P) = 0 Or Cells(i, P) = "" Then
Rows(i).Delete
j = j + 1
End If
End If
End If
End If
End If
Next i
MsgBox "Macro complete, " & j & " lines deleted."
End Sub
I have a ComboBox that has a Value of "ConcretePad". I also have a Range named "ConcretePad".
i am trying to Select Range based off of ComboBox Value.
***Private Sub CatagoryCB_Change()
Dim rg As String
rg = (CatagoryCB.Value)
Worksheets("Data").Select
If (CatagoryCB.Value = "") Then
GoTo Line2
ElseIf (CatagoryCB.Value <> "") Then
Range(rg).Select
Line2:
End If
End Sub***
Trying to make rg represent the Value of CatagoryCB.Value, which i did but when i put it in the cell reference for range i get an error
You're probably looking for something like this (provided you're using a ListFillRange):
Private Sub CatagoryCB_Change()
If (CatagoryCB.ListIndex <> -1) Then
Worksheets("Data").Select
Range(CatagoryCB.ListFillRange).Cells(CatagoryCB.ListIndex + 1, 1).Select
End If
End Sub
This just grabs the ListFillRange, navigates to the ListIndex which is in sync with it and selects it.
CatagoryCB.ListIndex will return the index of the selected item in the list.
If a value that isn't in the list is selected, it will return -1.
So, for example, if I set my ListFillRange to A1:A3 and select the first option, I will do a Range("A1:A3").Cells(1, 1).Select because the ListIndex of the selected item is 0 (first item) and .Cells(0 + 1, 1) = .Cells(1, 1).
If you're populating the ComboBox manually, you'd need to give it the range you want to link to or perform a find operation.
It's hard to tell from your code.
I figured it out. My (CatagoryCB.Value) was not equal to my Range Name. This is the code i was able to produce to add a part to my datasheet on my current worksheet. This also adds the new row to my range
Dim i As String
Dim c As Integer
Dim g As Integer
i = CatagoryCB.Value
Worksheets("Data").Select
If i = "" Then
GoTo Line2
ElseIf i <> "" Then
Range(i).Select
c = Range(i).Count
Range(i).Activate
ActiveCell.Offset(c, 0).Select
g = ActiveCell.Row
Worksheets("Data").Rows(g).Insert
Range(i).Resize(c + 1).Name = i
Cells(g, 1).FormulaR1C1 = Cells(g - 1, 1).FormulaR1C1
Cells(g, 3) = (Part_NumberTB.Value)
Cells(g, 4) = (VendorCB.Value)
Cells(g, 5) = (DescriptionTB.Value)
Cells(g, 7) = (CostTB.Value)
Cells(g, 8) = (CostTB.Value * 1.35)
Cells(g, 9) = (CostTB.Value * 1.35)
Cells(g, 10).FormulaR1C1 = Cells(g - 1, 10).FormulaR1C1
Cells(g, 11).FormulaR1C1 = Cells(g - 1, 11).FormulaR1C1
Line2:
End If
I have the following dataset
Key ID Status 1 Status 2 Order ID
1 A1 FALSE TRUE 1234-USF-0025
1 A1 FALSE TRUE 1234-USF-0026
1 A1 FALSE TRUE 1234-USF-0027
2 A1 TRUE TRUE 1234-USF-0025
2 A1 TRUE TRUE 1234-USF-0026
2 A1 TRUE TRUE 1234-USF-0027
3 A1 FALSE TRUE 1234-USF-0025
3 A1 FALSE TRUE 1234-USF-0026
3 A1 FALSE TRUE 1234-USF-0027
4 A2 TRUE TRUE 1234-USF-0028
4 A2 TRUE TRUE 1234-USF-0029
4 A2 TRUE TRUE 1234-USF-0030
5 A3 TRUE TRUE 1234-USF-0031
5 A3 TRUE TRUE 1234-USF-0032
5 A3 TRUE TRUE 1234-USF-0033
6 A4 TRUE TRUE 1234-USF-0034
6 A4 TRUE TRUE 1234-USF-0035
6 A4 TRUE TRUE 1234-USF-0036
I need the following
Order ID ID TRUE FALSE
1234-USF-0025 A1 2 1,3
1234-USF-0026 A1 2 1,3
1234-USF-0027 A1 2 1,3
1234-USF-0028 A2 4
1234-USF-0029 A2 4
1234-USF-0030 A2 4
1234-USF-0031 A3 5
1234-USF-0032 A3 5
1234-USF-0033 A3 5
1234-USF-0034 A4 6
1234-USF-0035 A4 6
1234-USF-0036 A4 6
In the second table (the one I need), each Order ID is listed next to the corresponding ID. Although A1 is listed 9 times in the original dataset, there are only 3 unique Order IDs in total for A1. However, A1 is also associated with 3 different Keys.
The goal is to concatenate the Keys for each Order ID and ID combination, where both Status 1 and Status 2 are TRUE and list them in the TRUE column. For those Order ID and ID combinations where at least one Status is FALSE, the Keys should be listed under the FALSE column.
What I've tried
I tried starting with just the TRUE column, using INDEX-MATCH as an array formula, and although I know the below formula would not work for my desired end goal, I was trying to start small and build upon the formula. Unfortunately, my knowledge of arrays is limited, I'm not sure how to proceed because I don't understand why it returns what it does or how to reach my goal from this point.
=INDEX($C$2:$C$19,MATCH(1,($H2 = $B$2:$B$19) * ($G2 = $E$2:$E$19)))
Next I tried to break the pieces apart in the original dataset, but got stuck on how to proceed. I think this is the easier solution, but I can't figure out how to concatenate based on the required criteria.
TRUE: =IF(AND($C2=TRUE,$D2=TRUE),$A2,"")
FALSE: =IF(OR($C2<>TRUE,$D2<>TRUE),$A2,"")
Notes:
An ID is associated with at least one Key, but can have more
Order ID can repeat for the same ID but only for different Keys for that ID.
I am open to a VBA, Python or R based solution as well, but not sure how to even start a script for this task, so I've been focusing on Excel.
This is kinda a verbose solution and assumes your data is exactly as you posted (and also on sheet1), but it works (I think). You'll also need to create a second sheet for the output data. Let me know if you're not sure where to post this code/how to run it.
Sub DoStuff()
'Initialize the output sheet
Sheet2.Cells.Clear
Sheet2.Cells(1, 1) = "Order ID"
Sheet2.Cells(1, 2) = "ID"
Sheet2.Cells(1, 3) = "TRUE"
Sheet2.Cells(1, 4) = "FALSE"
newRow = 2
'Loop through the first sheet and remove duplicates
lastRow = Sheet1.Range("E:E").Cells.SpecialCells(xlCellTypeConstants).Count
For i = 2 To lastRow
exists = False
For j = 2 To newRow
If Sheet1.Cells(i, 5).Value = Sheet2.Cells(j, 1).Value Then
exists = True
Exit For
End If
Next
If exists = False Then
Sheet2.Cells(newRow, 1) = Sheet1.Cells(i, 5).Value
Sheet2.Cells(newRow, 2) = Sheet1.Cells(i, 2).Value
'Populate the true and false columns
For k = 2 To lastRow
If Sheet1.Cells(k, 5).Value = Sheet1.Cells(i, 5).Value Then
If Sheet1.Cells(k, 3).Value = True And Sheet1.Cells(k, 4).Value = True Then
Sheet2.Cells(newRow, 3) = Sheet2.Cells(newRow, 3).Value & Sheet1.Cells(k, 1).Value & ", "
Else
Sheet2.Cells(newRow, 4) = Sheet2.Cells(newRow, 4).Value & Sheet1.Cells(k, 1).Value & ", "
End If
End If
Next
'Remove extra characters, if there are any
If Sheet2.Cells(newRow, 3).Value <> "" Then
Sheet2.Cells(newRow, 3).Value = Left(Sheet2.Cells(newRow, 3).Value, Len(Sheet2.Cells(newRow, 3).Value) - 2)
End If
If Sheet2.Cells(newRow, 4).Value <> "" Then
Sheet2.Cells(newRow, 4).Value = Left(Sheet2.Cells(newRow, 4).Value, Len(Sheet2.Cells(newRow, 4).Value) - 2)
End If
newRow = newRow + 1
End If
Next
End Sub
Results using your data as posted:
I used a dictionary and a Class module to help gather and transform the data.
It also has the advantage of being a bit easier to follow and maintain since the named parameters are more or less obvious.
I also "did the work" in a VBA array as, with any sizeable database, the execution speed will be considerably faster.
It should be obvious within the code where to define the worksheets and ranges you want to use for your source data and results
Regular Module
Option Explicit
'Set reference to Microsoft Scripting Runtime
Sub orgOrders()
Dim wsSrc As Worksheet, wsRes As Worksheet, rRes As Range
Dim vSrc As Variant, vRes As Variant
Dim dOrds As Dictionary, cOrd As cOrder
Dim I As Long, V As Variant
Dim sKey As String
'set source and result worksheet and range
Set wsSrc = Worksheets("Sheet2")
Set wsRes = Worksheets("Sheet2")
Set rRes = wsRes.Cells(1, 10)
'read source data into array
With wsSrc
vSrc = .Range(.Cells(1, 1), .Cells(.Rows.Count, 5).End(xlUp))
End With
'Read into order dictionary
Set dOrds = New Dictionary
For I = 2 To UBound(vSrc, 1)
Set cOrd = New cOrder
sKey = vSrc(I, 5) 'Order ID
With cOrd
.ID = vSrc(I, 2)
.Key = vSrc(I, 1)
.Status1 = vSrc(I, 3)
.Status2 = vSrc(I, 4)
.addTrueFalse .Key, .Status1, .Status2
If Not dOrds.Exists(sKey) Then
dOrds.Add Key:=sKey, Item:=cOrd
Else
dOrds(sKey).addTrueFalse .Key, .Status1, .Status2
End If
End With
Next I
'Dim Results array
ReDim vRes(0 To dOrds.Count, 1 To 4)
'Headers
vRes(0, 1) = "Order ID"
vRes(0, 2) = "ID"
vRes(0, 3) = "TRUE"
vRes(0, 4) = "FALSE"
'Data
I = 0
For Each V In dOrds.Keys
I = I + 1
With dOrds(V)
vRes(I, 1) = V
vRes(I, 2) = .ID
vRes(I, 3) = .TrueFalse(True)
vRes(I, 4) = .TrueFalse(False)
End With
Next V
'Write results
Set rRes = rRes.Resize(UBound(vRes, 1) + 1, UBound(vRes, 2))
With rRes
.EntireColumn.Clear
.Value = vRes
.Rows(1).Font.Bold = True
With .EntireColumn
.HorizontalAlignment = xlCenter
.AutoFit
End With
End With
End Sub
Class Module
RENAME this module cOrder
Option Explicit
Private pKey As Long
Private pID As String
Private pStatus1 As Boolean
Private pStatus2 As Boolean
Private pTrueFalse As Dictionary
Public Property Get Key() As Long
Key = pKey
End Property
Public Property Let Key(Value As Long)
pKey = Value
End Property
Public Property Get ID() As String
ID = pID
End Property
Public Property Let ID(Value As String)
pID = Value
End Property
Public Property Get Status1() As Boolean
Status1 = pStatus1
End Property
Public Property Let Status1(Value As Boolean)
pStatus1 = Value
End Property
Public Property Get Status2() As Boolean
Status2 = pStatus2
End Property
Public Property Let Status2(Value As Boolean)
pStatus2 = Value
End Property
Public Function addTrueFalse(Key As Long, Status1 As Boolean, Status2 As Boolean)
If Status1 = True And Status2 = True Then
If Not pTrueFalse.Exists(True) Then
pTrueFalse.Add Key:=True, Item:=Key
Else
pTrueFalse(True) = pTrueFalse(True) & "," & Key
End If
Else
If Not pTrueFalse.Exists(False) Then
pTrueFalse.Add Key:=False, Item:=Key
Else
pTrueFalse(False) = pTrueFalse(False) & "," & Key
End If
End If
End Function
Public Property Get TrueFalse() As Dictionary
Set TrueFalse = pTrueFalse
End Property
Private Sub Class_Initialize()
Set pTrueFalse = New Dictionary
End Sub
My code mostly works but it's taking a while to debug so I am beginning to think my architecture may be flawed XD So how can I architect this better?
I have groups of data separated by a blank row. You can tell each group apart by the ID in column C in addition to the blank row. For each ID, I have various numbers in column B that I need to capture. Sometimes those numbers only start with 5, sometimes it starts with 7. I need to capture the 5 and the 7 separately.
With projWS
With .Range("C1:C6000")
Set f = .Find(cc, LookIn:=xlValues, lookat:=xlPart)
End With
If Not f Is Nothing Then 'first occurence found
counter = 0
i = f.Row
Do
acct = .Cells(i, 2)
If (Len(projWS.Cells(i, 3)) < 1 Or Left(acct, 1) = "7") And done = False Then
acctStart = f.Row
acctRows = i - acctStart
Set acctRng = .Range(.Cells(acctStart, 2), .Cells(i - 1, 5))
Set amountRng = .Range(.Cells(acctStart, 7), .Cells(i - 1, 8))
done = True 'set flag to show range has been filled
End If
counter = counter + 1 'increment counter
i = i + 1 'move to next row
Loop Until Len(.Cells(i, 3)) < 1 'keep looping until blank row
End If
If counter - 1 > acctRows Then 'how we determine if there's a "7"
flag = True 'so we set flag to true
Set depreRng = Range(.Cells(acctStart + acctRows, 2), .Cells(i - 1, 8))
dep = depreRng.Value2 'store range into array
End If
End With
After capture, I need to drop it into another worksheet. This worksheet already has a block of 7 built in. Hence this is the loop I am using to drop the range of 7. There is no built in block for the 5.
For r = 112 To 120
For k = 1 To UBound(dep())
If .Cells(r, 1).Value2 = Trim(dep(k, 1)) Then
Debug.Print .Cells(r, 1).Value2
.Cells(r, 6) = dep(k, 6)
.Cells(r, 7) = dep(k, 7)
Exit For
Else
.Cells(r, 6) = 0
.Cells(r, 7) = 0
End If
Next k
Next r
I have debugged several errors already. The current one is that depreRng is breaking because my math is bad. Instead of debugging each error as I stumble onto it, how can I architect this better?
Ok, my approach it's different. First i use a filter for find the range of rows with the index you are looking for and then loop inside this filtered rows for find the 5xx and the 7xx range. The code:
Sub Macro1()
Dim rng_5xx_start, rng_5xx_stop, rng_7xx_start, rng_7xx_stop As Integer
rng_5xx_start = 0
rng_5xx_stop = 0
rng_7xx_start = 0
rng_7xx_stop = 0
Dim range_5xx, range_7xx As String
'filter for the index you are looking for
'specify the maximum range, the field is the "offset" from the column B (the firts of the range), so for filter for column C you need to put 2, criteria...is the critera :)
ActiveSheet.Range("$B$1:$H$6000").AutoFilter Field:=2, Criteria1:="b"
'the filter returns only the rows with the specifyed index, now a for inside this rows for find the 5xx and the 7xx sub-ranges
For Each Row In ActiveSheet.Range("b1:b6000").SpecialCells(xlCellTypeVisible)
If Cells(Row.Row, 2).Value > 4999 And Cells(Row.Row, 2).Value < 6000 Then
'or any test for understnd if i'm in the 5xx range, if you prefer use the strings use something like left(cells(row.row,2).value,1) = "5"
If rng_5xx_start = 0 Then 'found the first row with a 5xx value
rng_5xx_start = Row.Row 'set the start of the range to this row
End If
If rng_5xx_stop < Row.Row Then 'the row where i am is in the 5xx range and is grater than the current end i noticed
rng_5xx_stop = Row.Row 'refresh the end of the range...at the end this will have the last number of row of the 5xx range
End If
End If
If Cells(Row.Row, 2).Value > 6999 And Cells(Row.Row, 2).Value < 8000 Then
'same as above but for 7xx range
If rng_7xx_start = 0 Then
rng_7xx_start = Row.Row
End If
If rng_7xx_stop < Row.Row Then
rng_7xx_stop = Row.Row
End If
End If
Next
If rng_5xx_start = 0 Then
'not found 5xx rows
range_5xx = "" 'or False, or what you prefer...
Else
range_5xx = "B" & rng_5xx_start & ":H" & rng_5xx_stop
End If
If rng_7xx_start = 0 Then
'not found 7xx rows
range_7xx = "" 'or False, or what you prefer...
Else
range_7xx = "B" & rng_7xx_start & ":H" & rng_7xx_stop
End If
End Sub
That's how i would imagine a macro for your job ;)
Edit 1:
I forgot that this will leave the sheet with the filter on...use activesheet.showalldata for show all the rows and not only the filtered ones
Edit 2:
The tests
If rng_5xx_stop < Row.Row Then
rng_5xx_stop = Row.Row
End If
and
If rng_7xx_stop < Row.Row Then
rng_7xx_stop = Row.Row
End If
are not necessary, it's enough do rng_5xx_stop = Row.Row and rng_7xx_stop = Row.Row and save the two IF statements
You are grouping cells based on the first number of the cell values in column B (I am assuming that they can never be letters). If that is the case, then you can create an array of 0 to 9 and store your ranges in there. Then go through the range.areas in order to get the groupings you're looking for (as highlighted in your screenshot).
To do this, something like this is all you need. I commented code to try to explain it more:
Sub tgr()
Dim wsData As Worksheet
Dim rColB As Range
Dim BCell As Range
Dim aRanges(0 To 9) As Range
Dim SubGroup As Range
Dim lRangeNum As Long
Dim i As Long
'Change to your actual worksheet
Set wsData = ActiveWorkbook.ActiveSheet
'Change to your actual column range, this is based off the sample data
Set rColB = wsData.Range("B1", wsData.Cells(wsData.Rows.Count, "B").End(xlUp))
'Loop through the column range
For Each BCell In rColB.Cells
'Make sure the cell is populated and the starting character is numeric
If Len(BCell.Value) > 0 And IsNumeric(Left(BCell.Value, 1)) Then
'Get the starting digit
lRangeNum = Val(Left(BCell.Value, 1))
'Check if any ranges have been assigned to that array index location
'If not, start a range at that array index
'If so, combine the ranges with Union
Select Case (aRanges(lRangeNum) Is Nothing)
Case True: Set aRanges(lRangeNum) = BCell
Case Else: Set aRanges(lRangeNum) = Union(aRanges(lRangeNum), BCell)
End Select
End If
Next BCell
'You can use any method you want to access the ranges, this just loops
'through the array indices and displays the range areas of each
For i = 0 To 9
If Not aRanges(i) Is Nothing Then
For Each SubGroup In aRanges(i).Areas
'Do what you want with it here
'This just selects the subgroup so you can see it found the groups properly
SubGroup.Select
MsgBox SubGroup.Address
Next SubGroup
End If
Next i
End Sub
I see you've allready rewritten your code, but I'd like to offer how I would do it and would like to know your thoughts about it. Would this be inefficient? I guess it could be because you have to read the first character in cells 4 times for every increment, but not shure if that is a big problem.
Dim start_row As Long
Dim end_row As Long
start_row = 1
end_row = 0
For i = 2 To Range("B" & Rows.Count).End(xlUp).Row
If Cells(i - 1, 2) = "" Then
start_row = i
ElseIf Left(Cells(i - 1, 2), 1) <> Left(Cells(i, 2), 1) Then
start_row = i
End If
If Cells(i + 1, 2) = "" Then
end_row = i
ElseIf Left(Cells(i + 1, 2), 1) <> Left(Cells(i, 2), 1) Then
end_row = i
End If
If end_row <> 0 Then
Call copy_range(start_row, end_row)
end_row = 0
End If
Next i
Another approach that lets you only read the character once could be
Dim start_row As Long
Dim end_row As Long
Dim char_above As String
Dim this_char As String
start_row = 1
end_row = 1
For i = 1 To Range("B" & Rows.Count).End(xlUp).Row
If Cells(i, 2) = "" Then
end_row = i - 1
if i <>1 then Call copy_range(start_row, end_row,char_above)
start_row = i + 1
Else
this_char = Left(Cells(i, 2), 1)
If this_char <> char_above Then
end_row = i - 1
if i<> 1 then Call copy_range(start_row, end_row,char_above)
start_row = i
End If
char_above = this_char
End If
Next i
Let me know your thoughts.
I have date in the following example format:
ABC 001
ABC 002
ABC 003
ABC 004
I want to remove duplcate rows in column A BUT leave the line with the highest value in column B (in this case 004). A simple duplicate removal doesn't give me the control on which value is not deleted (unless I'm missing something).
This is part of a larger VBA code and therefore, I'd like to do it via VBA. I greatly appreciate any and all help.
Assuming that column B contains numeric values, then you can use the code below to remove all non-max-duplicates. This works however the data is sorted since it loads the information into an array that keeps track of which value from column B is the largest.
Sub RemoveDuplicates()
Dim sht As Worksheet
Dim NonDupArr() As Variant
Dim i As Integer
Dim j As Integer
Dim EntryFound As Boolean
Set sht = ActiveSheet
'Reads range into an array and retains the records with the largest value
For i = 2 To sht.Cells(sht.Rows.Count, 1).End(xlUp).Row Step 1
EntryFound = False
'If first entry
If i = 2 Then
ReDim Preserve NonDupArr(1 To 2, 1 To 1)
NonDupArr(1, 1) = sht.Cells(i, 1).Value
NonDupArr(2, 1) = sht.Cells(i, 2).Value
'For all other entries
Else
'Loops through array to see if entry already exist
For j = LBound(NonDupArr, 2) To UBound(NonDupArr, 2)
If sht.Cells(i, 1).Value = NonDupArr(1, j) Then
'If enty exists it replaces the value from column B if larger than
'the entry allready in the array
If sht.Cells(i, 2).Value > NonDupArr(2, j) Then
NonDupArr(2, j) = sht.Cells(i, 2).Value
End If
EntryFound = True
Exit For
End If
Next j
'If no entry were found it will be added to the array
If Not EntryFound Then
ReDim Preserve NonDupArr(1 To 2, 1 To UBound(NonDupArr, 2) + 1)
NonDupArr(1, UBound(NonDupArr, 2)) = sht.Cells(i, 1).Value
NonDupArr(2, UBound(NonDupArr, 2)) = sht.Cells(i, 2).Value
End If
End If
Next i
'Loops through the sheet and removes all rows that doesn't match rows in the array
For i = sht.Cells(sht.Rows.Count, 1).End(xlUp).Row To 2 Step -1
'Searches for match in array
For j = LBound(NonDupArr, 2) To UBound(NonDupArr, 2)
'If this is not the largest entry then the row is removed
If sht.Cells(i, 1).Value = NonDupArr(1, j) And sht.Cells(i, 2).Value <> NonDupArr(2, j) Then
sht.Cells(i, 1).EntireRow.Delete
Exit For
End If
Next j
Next i
End Sub