I am working on my computer to automate a quote in Excel with VBA
It consists of finding duplicates so they can be summed.
For example:
I have the following information:
Click here for the Excel file
The range from A2:C4 is a group that it states there are 28 bolts, 1 nut for each bolt & 1 washer for each bolt.
A5:C7 is another group that is the same 28 bolts, 1 nut for each bolt & 1 washer for each bolt.
A11:C13 is another group but the difference is that for this one are 2 nuts & 2 washer per bolt.
So this wont be sum
This would be the result:
I have the following code where it only looks through all the cells, I can't find a way to make it look in groups or ranges.
Sub Macro1()
Dim LastRow As Long, LastColumn As Long
Dim wSrc As Worksheet: Set wSrc = Sheets("Hoja1")
With Application
.ScreenUpdating = False
.Calculation = xlCalculationManual
End With
With wSrc
LastRow = .Range("B" & .Rows.Count).End(xlUp).Row
Set rng = .Range("B1:B" & LastRow)
LastColumn = .Cells(1, .Columns.Count).End(xlToLeft).Column + 2
rng.AdvancedFilter Action:=xlFilterCopy, copytoRange:=.Cells(1, LastColumn), unique:=True
Z = .Cells(.Rows.Count, LastColumn).End(xlUp).Row
LastColumn = LastColumn + 1
.Cells(1, LastColumn).Value = "Total"
.Range(.Cells(2, LastColumn), .Cells(Z, LastColumn)).Formula = _
"=SUMIF(" & rng.Address & "," & .Cells(2, LastColumn - 1).Address(False, False) & "," & rng.Offset(, 1).Address & ")"
End With
With Application
.ScreenUpdating = Truek
.Calculation = xlCalculationAutomatic
End With
End Sub
Click below for the Excel file
Here is an approach that utilizes User Defined Object for the Hardware, and Hardware groups.
We could create more compact code with fewer loops, but, unless there is a significant speed issue, this is probably more readable, and can be more easily adapted to future needs.
We create two class modules (and be sure to rename them as indicated in the code).
One class module is for the hardware items, the second is for the different groups.
The hardware items properties are the description, the weight per item, and the number of items.
The hardware groups properties are a collection of Hardware items, and the Quantity of items in that group.
We then combine the hardware groups into a collection of unique hardware groups.
As the code is written, you could combine in other ways to generate other types of reports.
The results:
Class Module 1
'**Rename: cHardware**
Option Explicit
Private pDescription As String
Private pWt As Double
Private pItemCount As Long
Public Property Get Description() As String
Description = pDescription
End Property
Public Property Let Description(Value As String)
pDescription = Value
End Property
Public Property Get Wt() As Double
Wt = pWt
End Property
Public Property Let Wt(Value As Double)
pWt = Value
End Property
Public Property Get ItemCount() As Long
ItemCount = pItemCount
End Property
Public Property Let ItemCount(Value As Long)
pItemCount = Value
End Property
Class Module 2
'**Rename: cHardwareGrp**
Option Explicit
Private pHW As cHardWare
Private pHWs As Collection
Private pQty As Long
Private Sub Class_Initialize()
Set pHWs = New Collection
End Sub
Public Property Get HW() As cHardWare
Set HW = pHW
End Property
Public Property Let HW(Value As cHardWare)
Set pHW = Value
End Property
Public Property Get HWs() As Collection
Set HWs = pHWs
End Property
Public Function AddHW(Value As cHardWare)
Dim I As Long, J As Long
If pHWs.Count = 0 Then
pHWs.Add Value
Else 'Insert in sorted order
For J = pHWs.Count To 1 Step -1
If pHWs(J).Description <= Value.Description Then Exit For
Next J
If J = 0 Then
pHWs.Add Value, before:=1
Else
pHWs.Add Value, after:=J
End If
End If
End Function
Public Property Get Qty() As Long
Qty = pQty
End Property
Public Property Let Qty(Value As Long)
pQty = Value
End Property
Regular Module
Option Explicit
Sub SummarizeHW()
Dim wsRes As Worksheet, wsSrc As Worksheet, rRes As Range
Dim vSrc As Variant, vRes() As Variant
Dim cHW As cHardWare, colHW As Collection
Dim cHWG As cHardwareGrp, colHWG As Collection
Dim colUniqueHWG As Collection
Dim I As Long, J As Long, K As Long
Dim lQTY As Long
Dim S As String
Dim V As Variant
Dim RE As Object, MC As Object
'Set Source and Results Worksheets and Ranges
Set wsSrc = Worksheets("Hoja1")
Set wsRes = Worksheets("Hoja2")
Set rRes = wsRes.Cells(1, 1)
'Get Source Data
With wsSrc
vSrc = .Range(.Cells(1, 2), .Cells(.Rows.Count, 2).End(xlUp)) _
.Offset(columnoffset:=-1).Resize(columnsize:=3)
End With
'Set up regex to extract number of HW items in description
Set RE = CreateObject("vbscript.regexp")
With RE
.Global = False
.Pattern = "^\((\d+)\)\s*"
.MultiLine = True
End With
'Collect unique list of hardware items
' compute the weight of each single item
Set colHW = New Collection
On Error Resume Next
For I = 2 To UBound(vSrc, 1) 'assumes header row
If vSrc(I, 1) <> "" Then lQTY = vSrc(I, 1)
Set cHW = New cHardWare
With cHW
S = vSrc(I, 2)
If RE.test(S) = True Then
Set MC = RE.Execute(S)
.ItemCount = CLng(MC(0).submatches(0))
Else
.ItemCount = 1
End If
.Wt = vSrc(I, 3) / lQTY / .ItemCount
.Description = S
colHW.Add cHW, .Description
End With
Next I
On Error GoTo 0
'Collect the Hardware Groups
'HW group starts if there is a "Qty" in column 1
Set colHWG = New Collection
For I = 2 To UBound(vSrc, 1)
If vSrc(I, 1) <> "" Then lQTY = vSrc(I, 1)
Set cHWG = New cHardwareGrp
Do
With cHWG
.HW = colHW(vSrc(I, 2))
.AddHW .HW
.Qty = lQTY
End With
I = I + 1
If I > UBound(vSrc, 1) Then Exit Do
Loop Until vSrc(I, 1) <> ""
colHWG.Add cHWG
I = I - 1
Next I
'Collect the unique hardware groups
' A group is defined by ALL of the hardware components being identical
' in both type and quantity. Therefore, we can concatenate them as a key
Set colUniqueHWG = New Collection
On Error Resume Next
For I = 1 To colHWG.Count
With colHWG(I)
ReDim V(1 To .HWs.Count)
For J = 1 To UBound(V)
V(J) = .HWs(J).Description
Next J
S = Join(V, "|")
colUniqueHWG.Add colHWG(I), S
Select Case Err.Number
Case 457 'a duplicate so add the QTY
colUniqueHWG(S).Qty = colUniqueHWG(S).Qty + .Qty
Err.Clear
Case Is <> 0 'error stop
Debug.Print Err.Number, Err.Description
End Select
End With
Next I
On Error GoTo 0
'Final Report
'# of columns = 3
'# of rows = sum of the number of HW items in each group + 1 for the header
J = 0
For I = 1 To colUniqueHWG.Count
J = J + colUniqueHWG(I).HWs.Count
Next I
ReDim vRes(0 To J, 1 To 3)
'Column headers
vRes(0, 1) = "Qty"
vRes(0, 2) = "Hardware Description"
vRes(0, 3) = "Weight"
'populate the results array'
K = 1
For I = 1 To colUniqueHWG.Count
With colUniqueHWG(I)
For J = 1 To .HWs.Count
If J = 1 Then vRes(K, 1) = .Qty
vRes(K, 2) = .HWs(J).Description
vRes(K, 3) = .Qty * .HWs(J).Wt * .HWs(J).ItemCount
K = K + 1
Next J
End With
Next I
'Write the results on a new sheet
Set rRes = rRes.Resize(UBound(vRes, 1) + 1, UBound(vRes, 2))
With rRes
.EntireColumn.Clear
.Value = vRes
.ColumnWidth = 255
With Rows(1)
.Font.Bold = True
.HorizontalAlignment = xlCenter
End With
.EntireColumn.AutoFit
End With
End Sub
Hmmm. I see from your comments that the hardware may not always be in the same order. I will add a sorting routine to our group generation so that will be irrelevant.
EDIT: The AddHW function was modified to insert the HW items in sorted order. Since there should only be a few items, this insertion sort should be adequate.
Taking a different approach.
take advantage of the structure; three lines define it
Put results on a different tab
This input ...
generates this output ...
using this code ...
Option Explicit
Sub Macro1()
Dim LastRow As Long, LastColumn As Long
Dim wSrc As Worksheet: Set wSrc = Sheets("Hoja1")
Dim tmpSrc As Worksheet
Dim outRng As Range, inRng As Range
Dim iLoop As Long, jLoop As Long, QSum As Long
' turn off updating for speed
With Application
.ScreenUpdating = False
.Calculation = xlCalculationManual
End With
' setup - tmpSrc is the working and final result
Set tmpSrc = ActiveWorkbook.Sheets.Add(, wSrc)
Set inRng = wSrc.UsedRange
inRng.Copy
tmpSrc.Range("A1").PasteSpecial (xlPasteAll)
With tmpSrc
.Name = "Hoja2"
Set outRng = .UsedRange
LastRow = .UsedRange.Rows.Count
LastColumn = .UsedRange.Columns.Count
End With
' loop down through the range
For iLoop = 2 To LastRow
If outRng.Cells(iLoop, 1) <> "" Then
QSum = outRng.Cells(iLoop, 1).Value
For jLoop = LastRow To iLoop + 1 Step -1 'loop up through the range to find a match
' matches are defined by all three rows in column B
If outRng.Cells(jLoop, 1) <> "" And _
outRng.Cells(iLoop, 2) = outRng.Cells(jLoop, 2) And _
outRng.Cells(iLoop + 1, 2) = outRng.Cells(jLoop + 1, 2) And _
outRng.Cells(iLoop + 2, 2) = outRng.Cells(jLoop + 2, 2) Then
QSum = QSum + outRng.Cells(jLoop, 1).Value
outRng.Rows(jLoop + 2).Delete
outRng.Rows(jLoop + 1).Delete
outRng.Rows(jLoop).Delete
LastRow = LastRow - 3
End If
Next jLoop
outRng.Cells(iLoop, 1).Value = QSum
End If
Next iLoop
For iLoop = 1 To 3
outRng.Columns(iLoop).ColumnWidth = inRng.Columns(iLoop).ColumnWidth
Next iLoop
With Application
.ScreenUpdating = True
.Calculation = xlCalculationAutomatic
End With
End Sub
Edit:
Summing weights for bolts, nuts, and washers
Checking for case where nuts and washers appear in reverse order
n.b. I am using .UsedRange to find the last row and last column. Other methods are available.
.
Option Explicit
Sub Macro1()
Dim LastRow As Long, LastColumn As Long
Dim wSrc As Worksheet: Set wSrc = Sheets("Hoja1")
Dim tmpSrc As Worksheet
Dim outRng As Range, inRng As Range
Dim iLoop As Long, jLoop As Long, QSum As Long
Dim WSum1 As Double, WSum2 As Double, WSum3 As Double
' turn off updating for speed
With Application
.ScreenUpdating = False
.Calculation = xlCalculationManual
End With
' setup - tmpSrc is the working and final result
Set tmpSrc = ActiveWorkbook.Sheets.Add(, wSrc)
Set inRng = wSrc.UsedRange
inRng.Copy
tmpSrc.Range("A1").PasteSpecial (xlPasteAll)
With tmpSrc
.Name = "Hoja2"
Set outRng = .UsedRange
LastRow = .UsedRange.Rows.Count
LastColumn = .UsedRange.Columns.Count
End With
' loop down through the range
For iLoop = 2 To LastRow
If outRng.Cells(iLoop, 1) <> "" Then
QSum = outRng.Cells(iLoop, 1).Value
WSum1 = outRng.Cells(iLoop, 3).Value
WSum2 = outRng.Cells(iLoop + 1, 3).Value
WSum3 = outRng.Cells(iLoop + 2, 3).Value
For jLoop = LastRow To iLoop + 1 Step -1 'loop up through the range to find a match
' matches are defined by all three rows in column B
If outRng.Cells(jLoop, 1) <> "" And _
outRng.Cells(iLoop, 2) = outRng.Cells(jLoop, 2) And _
outRng.Cells(iLoop + 1, 2) = outRng.Cells(jLoop + 1, 2) And _
outRng.Cells(iLoop + 2, 2) = outRng.Cells(jLoop + 2, 2) Then
QSum = QSum + outRng.Cells(jLoop, 1).Value
WSum1 = WSum1 + outRng.Cells(jLoop, 3).Value
WSum2 = WSum2 + outRng.Cells(jLoop + 1, 3).Value
WSum3 = WSum3 + outRng.Cells(jLoop + 2, 3).Value
outRng.Rows(jLoop + 2).Delete
outRng.Rows(jLoop + 1).Delete
outRng.Rows(jLoop).Delete
LastRow = LastRow - 3
Else ' check if bolts and washers are in reverse order
If outRng.Cells(jLoop, 1) <> "" And _
outRng.Cells(iLoop, 2) = outRng.Cells(jLoop, 2) And _
outRng.Cells(iLoop + 1, 2) = outRng.Cells(jLoop + 2, 2) And _
outRng.Cells(iLoop + 2, 2) = outRng.Cells(jLoop + 1, 2) Then
QSum = QSum + outRng.Cells(jLoop, 1).Value
WSum1 = WSum1 + outRng.Cells(jLoop, 3).Value
WSum2 = WSum2 + outRng.Cells(jLoop + 2, 3).Value
WSum3 = WSum3 + outRng.Cells(jLoop + 1, 3).Value
outRng.Rows(jLoop + 2).Delete
outRng.Rows(jLoop + 1).Delete
outRng.Rows(jLoop).Delete
LastRow = LastRow - 3
End If
End If
Next jLoop
outRng.Cells(iLoop, 1).Value = QSum
outRng.Cells(iLoop, 3).Value = WSum1
outRng.Cells(iLoop + 1, 3).Value = WSum2
outRng.Cells(iLoop + 2, 3).Value = WSum3
End If
Next iLoop
For iLoop = 1 To 3
outRng.Columns(iLoop).ColumnWidth = inRng.Columns(iLoop).ColumnWidth
Next iLoop
With Application
.ScreenUpdating = True
.Calculation = xlCalculationAutomatic
End With
End Sub
Related
Im trying to change values in a dictionary dynamically. If value exists in dictionary, change that value to dictionary value + new value (incremental).
Im unable to do this however, i get the Run-time error 451: Property let procedure not defined and property get procedure did not return an object. Can someone help me do a "sumifs" -type of changes to the dictionary?
Sub Sumifs()
Dim objDictionary
Set objDictionary = CreateObject("Scripting.Dictionary")
Dim arr As Variant
Dim lr1 As Long
Dim arr2 As Variant
Dim lr2 As Long
With Blad15
lr1 = Worksheets("Sheet1").Cells(.Rows.Count, 5).End(xlUp).Row
arr = Worksheets("Sheet1").Range("E20:E" & lr1)
Debug.Print UBound(arr)
Debug.Print lr1
End With
ThisWorkbook.Sheets("Sheet1").Select
For i = 1 To UBound(arr)
objDictionary.Add Key:=CStr(Cells(i + 19, 5)), Item:=CStr(Cells(i + 19, 5))
Next
ThisWorkbook.Sheets("Sheet2").Select
With Blad6
lr2 = Worksheets("Sheet2").Cells(.Rows.Count, 2).End(xlUp).Row
arr2 = Worksheets("Sheet2").Range("B2:B" & lr2 + 1)
End With
For i = 1 To UBound(arr)
If objDictionary.Exists(Cells(i + 1, 2).Value) Then
objDictionary(Cells(i + 1, 2).Value) = objDictionary.Items(Cells(i + 1, 2)) + Worksheets("Sheet2").Cells(i + 1, 8).Value 'Error occurs here
End If
Next
End Sub
Based on your comments and the screenshots I understood it like that:
I created a new class module customer with the code below
Option Explicit
Public customerName As String
Public invoiceAmount As Double
Public cashReceived As Double
and then I created a new module with the following code for creating the summary
Sub CreateSummary()
Dim dict As Dictionary
Dim rgInvoices As Range
Set rgInvoices = Worksheets("Invoices sent").Range("A1").CurrentRegion
Set rgInvoices = rgInvoices.Offset(1).Resize(rgInvoices.Rows.Count - 1)
Dim sngRow As Range
Dim oneCustomer As customer
Set dict = New Dictionary
Dim customerName As String
Dim amount As Double
' Sum up the invoice amount for each single customer
For Each sngRow In rgInvoices.Rows
customerName = sngRow.Cells(1, 1).Value
amount = sngRow.Cells(1, 3).Value
If dict.Exists(sngRow.Cells(1, 1).Value) Then
dict(customerName).invoiceAmount = dict(customerName).invoiceAmount + amount
Else
Set oneCustomer = New customer
With oneCustomer
.customerName = customerName
.invoiceAmount = amount
End With
dict.Add oneCustomer.customerName, oneCustomer
End If
Next sngRow
Dim rgCashReceived As Range
Set rgCashReceived = Worksheets("Cash received").Range("A1").CurrentRegion
Set rgCashReceived = rgCashReceived.Offset(1).Resize(rgCashReceived.Rows.Count - 1)
' Sum up the cash received for each single customer
For Each sngRow In rgCashReceived.Rows
customerName = sngRow.Cells(1, 1).Value
amount = sngRow.Cells(1, 3).Value
If dict.Exists(sngRow.Cells(1, 1).Value) Then
dict(customerName).cashReceived = dict(customerName).cashReceived + amount
Else
Set oneCustomer = New customer
With oneCustomer
.customerName = customerName
.cashReceived = amount
End With
dict.Add oneCustomer.customerName, oneCustomer
End If
Next sngRow
' Print Out
Dim vKey As Variant
Dim i As Long
Dim shOut As Worksheet
Set shOut = Worksheets("Summary")
' Heading
With shOut
.Cells(1, 1).CurrentRegion.Clear
.Cells(1, 1).Value = "Customer Name"
.Cells(1, 2).Value = "Invocie amount"
.Cells(1, 3).Value = "Cash received"
' single rows
i = 2
For Each vKey In dict.Keys
Debug.Print vKey, dict(vKey).invoiceAmount, dict(vKey).cashReceived
.Cells(i, 1).Value = vKey
.Cells(i, 2).Value = dict(vKey).invoiceAmount
.Cells(i, 3).Value = dict(vKey).cashReceived
i = i + 1
Next vKey
End With
End Sub
Resolution for Compile error: User defined type not defined underlining Dim dict As dictionary Select Tools->Reference from the Visual Basic menu. Place a check in the box beside “Microsoft Scripting Runtime”
Though question remains: Why don't you use excel's built in SUMIF?
You can also try the second example from Macromastery
I'm looking for a way to speed up this code as it takes my computer 20-30 minutes to run. It essentially runs through a list of column values in sheet "A" and if It matches a column value in sheet "B" it will pull the entire corresponding row to the sheet "Match".
Sub MatchSheets()
Dim lastRowAF As Integer
Dim lastRowL As Integer
Dim lastRowM As Integer
Dim foundTrue As Boolean
Application.ScreenUpdating = False
lastRowAF = Sheets("FHA").Cells(Sheets("FHA").Rows.Count, "AF").End(xlUp).Row
lastRowL = Sheets("New Construction").Cells(Sheets("New Construction").Rows.Count, "L").End(xlUp).Row
lastRowM = Sheets("Match").Cells(Sheets("Match").Rows.Count, "A").End(xlUp).Row
For i = 1 To lastRowAF
foundTrue = False
For j = 1 To lastRowL
If Sheets("FHA").Cells(i, 32).Value = Sheets("New Construction").Cells(j, 12).Value Then
foundTrue = True
Exit For
End If
Next j
If foundTrue Then
Sheets("FHA").Rows(i).Copy Destination:= _
Sheets("Match").Rows(lastRowM + 1)
lastRowM = lastRowM + 1
End If
Next i
Application.ScreenUpdating = True
End Sub
Collections are optimized for looking values. Using a combination of a Collection and Array is usually the best way to match two list. 20K Rows X 54 Columns (140K Values) took this code 10.87 seconds to copy over on a slow PC.
Sub NewMatchSheets()
Dim t As Double: t = Timer
Const NUM_FHA_COLUMNS As Long = 54, AF As Long = 32
Dim list As Object
Dim key As Variant, data() As Variant, results() As Variant
Dim c As Long, r As Long, count As Long
ReDim results(1 To 50000, 1 To 100)
Set list = CreateObject("System.Collections.ArrayList")
With ThisWorkbook.Worksheets("New Construction")
data = .Range("L1", .Cells(.Rows.count, "L").End(xlUp)).Value
For Each key In data
If key <> "" Then
If Not list.Contains(key) Then list.Add key
End If
Next
End With
With ThisWorkbook.Worksheets("FHA")
data = .Range(.Range("A1").Resize(1, NUM_FHA_COLUMNS), .Cells(.Rows.count, AF).End(xlUp)).Value
For r = 1 To UBound(data)
key = data(r, AF)
If list.Contains(key) Then
count = count + 1
For c = 1 To UBound(data, 2)
results(count, c) = data(r, c)
Next
End If
Next
End With
If count = 0 Then Exit Sub
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = True
With ThisWorkbook.Worksheets("Match")
With .Cells(.Rows.count, "A").End(xlUp)
.Offset(1).Resize(count, NUM_FHA_COLUMNS).Value = results
End With
End With
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = False
Debug.Print Round(Timer - t, 2)
End Sub
use variant arrays:
Sub MatchSheets()
Dim lastRowAF As Long
Dim lastRowL As Long
Dim lastRowM As Long
Application.ScreenUpdating = False
lastRowAF = Sheets("FHA").Cells(Sheets("FHA").Rows.Count, "AF").End(xlUp).Row
lastRowL = Sheets("New Construction").Cells(Sheets("New Construction").Rows.Count, "L").End(xlUp).Row
lastRowM = Sheets("Match").Cells(Sheets("Match").Rows.Count, "A").End(xlUp).Row
Dim FHAArr As Variant
FHAArr = Sheets("FHA").Range(Sheets("FHA").Cells(1, 1), Sheets("FHA").Cells(lastRowAF, Columns.Count).End(xlToLeft)).Value
Dim NewConArr As Variant
NewConArr = Sheets("New Construction").Range(Sheets("New Construction").Cells(1, 12), Sheets("New Construction").Cells(lastRowL, 12)).Value
Dim outarr As Variant
ReDim outarr(1 To UBound(FHAArr, 1), 1 To UBound(FHAArr, 2))
Dim k As Long
k = 0
Dim l As Long
For i = 1 To lastRowAF
For j = 1 To lastRowL
If FHAArr(i, 32) = NewConArr(j, 1) Then
For l = 1 To UBound(FHAArr, 2)
k = k + 1
outarr(k, l) = FHAArr(i, l)
Next l
Exit For
End If
Next j
Next i
Sheets("Match").Cells(lastRowM + 1, 1).Resize(UBound(outarr, 1), UBound(outarr, 2)).Value = outarr
Application.ScreenUpdating = True
End Sub
FHA Worksheet: 2500 rows by 50 columnsNew Construction Worksheet: 500 rows by 1 column LMatch Worksheet: 450 transfers from FMA Elapsed time: 0.13 seconds
Get rid of all the nested loop and work with arrays.
Your narrative seemed to suggest that there might be multiple matches for any one value but your code only looks for a single match then Exit For. I'll work with the latter of the two scenarios.
Sub MatchSheets()
Dim i As Long, j As Long
Dim vFM As Variant, vNC As Variant
Debug.Print Timer
With Worksheets("New Construction")
vNC = .Range(.Cells(1, "L"), _
.Cells(.Rows.Count, "L").End(xlUp)).Value2
End With
With Worksheets("FHA")
vFM = .Range(.Cells(1, "A"), _
.Cells(.Rows.Count, _
.Cells(1, .Columns.Count).End(xlToLeft).Column).End(xlUp)).Value2
End With
ReDim vM(LBound(vFM, 2) To UBound(vFM, 2), 1 To 1)
For i = LBound(vFM, 1) To UBound(vFM, 1)
If Not IsError(Application.Match(vFM(i, 32), vNC, 0)) Then
For j = LBound(vFM, 2) To UBound(vFM, 2)
vM(j, UBound(vM, 2)) = vFM(i, j)
Next j
ReDim Preserve vM(LBound(vFM, 2) To UBound(vFM, 2), LBound(vM, 2) To UBound(vM, 2) + 1)
End If
Next i
With Worksheets("match")
.Cells(.Rows.Count, "A").End(xlUp).Offset(1, 0).Resize(UBound(vM, 2), UBound(vM, 1)) = _
Application.Transpose(vM)
End With
Application.ScreenUpdating = True
Debug.Print Timer
End Sub
Try changing this line:
Sheets("FHA").Rows(i).Copy Destination:= _
Sheets("Match").Rows(lastRowM + 1)
For the following line:
Sheets("Match").Rows(lastRowM + 1).Value for Sheets("FHA").Rows(i).value
If you really need to shave milliseconds, you could also set: lastRowM to be:
lastRowM = Sheets("Match").Cells(Sheets("Match").Rows.Count, "A").End(xlUp).Row + 1
And use:
Sheets("Match").Rows(lastRowM).Value for Sheets("FHA").Rows(i).value
Thus saving you an addition every time you go through that part of the code
I am writing a VBA code on excel using loops to go through 10000+ lines.
Here is an example of the table
And here is the code I wrote :
Sub Find_Matches()
Dim wb As Workbook
Dim xrow As Long
Set wb = ActiveWorkbook
wb.Worksheets("Data").Activate
tCnt = Sheets("Data").UsedRange.Rows.Count
Dim e, f, a, j, h As Range
xrow = 2
Application.ScreenUpdating = False
Application.Calculation = xlManual
For xrow = 2 To tCnt Step 1
Set e = Range("E" & xrow)
Set f = e.Offset(0, 1)
Set a = e.Offset(0, -4)
Set j = e.Offset(0, 5)
Set h = e.Offset(0, 3)
For Each Cell In Range("E2:E" & tCnt)
If Cell.Value = e.Value Then
If Cell.Offset(0, 1).Value = f.Value Then
If Cell.Offset(0, -4).Value = a.Value Then
If Cell.Offset(0, 5).Value = j.Value Then
If Cell.Offset(0, 3).Value = h.Value Then
If (e.Offset(0, 7).Value) + (Cell.Offset(0, 7).Value) = 0 Then
Cell.EntireRow.Interior.Color = vbYellow
e.EntireRow.Interior.Color = vbYellow
End If
End If
End If
End If
End If
End If
Next
Next
End Sub
As you can imagine, this is taking a lot of time to go through 10000+ lines and I would like to find a faster solution. There must be a method I don't think to avoid the over looping
Here are the condition :
For each line, if another line anywhere in the file has the exact same
:
Buyer ID (col. E)
`# purchased (col. F)
Product ID (col.A)
Payment (col. J)
Date purchased (col. H)
Then, if the SUM of the Amount (col. L) the those two matching line is
0, then color both rows in yellow.
Note that extra columns are present and not being compared (eg- col. B) but are still important for the document and cannot be deleted to ease the process.
Running the previous code, in my example, row 2 & 5 get highlighted :
This is using nested dictionaries and arrays to check all conditions
Timer with my test data: Rows: 100,001; Dupes: 70,000 - Time: 14.217 sec
Option Explicit
Public Sub FindMatches()
Const E = 5, F = 6, A = 1, J = 10, H = 8, L = 12
Dim ur As Range, x As Variant, ub As Long, d As Object, found As Object
Set ur = ThisWorkbook.Worksheets("Data").UsedRange
x = ur
Set d = CreateObject("Scripting.Dictionary")
Set found = CreateObject("Scripting.Dictionary")
Dim r As Long, rId As String, itm As Variant, dupeRows As Object
For r = ur.Row To ur.Rows.Count
rId = x(r, E) & x(r, F) & x(r, A) & x(r, J) & x(r, H)
If Not d.Exists(rId) Then
Set dupeRows = CreateObject("Scripting.Dictionary")
dupeRows(r) = 0
Set d(rId) = dupeRows
Else
For Each itm In d(rId)
If x(r, L) + x(itm, L) = 0 Then
found(r) = 0
found(itm) = 0
End If
Next
End If
Next
Application.ScreenUpdating = False
For Each itm In found
ur.Range("A" & itm).EntireRow.Interior.Color = vbYellow
Next
Application.ScreenUpdating = True
End Sub
Before
After
I suggest a different approach altogether: add a temporary column to your data that contains a concatenation of each cell in the row. This way, you have:
A|B|C|D|E
1|Mr. Smith|500|A|1Mr. Smith500A
Then use Excel's conditional formatting on the temporary column, highlighting duplicate values. There you have your duplicated rows. Now it's only a matter of using a filter to check which ones have amounts equal to zero.
You can use the CONCATENATE function; it requires you to specify each cell separately and you can't use a range, but in your case (comparing only some of the columns) it seems like a good fit.
Maciej's answer is easy to implement (if you can add columns to your data without interrupting anything), and I would recommend it if possible.
However, for the sake of answering your question, I will contribute a VBA solution as well. I tested it on dataset that is a bit smaller than yours, but I think it will work for you. Note that you might have to tweak it a little (which row you start on, table name, etc) to fit your workbook.
Most notably, the segment commented with "Helper column" is something you most likely will have to adjust - currently, it compares every cell between A and H for the current row, which is something you may or may not want.
I've tried to include a little commentary in the code, but it's not much. The primary change is that I'm using in-memory processing of an array rather than iterating over a worksheet range (which for larger datasets should be exponentially faster).
Option Base 1
Option Explicit
' Uses ref Microsoft Scripting Runtime
Sub Find_Matches()
Dim wb As Workbook, ws As Worksheet
Dim xrow As Long, tCnt As Long
Dim e As Range, f As Range, a As Range, j As Range, h As Range
Dim sheetArr() As Variant, arr() As Variant
Dim colorTheseYellow As New Dictionary, colorResults() As String, dictItem As Variant
Dim arrSize As Long, i As Long, k As Long
Dim c As Variant
Set wb = ThisWorkbook
Set ws = wb.Worksheets("Data")
ws.Activate
tCnt = ws.UsedRange.Rows.Count
xrow = 2
Application.ScreenUpdating = False
Application.Calculation = xlManual
' Read range into an array so we process in-memory
sheetArr = ws.Range("A2:H" & tCnt)
arrSize = UBound(sheetArr, 1)
' Build new arr with "helper column"
ReDim arr(1 To arrSize, 1 To 9)
For i = 1 To arrSize
For k = 1 To 8
arr(i, k) = sheetArr(i, k)
arr(i, 9) = CStr(arr(i, 9)) & CStr(arr(i, k)) ' "Helper column"
Next k
Next i
' Iterate over array & build collection to indicate yellow lines
For i = LBound(arr, 1) To UBound(arr, 1)
If Not colorTheseYellow.Exists(i) Then colorResults = Split(ReturnLines(arr(i, 9), arr), ";")
For Each c In colorResults
If Not colorTheseYellow.Exists(CLng(c)) Then colorTheseYellow.Add CLng(c), CLng(c)
Next c
Next i
' Enact row colors
For Each dictItem In colorTheseYellow
'Debug.Print "dict: "; dictItem
If dictItem <> 0 Then ws.ListObjects(1).ListRows(CLng(dictItem)).Range.Interior.Color = vbYellow
Next dictItem
End Sub
Function ReturnLines(ByVal s As String, ByRef arr() As Variant) As String
' Returns a "Index;Index" string indicating the index/indices where the second, third, etc. instance(s) of s was found
' Returns "0;0" if 1 or fewer matches
Dim i As Long
Dim j As Long
Dim tmp As String
ReturnLines = 0
j = 0
tmp = "0"
'Debug.Print "arg: " & s
For i = LBound(arr, 1) To UBound(arr, 1)
If arr(i, 9) = s Then
j = j + 1
'Debug.Print "arr: " & arr(i, 9)
'Debug.Print "ReturnLine: " & i
tmp = tmp & ";" & CStr(i)
End If
Next i
'If Left(tmp, 1) = ";" Then tmp = Mid(tmp, 2, Len(tmp) - 1)
'Debug.Print "tmp: " & tmp
If j >= 2 Then
ReturnLines = tmp
Else
ReturnLines = "0;0"
End If
End Function
On my simple dataset, it yields this result (marked excellently with freehand-drawn color indicators):
Thanks everybody for your answers,
Paul Bica's solution actually worked and I am using a version of this code now.
But, just to animate the debate, I think I also found another way around my first code, inspired by Maciej's idea of concatenating the cells and using CStr to compare the values and, of course Vegard's in-memory processing by using arrays instead of going through the workbook :
Sub Find_MatchesStr()
Dim AmountArr(300) As Variant
Dim rowArr(300) As Variant
Dim ws As Worksheet
Dim wb As Workbook
Set ws = ThisWorkbook.Sheets("Data")
ws.Activate
Range("A1").Select
rCnt = ws.Cells.SpecialCells(xlCellTypeLastCell).Row
For i = 2 To rCnt
If i = rCnt Then
Exit For
Else
intCnt = 0
strA = ws.Cells(i, 1).Value
strE = ws.Cells(i, 5).Value
strF = ws.Cells(i, 6).Value
strH = ws.Cells(i, 8).Value
strL = ws.Cells(i, 10).Value
For j = i To rCnt - 1
strSearchA = ws.Cells(j, 1).Value
strSearchE = ws.Cells(j, 5).Value
strSearchF = ws.Cells(j, 6).Value
strSearchH = ws.Cells(j, 8).Value
strSearchL = ws.Cells(j, 10).Value
If CStr(strE) = CStr(strSearchE) And CStr(strA) = CStr(strSearchA) And CStr(strF) = CStr(strSearchF) And CStr(strH) = CStr(strSearchH) And CStr(strL) = CStr(strSearchL) Then
AmountArr(k) = ws.Cells(j, 12).Value
rowArr(k) = j
intCnt = intCnt + 1
k = k + 1
Else
Exit For
End If
Next
strSum = 0
For s = 0 To UBound(AmountArr)
If AmountArr(s) <> "" Then
strSum = strSum + AmountArr(s)
Else
Exit For
End If
Next
strAppenRow = ""
For b = 0 To UBound(rowArr)
If rowArr(b) <> "" Then
strAppenRow = strAppenRow & "" & rowArr(b) & "," & AmountArr(b) & ","
Else
Exit For
End If
Next
If intCnt = 1 Then
Else
If strSum = 0 Then
For rn = 0 To UBound(rowArr)
If rowArr(rn) <> "" Then
Let rRange = rowArr(rn) & ":" & rowArr(rn)
Rows(rRange).Select
Selection.Interior.Color = vbYellow
Else
Exit For
End If
Next
Else
strvar = ""
strvar = Split(strAppenRow, ",")
For ik = 1 To UBound(strvar)
If strvar(ik) <> "" Then
strVal = CDbl(strvar(ik))
For ik1 = ik To UBound(strvar)
If strvar(ik1) <> "" Then
strVal1 = CDbl(strvar(ik1))
If strVal1 + strVal = 0 Then
Let sRange1 = strvar(ik - 1) & ":" & strvar(ik - 1)
Rows(sRange1).Select
Selection.Interior.Color = vbYellow
Let sRange = strvar(ik1 - 1) & ":" & strvar(ik1 - 1)
Rows(sRange).Select
Selection.Interior.Color = vbYellow
End If
Else
Exit For
End If
ik1 = ik1 + 1
Next
Else
Exit For
End If
ik = ik + 1
Next
End If
End If
i = i + (intCnt - 1)
k = 0
Erase AmountArr
Erase rowArr
End If
Next
Range("A1").Select
End Sub
I still have some mistakes (rows not higlighted when they should be), the above code is not perfect, but I thought it'd be OK to give you an idea of where I was going before Paul Bica's solution came in.
Thanks again !
If your data is only till column L, then use below code, I found it is taking less time to run....
Sub Duplicates()
Application.ScreenUpdating = False
Dim i As Long, lrow As Long
lrow = Cells(Rows.Count, 1).End(xlUp).Row
Range("O2") = "=A2&E2&F2&J2&L2"
Range("P2") = "=COUNTIF(O:O,O2)"
Range("O2:P" & lrow).FillDown
Range("O2:O" & lrow).Copy
Range("O2:O" & lrow).PasteSpecial xlPasteValues
Application.CutCopyMode = False
For i = 1 To lrow
If Cells(i, 16) = 2 Then
Cells(i, 16).EntireRow.Interior.Color = vbYellow
End If
Next
Application.ScreenUpdating = True
Range("O:P").Delete
Range("A1").Select
MsgBox "Done"
End Sub
I'm currently trying to make a map that transposes unique values from a column and populate this new list with some parameters from another table,
the result on this map should be the following
I've already have the code for the unique values as follows:
Dim d As Object
Dim c As Variant
Dim i As Long
Dim lr As Long
Set d = CreateObject("Scripting.Dictionary")
lr = Cells(Rows.Count, 9).End(xlUp).Row
c = Range("B2:B" & lr)
For i = 1 To UBound(c, 1)
d(c(i, 1)) = 1
Next i
Range("AK2").Resize(d.Count) = Application.Transpose(d.keys)
Although for filling the amounts on the columns Base and VAT I'm having some issues trying to think on the formula, basically for "Base" the value should be the total by Document Nr of the accounts starting with 6*,7* which are a result of Dr - Cr.
I know it may sound a bit confusing, but if anyone could please help me I would be much appreciated.
Using #RonRosenfeld formula from comments, following might be helpful:
Sub Demo()
Dim lastRow As Long, lastCol As Long, currLR As Long
Dim rng As Range, rngWH As Range
Dim srcSht As Worksheet
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Set srcSht = Sheets("Sheet1") 'set data sheet here
With srcSht
lastRow = .Range("A" & .Rows.Count).End(xlUp).Row 'last row with data in sheet
Set rng = .Range("A1:A" & lastRow) 'range for filter
Set rngWH = .Range("A2:A" & lastRow) 'range for formulas
lastCol = .Cells(1, .Columns.Count).End(xlToLeft).Column + 2 'column to display data
rng.AdvancedFilter Action:=xlFilterCopy, copytoRange:=.Cells(1, lastCol), unique:=True
currLR = .Cells(.Rows.Count, lastCol).End(xlUp).Row 'unique nr. doc count
lastCol = lastCol + 1
'formula for Base
.Cells(1, lastCol).Value = "Base"
.Range(.Cells(2, lastCol), .Cells(currLR, lastCol)).Formula = _
"=SUMPRODUCT((" & .Cells(2, lastCol - 1).Address(False, False) & "=" & rngWH.Address & ")*(LEFT(" & rngWH.Offset(, 1).Address & ")={""6"",""7""})*(" & rngWH.Offset(, 2).Address & "))"
'formula for Vat
.Cells(1, lastCol + 1).Value = "VAT"
'enter formula here for VAT
'formula for Total
.Cells(1, lastCol + 2).Value = "Total"
.Range(.Cells(2, lastCol + 2), .Cells(currLR, lastCol + 2)).Formula = _
"=SUMIF(" & rngWH.Address & "," & .Cells(2, lastCol - 1).Address(False, False) & "," & rngWH.Offset(, 3).Address & ")"
.Range(.Cells(2, lastCol), .Cells(currLR, lastCol + 2)).Value = .Range(.Cells(2, lastCol), .Cells(currLR, lastCol + 2)).Value
End With
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
For a "pure" VBA solution, I would
create a user defined object which as the properties of Nr.Doc, Acct, VAT, Base and Total.
Base, as you wrote, we detect by checking the first digit of the account
VAT will be any amount in the Dr column that does not start with 6 or 7
Total will be the values in the Cr column.
If your rules are different, by setting things up like this, they can be easily changed, as the code is almost self-documenting.
For the UDO, we enter a class module and rename it cDoc.
Also, I chose to use early-binding so we set a reference to Microsoft Scripting Runtime. If you want to change it to late-binding as you do in your posted code, feel free to do that. It can be easier if you are distributing the file; but I prefer to have the Intellisense available when I am coding.
Class Module
Option Explicit
'Rename this module "cDoc"
Private pDocNum As String
Private pAcct As String
Private pBase As Currency
Private pVAT As Currency
Private pTotal As Currency
Public Property Get Acct() As String
Acct = pAcct
End Property
Public Property Let Acct(Value As String)
pAcct = Value
End Property
Public Property Get Base() As Currency
Base = pBase
End Property
Public Property Let Base(Value As Currency)
pBase = Value
End Property
Public Property Get VAT() As Currency
VAT = pVAT
End Property
Public Property Let VAT(Value As Currency)
pVAT = Value
End Property
Public Property Get Total() As Currency
Total = pTotal
End Property
Public Property Let Total(Value As Currency)
pTotal = Value
End Property
Public Property Get DocNum() As String
DocNum = pDocNum
End Property
Public Property Let DocNum(Value As String)
pDocNum = Value
End Property
Regular Module
Option Explicit
'Set Reference to Microsoft Scripting Runtime
' you can change this to late binding if everything works
Sub ReOrganizeTable()
Dim wsSrc As Worksheet, wsRes As Worksheet, rRes As Range
Dim vSrc As Variant, vRes As Variant
Dim dDoc As Dictionary, cD As cDoc
Dim I As Long
Dim V As Variant
'Set source and results worksheets
'Read source data into variant array
Set wsSrc = Worksheets("sheet1")
With wsSrc
vSrc = .Range(.Cells(1, 1), .Cells(.Rows.Count, 1).End(xlUp)).Resize(columnsize:=4)
End With
On Error Resume Next
Set wsRes = Worksheets("Results")
Select Case Err.Number
Case 9
Set wsRes = Worksheets.Add(after:=wsSrc)
wsRes.Name = "Results"
Case Is <> 0
Debug.Print Err.Number, Err.Description
Stop
End Select
On Error GoTo 0
Set rRes = wsRes.Cells(1, 1)
'Gather and organize the data
Set dDoc = New Dictionary
For I = 2 To UBound(vSrc, 1)
Set cD = New cDoc
With cD
.DocNum = vSrc(I, 1)
.Acct = CStr(vSrc(I, 2))
Select Case Left(.Acct, 1)
Case 6, 7
.Base = vSrc(I, 3)
Case Else
.VAT = vSrc(I, 3)
End Select
.Total = vSrc(I, 4)
If Not dDoc.Exists(.DocNum) Then
dDoc.Add Key:=.DocNum, Item:=cD
Else
dDoc(.DocNum).Base = dDoc(.DocNum).Base + .Base
dDoc(.DocNum).VAT = dDoc(.DocNum).VAT + .VAT
dDoc(.DocNum).Total = dDoc(.DocNum).Total + .Total
End If
End With
Next I
'Size results array
ReDim vRes(0 To dDoc.Count, 1 To 4)
'Headers
vRes(0, 1) = "Nr Doc"
vRes(0, 2) = "Base"
vRes(0, 3) = "VAT"
vRes(0, 4) = "Total"
'Populate the data area
I = 0
For Each V In dDoc.Keys
I = I + 1
Set cD = dDoc(V)
With cD
vRes(I, 1) = .DocNum
vRes(I, 2) = .Base
vRes(I, 3) = .VAT
vRes(I, 4) = .Total
End With
Next V
'write and format the results
Set rRes = rRes.Resize(UBound(vRes, 1) + 1, UBound(vRes, 2))
With rRes
.EntireColumn.Clear
.Value = vRes
With .Rows(1)
.Font.Bold = True
.HorizontalAlignment = xlCenter
End With
.EntireColumn.AutoFit
End With
End Sub
Results
Using your original posted data
I'm making a code in order to copy data from a database in a new list. This with the purpose of using this data in a .txt export file in another program.
I have a large list with names and data next to it. And I would like to copy the names with the associated data in a new workbook. This data also needs to be sorted in 'depth' in order to get the result that I need for the export file.
I hope somebody can help me with this. At this moment I managed to get a list of the names and the start and end row of each name in a new workbook. I think this helps me with getting the data sorted.
Now I want to expand this code so that I can copy the data that I need.
I included a picture where you can see how my database looks. This is indicated in the black square. And in the red square you see how I want the result list to look like.
I hope somebody can help me with this!
Database and Result
This is the code that I have until now:
Option Explicit
Sub RowCount()
Dim Oldstatusbar As Boolean
Dim DOF As Integer, Counter As Integer, Row_Copied As Integer
Dim CurrentMin As Long, StartRow As Long, StartColumn As Long
Dim OutputColumn As Long, OutputRow As Long
Dim Borehole As String, Start_Row As String, End_Row As String, Output As String
Dim CurrentName As String
Dim rng As RANGE, Cell As RANGE, brh As RANGE
Dim wbMain As Workbook, wbWellsRowCount As Workbook
Dim wsLog As Worksheet, wsSheet1 As Worksheet, wsSheet2 As Worksheet
Dim HCdatabase2 As Variant
Oldstatusbar = Application.DisplayStatusBar
Set wbMain = Workbooks("HCdatabase2.xlsm")
Set wsLog = wbMain.Sheets("Log")
DOF = 1
Counter = 1
Row_Copied = 0
wsLog.Select
StartColumn = 1
StartRow = 1
wsLog.Cells(StartRow + DOF, StartColumn).End(xlDown).Select
Set rng = wsLog.RANGE(wsLog.Cells(StartRow + DOF, StartColumn), wsLog.Cells(StartRow + DOF, StartColumn).End(xlDown))
CurrentName = wsLog.Cells(StartRow + DOF, StartColumn).Value
CurrentMin = Cells(StartRow + DOF, StartColumn).Row
Set wbWellsRowCount = Workbooks.Add
wbWellsRowCount.SaveAs "H:\Petrel\2016 Youri Kickken - Stage - HC Shows\VBA\Code Set-up\VBA-DATABASE\wbWellsRowCount.xls"
Set wsSheet1 = wbWellsRowCount.Sheets("Sheet1")
wsSheet1.Select
OutputColumn = 1
OutputRow = DOF + 1
wsSheet1.Cells(OutputRow, OutputColumn).Value = CurrentName
wsSheet1.Cells(OutputRow, OutputColumn + 1).Value = CurrentMin
wsSheet1.Cells(1, 1).Name = "Borehole"
wsSheet1.Cells(1, 2).Name = "Start_Row"
wsSheet1.Cells(1, 3).Name = "End_Row"
wsSheet1.Cells(1, 4).Name = "Output"
ActiveWorkbook.Sheets.Add After:=Worksheets(Worksheets.Count)
Set wsSheet2 = wbWellsRowCount.Sheets("Sheet2")
For Each Cell In rng
If Cell.Value <> CurrentName Then
wsSheet1.Cells(OutputRow, OutputColumn + 2).Value = Cell.Row - 1
CurrentName = Cell.Value
CurrentMin = Cell.Row
OutputRow = OutputRow + 1
wsSheet1.Cells(OutputRow, OutputColumn).Value = CurrentName
wsSheet1.Cells(OutputRow, OutputColumn + 1).Value = CurrentMin
wsSheet1.Cells(Counter + DOF, "D").Value = Counter
Counter = Counter + 1
End If
Next Cell
Set Cell = rng.End(xlDown)
wsSheet1.Cells(OutputRow, OutputColumn + 2).Value = Cell.Row
wsSheet1.Cells(Counter + DOF, "D").Value = Counter
End If
Next Cell
wbWellsRowCount.Close True
RANGE("A1").Select
ActiveWindow.ScrollRow = RANGE("A1").Row
Application.ScreenUpdating = True
Application.DisplayStatusBar = Oldstatusbar
End Sub
you can adapt and use this code:
Option Explicit
Sub main()
With Workbooks("Data").Worksheets("Depths") '<--| change 'Workbooks("Data").Worksheets("Depths")' with your actual workbook and worksheet name
With .Range("A2", .Cells(.Rows.Count, 1).End(xlUp)) '<--| refer to column "A" cells from row 2 down to last non empty one
.Offset(.Rows.Count).value = .value '<--| duplicate names down column "A"
.Offset(.Rows.Count, 1).value = .Offset(, 3).value '<--| duplicate 2nd Depth column down 1st Depth column
.Offset(.Rows.Count, 4).value = .Offset(, 4).value '<--| duplicate Class_2 column down itself
.Offset(, 4).ClearContents '<--| clear original Class_2 column
.Offset(, 3).EntireColumn.Delete '<--| delete 2nd Depth column, no longer needed
With .Offset(, 1).Resize(2 * .Rows.Count) '<--|refer to Depth column (the only one remained)
If WorksheetFunction.CountBlank(.Cells) > 0 Then .SpecialCells(XlCellType.xlCellTypeBlanks).EntireRow.Delete '<--| delete empty values rows
End With
End With
With .Range("A2", .Cells(.Rows.Count, 1).End(xlUp)).Resize(, 4) '<--| refer to all data: columns "A:D" from row 2 down to column "A" last non empty one
.Sort key1:=.Cells(1, 1), order1:=xlAscending, key2:=.Cells(1, 2), order2:=xlAscending, header:=xlNo, Orientation:=xlTopToBottom, DataOption1:=xlSortNormal, DataOption2:=xlSortNormal '<--| sort it!
End With
End With
End Sub
so that once you have your original data already copied in the final place as per your "Starting Point database" example, you just:
change Workbooks("Data").Worksheets("Depths") with your actual final place workbook and worksheet references
run it and you'll have the final data arrangement
I hope I didn't overkill it, but your post raised a light-bulb for me, as I thought of defining and using a cDepth Class.
The following Sub (your code, mostly) does the following things:
1) Scans the entire wsLog worksheet, and organizes the data in cDepth Class (array).
2) Sorts the Depths_Arr (from cDepth Class) according to Name and then by Depth).
3) Copies the data back (in my code I am copying the data to Columns H:K in the same worksheet) - you can modify the target easily.
Option Explicit
' Class Array CDates Variables to store all Series data
Public Current_Depth As CDepth
Public Depths_Arr() As CDepth
Sub RowCount()
Dim Oldstatusbar As Boolean
Dim DOF As Integer, Counter As Integer, Row_Copied As Integer
Dim CurrentMin As Long, StartRow As Long, StartColumn As Long
Dim OutputColumn As Long, OutputRow As Long
Dim Borehole As String, Start_Row As String, End_Row As String, Output As String
Dim CurrentName As String
Dim rng As Range, Cell As Range, brh As Range
Dim wbMain As Workbook, wbWellsRowCount As Workbook
Dim wsLog As Worksheet, wsSheet1 As Worksheet, wsSheet2 As Worksheet
Dim HCdatabase2 As Variant
Dim LastRow As Long, lRow As Long
Dim ClassIndex As Long
Oldstatusbar = Application.DisplayStatusBar
Set wbMain = Workbooks("HCdatabase2.xlsm")
Set wsLog = wbMain.Sheets("Log")
DOF = 1
StartColumn = 1
StartRow = 1
ClassIndex = 0
LastRow = wsLog.Cells(wsLog.Rows.Count, StartColumn).End(xlUp).Row
For lRow = StartRow + DOF To LastRow
Set Current_Depth = New CDepth
' organize data in Current_Depth array
With Current_Depth
If wsLog.Cells(lRow, 2) > 0 Then
.cName = wsLog.Cells(lRow, StartColumn)
.Depth = wsLog.Cells(lRow, StartColumn + 1)
.ClassVal = wsLog.Cells(lRow, StartColumn + 2)
.ClassType = 1
ReDim Preserve Depths_Arr(0 To ClassIndex)
Set Depths_Arr(ClassIndex) = Current_Depth
ClassIndex = ClassIndex + 1
Set Current_Depth = Nothing
End If
End With
Set Current_Depth = New CDepth
With Current_Depth
If wsLog.Cells(lRow, 4) > 0 Then
.cName = wsLog.Cells(lRow, StartColumn)
.Depth = wsLog.Cells(lRow, StartColumn + 3)
.ClassVal = wsLog.Cells(lRow, StartColumn + 4)
.ClassType = 2
ReDim Preserve Depths_Arr(0 To ClassIndex)
Set Depths_Arr(ClassIndex) = Current_Depth
ClassIndex = ClassIndex + 1
Set Current_Depth = Nothing
End If
End With
Next lRow
' variables for bubble-sort
Dim tmp_DepthArr As CDepth
Dim i, j As Long
' sort Depth array >> first by Name >> second by Depth
For i = LBound(Depths_Arr) To UBound(Depths_Arr) - 1
For j = i + 1 To UBound(Depths_Arr)
' first sort >> by Name
If Depths_Arr(i).cName > Depths_Arr(j).cName Then
Set tmp_DepthArr = Depths_Arr(i)
Set Depths_Arr(i) = Depths_Arr(j)
Set Depths_Arr(j) = tmp_DepthArr
Set tmp_DepthArr = Nothing
Exit For
End If
' second sort >> by Depth
If Depths_Arr(i).cName = Depths_Arr(j).cName And Depths_Arr(i).Depth > Depths_Arr(j).Depth Then
' switch position between cMilesones class array elements according to Plan Date
Set tmp_DepthArr = Depths_Arr(i)
Set Depths_Arr(i) = Depths_Arr(j)
Set Depths_Arr(j) = tmp_DepthArr
Set tmp_DepthArr = Nothing
End If
Next j
Next i
' copy sorted Depths Array back to sheet >> Modify target according to your needs
For i = LBound(Depths_Arr) To UBound(Depths_Arr)
wsLog.Cells(i + 2, StartColumn + 7) = Depths_Arr(i).cName
wsLog.Cells(i + 2, StartColumn + 8) = Depths_Arr(i).Depth
wsLog.Cells(i + 2, StartColumn + 8 + Depths_Arr(i).ClassType) = Depths_Arr(i).ClassVal
Next i
End Sub
The following cDepth Class is for storing the Table's data in an organized array with the following attributes:
Name, Depth, ClassVal and ClassType
CDepth Class code:
'private Attributes
Private pName As String
Private pDepth As Integer
Private pClassVal As Integer
Private pClassType As Integer
' --- Get/Let Methods ---
Public Property Get cName() As String
cName = pName
End Property
Public Property Let cName(value As String)
pName = value
End Property
Public Property Get Depth() As Integer
Depth = pDepth
End Property
Public Property Let Depth(value As Integer)
pDepth = value
End Property
Public Property Get ClassVal() As Integer
ClassVal = pClassVal
End Property
Public Property Let ClassVal(value As Integer)
pClassVal = value
End Property
Public Property Get ClassType() As Integer
ClassType = pClassType
End Property
Public Property Let ClassType(value As Integer)
pClassType = value
End Property