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
Related
I am working with a schedule, that I have imported and formatted into my workbook.
I am wanting this to populate Phase in the upper listbox and then when a phase is selected the sub-task associated with those phases are displayed in the bottom listbox.
I want to use an array but I seem to be having problems when the columns are not next to each other or there are "gaps" with the blank cells.
My first attempt using assigning the Array to the currentregion worked but brought all columns and fields in. Listbox1 should contain (ID, PHASE NAME, DURATION, START DATE, FINISH DATE) List box 2 should when a Phase is selected contain the subtasks if any from the column to the right, listed before the next next Phase name. (ID, SUB-TASK NAME, DURATION, START DATE, FINISH DATE)
(See picture)
I have code but its more me trouble-shooting than an actual semi working script.
Dim shT As Worksheet
Dim schnumrng1 As Range
Dim schnumrng2 As Range
Dim schnumrng3 As Range
Dim schnumrng4 As Range
Dim schnumrng5 As Range
Dim schpersonrng As Range
Dim schphaserng As Range
Dim schlistrng As Range
Dim maxschnum
Dim schstatus
Dim schperson
Dim schlistnum
Dim Ar() As String
Dim i As Long
Dim j As Long
Dim rng As Range
Dim cl As Range
Dim lc
'allowevents = True
''Set Screen parameters
'Application.ScreenUpdating = False
'Application.EnableEvents = False
'
Worksheets("Schedule").Visible = True
ThisWorkbook.Worksheets("Schedule").Activate
'
Set shT = Worksheets("Schedule")
maxschnum = shT.Cells(shT.Rows.Count, "A").End(xlUp).Row
Set schnumrng = Range("B5", "B" & maxschnum)
'Set Ranges for the list box
Set schnumrng1 = Range("A5", "A" & maxschnum)
Set schnumrng2 = Range("B5", "B" & maxschnum)
Set schnumrng3 = Range("D5", "D" & maxschnum)
Set schnumrng4 = Range("E5", "E" & maxschnum)
Set schnumrng5 = Range("F5", "F" & maxschnum)
'This is static and not moving to the next line in my for statement / switched to named ranges and errors
Set rng = schnumrng1, schnumrng2, schnumrng3, schnumrng4, schnumrng5
'Set rng = Range("A5,B5,D5,E5,F5")
i = 1
j = 1
For Each lc In schnumrng
If lc <> vbNullString Then
For Each cl In rng
ReDim Preserve Ar(1, 1 To i)
Ar(j, i) = cl.Value
i = i + 1
Next cl
Else
End If
j = j + 1
Next lc
With ScheduleForm.SchMainTasklt
.ColumnCount = i - 1
.ColumnWidths = "50;150;50;50;50"
.List = Ar
End With
My problem then is two fold, trying to use the dynamic ranges or another tool Index? collection? to populate the 1st list box. 2. How to deal with blanks and noncontiguous columns when data is not separated for organization purposes.
I don't know if I figured out your intentions well.
First, only the data in column b, not empty cells, is extracted from listbox1.
Second, when listbox1 is selected, data related to listbox2 is collected through the selected listbox value.
Module Code
Place this code in the module. This is because global variables must be used.
Public vDB As Variant
Public Dic As Object 'Dictionary
Sub test()
Dim shT As Worksheet
Dim maxschnum As Long
Dim Ar() As String
Dim i As Long
Dim j As Long
Dim vC() As Variant
Dim cnt As Integer, n As Integer
Dim c As Integer
Dim s As String, s2 As String
Worksheets("Schedule").Visible = True
ThisWorkbook.Worksheets("Schedule").Activate
'
Set Dic = CreateObject("Scripting.Dictionary") 'New Scripting.Dictionary
Set shT = Worksheets("Schedule")
maxschnum = shT.Cells(shT.Rows.Count, "A").End(xlUp).Row
With shT
vDB = .Range("a5", .Range("f" & maxschnum))
End With
'vC is data colum A,B,D,E,F
vC = Array(1, 2, 4, 5, 6)
s2 = vDB(2, 2)
For i = 2 To UBound(vDB, 1)
s = vDB(i, 2) 'column B
If s = "" Then
n = n + 1
Else
If Dic.Exists(s) Then
Else
If i > 2 Then
Dic(s2) = Dic(s2) & "," & n
End If
Dic.Add s, i
s2 = s
cnt = cnt + 1
ReDim Preserve Ar(1 To 5, 1 To cnt)
For c = 0 To UBound(vC)
Ar(c + 1, cnt) = vDB(i, vC(c))
Next c
End If
n = 0
End If
Next i
Dic(s2) = Dic(s2) & "," & n
' Records information about the data in a dictionary.
' Dic is "phase neme" is Key, Item is "2,4"
' example for KICkOFF
' dic key is "KICKOFF", Item is "5,4"
' 5 is KICOFF's row number in array vDB
' 4 is the number of blank cells related to kickoff.
With ScheduleForm.SchMainTasklt
.ColumnCount = 5
.ColumnWidths = "50;150;50;60;60"
.BoundColumn = 2
'.List = Ar
.Column = Ar 'In the state that the array has been converted to row column, you can use listbox.column.
End With
End Sub
Form Code
Private Sub UserForm_Initialize()
Call test
End Sub
Private Sub SchMainTasklt_Click()
Dim s As String, sItem As String
Dim arr As Variant, vC As Variant
Dim vR() As Variant
Dim st As Long, ed As Long
Dim iLast As Long, iFirst As Long
Dim i As Long, n As Integer
Dim j As Integer
vC = Array(1, 3, 4, 5, 6) 'data colums A,C,D,E,F
s = SchMainTasklt.Value
'MsgBox s
sItem = Dic(s)
arr = Split(sItem, ",")
st = Val(arr(0))
ed = Val(arr(1))
iFirst = st + 1
iLast = st + ed
If ed = 0 Then
MsgBox "no data!!"
Exit Sub
End If
For i = iFirst To iLast
n = n + 1
ReDim Preserve vR(1 To 5, 1 To n)
For j = 0 To UBound(vC)
vR(j + 1, n) = vDB(i, vC(j))
Next j
Next i
With ListBox2
.ColumnCount = 5
.ColumnWidths = "50;150;50;60;60"
.BoundColumn = 2
.Column = vR
End With
End Sub
Result Image
When you click the "KICKOFF" , Show kickoff related data in listbox2.
I have a form where users enter the name of a project and the type of transaction.
I have written a macro that returns a selection of data from a table based on the name of the project the user entered, and it works perfectly.
Now I need to add in a function that reverses the order of that same list if the user enters a specific transaction type, it reverses the order of the same list of data.
For example, if type A returns:
Bob
Jerry
Andrew
Jeff
Then type B would reverse that order and return:
Jeff
Andrew
Jerry
Bob
The VBA I wrote for the first portion, to return the list based on project name is:
Sub finddata()
Dim projectName As String
Dim transactionType As String
Dim finalRow As Integer
Dim i As Integer
Sheets("Template_Test").Range("G10:I38").ClearContents
projectName = Sheets("Template_Test").Range("E10").Value
finalRow = Sheets("Project_Structure").Range("A20000").End(xlUp).Row
transactionType = Sheets("Template_Test").Range("E14").Value
For i = 2 To finalRow
Sheets("Project_Structure").Activate
If Cells(i, 1) = projectName Then
Sheets("Project_Structure").Range(Cells(i, 2), Cells(i, 4)).Copy
Sheets("Template_Test").Activate
Sheets("Template_Test").Range("G100").End(xlUp).Offset(1, 0).PasteSpecial xlPasteFormulasAndNumberFormats
End If
Next i
Sheets("Template_Test").Range("E10").Select
End Sub
I can get the selection to reverse order using the built in vba function strReverse and a specific range, but my data is not a consistent length of cells - sometimes it's 6 names and sometimes it's 15 - and I can't figure out how to get it to adjust the length it needs to reverse without including blank cells underneath the range.
Here is a method using the .Reverse method of ArrayList object
Option Explicit
Public Sub ReverseAList()
Dim ws As Worksheet, arr(), i As Long, aList As Object, lastRow As Long
Set ws = ThisWorkbook.Worksheets("Sheet1")
Set aList = CreateObject("System.Collections.ArrayList")
With ws
lastRow = .Cells(.Rows.Count, "A").End(xlUp).Row 'whichever column is required to determine last row. Assumes there are headers in row1
If lastRow = 2 Then arr(0) = .Range("A2").Value
arr = .Range("A2:A" & lastRow).Value
For i = LBound(arr, 1) To UBound(arr, 1)
aList.Add arr(i, 1)
Next i
aList.Reverse
For i = 0 To aList.Count - 1
arr(i + 1, 1) = aList(i)
Next
.Cells(2, 2).Resize(aList.Count, 1) = arr
End With
End Sub
Data and output
Same thing re-writing a sub by Ryan Wells as a function:
Public Sub ReverseAList2()
Dim ws As Worksheet, arr(), i As Long, aList As Object, lastRow As Long
Set ws = ThisWorkbook.Worksheets("Sheet1")
With ws
lastRow = .Cells(.Rows.Count, "A").End(xlUp).Row 'whichever column is required to determine last row. Assumes there are headers in row1
If lastRow = 2 Then arr(0) = .Range("A2").Value
arr = .Range("A2:A" & lastRow).Value
.Cells(2, 2).Resize(UBound(arr), 1) = ReverseArray(arr)
End With
End Sub
Public Function ReverseArray(vArray As Variant) As Variant
Dim vTemp As Variant, i As Long, iUpper As Long, iMidPt As Long
iUpper = UBound(vArray, 1)
iMidPt = (UBound(vArray, 1) - LBound(vArray, 1)) \ 2 + LBound(vArray)
For i = LBound(vArray) To iMidPt
vTemp = vArray(iUpper, 1)
vArray(iUpper, 1) = vArray(i, 1)
vArray(i, 1) = vTemp
iUpper = iUpper - 1
Next i
ReverseArray = vArray
End Function
the code below works fine apart from one thing, the names are copied to the second sheet in the same position as they are in in the first sheet so I end up with this.
As you can see there are loads of blanks, what I need it to end up like is,
This
There are three parts to the code as you can see
1 gather names and status
2 test the availability of the person and write their name to the second sheet if they are available
3 clear out the blanks
Is there any way I can amend the line;
Activecell.offset to place the name in the next available cell in each column as it cycles through?
I can’t use the “clear the blanks” as it screws up all the buttons positions in the second sheet
Code
Option Explicit
Sub Copy_all_available_names_to_sorted_sidesmen_50()
'record all the names and availability into a single array
Dim AllData() As Variant
Dim Name As Long, Status As Long
Dim Storedname As String
Dim Storedstatus As String
Dim nameindex As Long
Sheets("Everyones Availability").Select
Name = Range("A3", Range("A3").End(xlDown)).Count - 1
Status = Range("a3", Range("a3").End(xlToRight)).Count - 1
ReDim AllData(0 To Name, 0 To Status)
For Name = LBound(AllData, 1) To UBound(AllData, 1)
For Status = LBound(AllData, 2) To UBound(AllData, 2)
AllData(Name, Status) = Range("A3").Offset(Name, Status).Value
Next Status
Next Name
Sheets("Sorted sidesmen").Select
Range("A3").Select
For Name = LBound(AllData, 1) To UBound(AllData, 1)
For Status = LBound(AllData, 2) To UBound(AllData, 2)
Storedname = AllData(Name, 0)
Storedstatus = AllData(Name, Status)
If Storedstatus = "Available" Then
ActiveCell.Offset(1, 0)(Name, Status).Value = Storedname
End If
Next Status
Next Name
Dim rng As Range
On Error GoTo NoBlanksFound
Set rng = Range("a3:z46").SpecialCells(xlCellTypeBlanks)
On Error GoTo 0
rng.Rows.Delete shift:=xlShiftUp
NoBlanksFound:
MsgBox "All Blanks have been removed"
End Sub
Thank you for looking and help you may be able to give
This should work
Option Explicit
Public Sub CopyAllAvailableNamesToSortedSidesmen50()
Dim wsEA As Worksheet: Set wsEA = ThisWorkbook.Worksheets("Everyones Availability")
Dim wsSS As Worksheet: Set wsSS = ThisWorkbook.Worksheets("Sorted sidesmen")
Dim topEAcel As Range: Set topEAcel = wsEA.Cells(3, "A")
Dim topSScel As Range: Set topSScel = wsSS.Cells(3, "A")
Dim lrEA As Long: lrEA = wsEA.Cells(wsEA.Rows.Count, "A").End(xlUp).Row
Dim lcEA As Long: lcEA = wsEA.Cells(1, wsEA.Columns.Count).End(xlToLeft).Column
wsSS.Range(topSScel, wsSS.Cells(lrEA, lcEA)).ClearContents 'clear Sorted sidesmen
Dim arrEA As Variant: arrEA = wsEA.Range(topEAcel, wsEA.Cells(lrEA, lcEA))
Dim arrSS As Variant: arrSS = wsSS.Range(topSScel, wsSS.Cells(lrEA, lcEA))
Dim rEA As Long, cEA As Long, rSS As Long
For cEA = 2 To lcEA 'by columns
rSS = 1
For rEA = 1 To lrEA - 2 'by rows
If arrEA(rEA, cEA) = "Available" Then
arrSS(rSS, cEA) = arrEA(rEA, 1) 'copy available names
rSS = rSS + 1
End If
Next
Next
wsSS.Range(topSScel, wsSS.Cells(lrEA, lcEA)).Value2 = arrSS 'paste in wsSS
End Sub
Sheet1 ("Everyones Availability")
Sheet2 ("Sorted sidesmen")
Key items in code:
Last Row on "Everyones Availability": lrEA
Last Col on "Everyones Availability": lcEA
lrEA = wsEA.Cells(wsEA.Rows.Count, "A").End(xlUp).Row
lcEA = wsEA.Cells(1, wsEA.Columns.Count).End(xlToLeft).Column
Note: initial methods (xlDown, and xlToRight) were causing issues with empty cells
- All data on "Everyones Availability": arrEA = Variant Array (copy from)
- All data on "Sorted Sidesmen": arrSS = Variant Array (copy to; empty before copy)
arrEA = wsEA.Range(topEAcel, wsEA.Cells(lrEA, lcEA))
arrSS = wsSS.Range(topSScel, wsSS.Cells(lrEA, lcEA)) 'Same size as arrEA
If arrEA(rEA, cEA) = "Available" Then
arrSS(rSS, cEA) = arrEA(rEA, 1) 'copy names
rSS = rSS + 1 'separate row counter for "Sorted sidesmen", increment only if "Available"
End If
Could you simply sort the output in the final sheet?
Option Explicit
Public Sub Ordering()
Dim col As Range, lastRow As Long
With ThisWorkbook.Worksheets("Sheet1") 'change as appropriate
lastRow = .UsedRange.SpecialCells(xlLastCell).Row
For Each col In Intersect(Range("A:D"), .UsedRange).Columns
.Range(.Cells(3, col.Column), .Cells(lastRow, col.Column)).Sort Key1:=.Range(.Cells(3, col.Column), .Cells(lastRow, col.Column)), Order1:=xlAscending, Header:=xlNo ' 'Sort to ensure in order
Next col
End With
End Sub
Before:
After:
This code should do what you need:
Assuming your source sheet is called "Everyones Availability" and new sheet "Sorted sidesmen"
Sub copy_to_newsheet()
Dim i, j, lr, lc, newlr, newlc As Long
Sheets("Sorted sidesmen").Cells.ClearContents
lr = Sheets("Everyones Availability").Range("A10000").End(xlUp).Row '' your last row
lc = Sheets("Everyones Availability").Range("A1").End(xlToRight).Column '' your last column
Sheets("Everyones Availability").Range(Cells(1, 1), Cells(2, lc)).Copy
Sheets("Sorted sidesmen").Cells(1, 1).PasteSpecial Paste:=xlPasteValues
For j = 1 To lc
For i = 3 To lr
Sheets("Sorted sidesmen").Select
Cells(1, j).Select
newlr = Selection.End(xlDown).Row '' your new last row
newlc = Selection.End(xlToRight).Column '' your new last column
If Sheets("Everyones Availability").Cells(i, j).Value = "" Then GoTo thenexti
Sheets("Everyones Availability").Cells(i, j).Copy
Sheets("Sorted sidesmen").Cells(newlr + 1, j).PasteSpecial Paste:=xlPasteValues
thenexti:
Next
Next
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 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