Как объединить похожие записи в отсортированном списке без вывода на лист с помощью VBA/Excel
У меня есть массив, который хранит его значения в отсортированном списке. Я использовал этот отсортированный список для организации данных по дате в нескольких других электронных таблицах.
Мои исходные данные - это серия из 12 листов в одной книге. Каждый лист отражает один календарный месяц. Количество транзакций / прогонов является динамическим - в среднем около 60 или около того в месяц, поэтому я установил ограничение на цикл в 200, так как этого должно быть более чем достаточно, чтобы покрыть любой рост в бизнесе.
Мой текущий набор данных таков, что у меня есть несколько повторных доставок (разные грузы / веса и др., Но в одном и том же месте доставки). Я хочу объединить эти "повторяющиеся"/ похожие строки в одну запись в списке, суммировать количество доставленных деталей, вес и стоимость доставки, а также увеличить счетчик, чтобы показать количество повторных доставок на соответствующий сайт.
Example: January, 2016
Delivered from: Delivered to: No. Pieces: Weight: Cost:
Site A Site B 10 100 $120.00
Site A Site C 5 20 $80.00
Site B Site C 2 30 $45.00
Site A Site C 20 460 $375.00
Summary:
Delivered to: No. of Deliveries: No. Pieces: Weight: Cost:
Site B 1 10 100 $120.00
Site C 3 27 510 $500.00
Я могу придумать, как это сделать, выгрузив данные на лист "макулатуры", однако мне нужно решение "VBA", которое является "внутренним", так что такая "блокнот" не требуется.
Количество поставок, в целом, является динамическим. Количество повторных поставок для любого заданного местоположения также является динамическим.
Мне очень сложно составить эффективный способ объединения информации в моем списке с указанными выше параметрами, так как я все еще плохо знаком с VBA/Excel.
Любые предложения приветствуются, в частности, если у вас есть пример кода - я знаю, чего хочу, я просто не уверен, как реализовать его в VBA.
Пример загрузки и переноса моего массива в список показан ниже (определения переменных и др. Опущены).
Set List = CreateObject("System.Collections.SortedList")
'Grab Monthly Data by Route
For Each ws In Worksheets
If ws.Name <> "Summary" Then
Call DeleteHidden 'Delete Hidden Rows/Columns in the active worksheet if any
With ws
'loop through the sheet to 207 (~3x greatest number of deliveries)
For RowCount = 7 To 207
'Check for dates for each row (Month/Day/Year)
d = DateValue(.Cells(RowCount, 1))
If List.Containskey(d) Then
arTemp = List(d)
Else
ReDim arTemp(12)
End If
'Monthly Totals
arTemp(0) = arTemp(0) + .Cells(RowCount, 1) 'Grab Entry Date/Time
arTemp(1) = arTemp(1) + .Cells(RowCount, 2) 'Grab Delivery Date/Time
arTemp(2) = arTemp(2) + .Cells(RowCount, 3) 'Grab PU Location
arTemp(3) = arTemp(3) + .Cells(RowCount, 4) 'Grab PU Street
arTemp(4) = arTemp(4) + .Cells(RowCount, 5) 'Grab PU City/Province/PC
arTemp(5) = arTemp(5) + .Cells(RowCount, 6) 'Grab Del Location
arTemp(6) = arTemp(6) + .Cells(RowCount, 7) 'Grab Del Street
arTemp(7) = arTemp(7) + .Cells(RowCount, 8) 'Grab Del City/Province/PC
arTemp(8) = arTemp(8) + .Cells(RowCount, 9) 'Grab No. Pieces
arTemp(9) = arTemp(9) + .Cells(RowCount, 10) 'Grab Cargo Weight (LBS)
arTemp(10) = arTemp(10) + .Cells(RowCount, 11) 'Grab Cost
'potential add point of a sort and consolidate function if working with the array prior to data being added to the list (but then such would run for each record of each worksheet---seems too inefficient)
arTemp(12) = arTemp(12) + 1
List(d) = arTemp
Next RowCount
Call QuickSort(arTemp, 0, RowCount - 1) 'Sort the Monthly Array at the end of the Month (can manipulate the array but the list is already loaded..how to manipulate/consolidate the list???)
End With
End If
Next
3 ответа
Я добавил столбец месяца в резюме.
Sub Summary()
Dim ws As Worksheet
Dim iMonth As Integer, x As Long, x1 As Long
Dim Data, key
Dim list(1 To 12) As Object
For x = 1 To 12
Set list(x) = CreateObject("System.Collections.SortedList")
Next
For Each ws In Worksheets
If ws.Name <> "Summary" Then
Call DeleteHidden 'Delete Hidden Rows/Columns in the active worksheet if any
With ws
For x = 1 To 207
If IsDate(.Cells(x, 1)) Then
iMonth = Month(.Cells(x, 1))
key = .Cells(x, 6) 'Grab Del Location
If list(iMonth).ContainsKey(key) Then
Data = list(iMonth)(key)
Else
ReDim Data(5)
Data(0) = iMonth
Data(1) = .Cells(x, 6) 'Grab Del Location
End If
Data(2) = Data(2) + 1
Data(3) = Data(3) + .Cells(x, 9) 'Grab No. Pieces
Data(4) = Data(4) + .Cells(x, 10) 'Grab Cargo Weight (LBS)
Data(5) = Data(5) + .Cells(x, 11) 'Grab Cost
list(iMonth)(key) = Data
End If
Next
End With
End If
Next
With Worksheets("Summary")
For x = 1 To 12
For x1 = 0 To list(x).Count - 1
.Range("A" & .Rows.Count).End(xlUp).Offset(1).Resize(1, 6).Value = list(x).GetByIndex(x1)
Next
Next
End With
End Sub
Используя ADO, можно рассматривать рабочую книгу Excel как базу данных и использовать для нее операторы SQL.
(У меня были проблемы с точками в имени поля, поэтому я изменил No. Pieces
в Number of Pieces
в исходных данных. Спасибо @ThomasInzina.)
SELECT [Delivered to:],
COUNT(*) AS NumberOfDeliveries,
SUM([Number of Pieces:]) AS NumberOfPieces,
SUM([Weight:]) AS SumOfWeight,
SUM([Cost:]) AS SumOfCost
FROM [January, 2016$]
GROUP BY [Delivered to:]
Первым шагом было бы получить список имен рабочих листов, используя соединение ADO.
Затем вы можете перебрать имена и выполнить оператор SQL. Данные возвращаются как Recordset
объект, который может быть легко вставлен в лист Excel с помощью CopyRecordset
метод.
Если выходные данные будут относиться к другой рабочей книге, то можно было бы сохранить рабочую книгу открытой в течение всей рабочей книги. For Each
непрерывно создавайте новые рабочие листы на каждый месяц и звоните CopyFromRecordset
на каждой итерации For Each
, Однако при одновременном доступе к одной и той же книге через автоматизацию и соединение ADO CopyFromRecordset
казалось, ничего не делал.
Поэтому мы используем отключенные наборы записей для каждого рабочего листа, которые хранят все данные в памяти даже после закрытия коллекции; и хранение ссылок на них с использованием Scripting.Dictionary, где каждый ключ - это конечное имя рабочего листа, а значение - отключенный набор записей.
Это означает, что все окончательные данные хранятся в памяти, что может быть проблемой. Возможный обходной путь - создать новую выходную рабочую книгу для хранения вставленных данных набора записей, а после завершения всех итераций и закрытия подключения вставить рабочие листы из выходной рабочей книги в исходную рабочую книгу и удалить выходную рабочую книгу. Однако в вопросе вы указали, что не хотите этого делать.
Добавьте ссылки (Инструменты -> Ссылки...) к объектам данных Microsoft ActiveX (выберите последнюю версию; обычно это 6.1) и Microsoft Scripting Runtime.
Dim pathToWorkbook As String
pathToWorkbook = "C:\path\to\workbook.xlsx"
Dim conn As New ADODB.Connection
Dim schema As ADODB.Recordset
Dim sheetname As Variant
Dim sql As String
Dim rs As ADODB.Recordset
Dim dict As New Scripting.Dictionary
With conn
.Provider = "Microsoft.ACE.OLEDB.12.0"
.ConnectionString = "Data Source=""" & pathToWorkbook & """;" & _
"Extended Properties=""Excel 12.0;HDR=Yes"""
.Open
Set schema = .OpenSchema(adSchemaTables)
For Each sheetname In schema.GetRows(, , "TABLE_NAME") 'returns a 2D array of one column
If Not sheetname Like "*(Summary)*" Then
sql = _
"SELECT [Delivered to:], " & _
"COUNT(*) AS NumberOfDeliveries, " & _
"SUM([Number Of Pieces:]) AS SumNumberOfPieces, " & _
"SUM([Weight:]) AS SumOfWeight, " & _
"SUM([Cost:]) AS SumOfCost " & _
"FROM [" & sheetname & "] " & _
"GROUP BY [Delivered to:]"
Set rs = New ADODB.Recordset
rs.CursorLocation = adUseClient 'This defines a disconnected recordset
rs.Open sql, conn, adOpenStatic, adLockBatchOptimistic 'Disconnected recordsets require these options
Set rs.ActiveConnection = Nothing 'Recordset disconnected
sheetname = Mid(sheetname, 2, Len(sheetname) - 3)
dict.Add sheetname & " (Summary)", rs
End If
Next
.Close
End With
Dim xlApp As New Excel.Application
xlApp.Visible = True
xlApp.UserControl = True
Dim wkbk As Excel.Workbook
Dim wks As Excel.Worksheet
Dim key As Variant
Set wkbk = xlApp.Workbooks.Open(pathToWorkbook)
For Each key In dict.Keys
Set wks = wkbk.Sheets.Add
wks.Name = key
wks.Range("A1").CopyFromRecordset dict(key)
Next
Ссылки:
MSDN:
- ADO - объекты подключения и записи
- Как создать отключенные наборы записей
- VBA
- Scripting.Dictionary
- Автоматизация Excel
Другой:
Вот более короткая более ленивая версия, которая объединит данные примера в двумерный массив, но предполагает, что A6:E6
имеет те же имена заголовков, что и в вашем примере:
Dim arr(), rs As Object: Set rs = CreateObject("ADODB.Recordset")
rs.Open "Select [Delivered to:], Count(*), Sum([No# Pieces:]), " & _
"Sum([Weight:]), Format(Sum([Cost:]),'$0.00') " & _
"From ( SELECT * From [January$A6:E207] Union All " & _
" SELECT * From [February$A6:E207] ) " & _
"Where [Delivered to:] > '' Group By [Delivered to:]", _
"Provider=MSDASQL;DSN=Excel Files;DBQ=" & ThisWorkbook.FullName
If Not rs.EOF Then arr = rs.GetRows ': For Each i In arr: Debug.Print i & " ";: Next
rs.Close: Set rs = Nothing
Если ячейки заголовка отсутствуют, для этой альтернативной версии необходимо установить поставщик ACE (поставляется с Access 2007 и более поздними версиями или может быть загружен и установлен отдельно).
rs.Open "Select F2, Count(*), Sum(F3), Sum(F4), Format(Sum(F5),'Currency') " & _
"From ( SELECT * From [January$A6:E207] Union All " & _
" SELECT * From [February$A6:E207] ) Where F2 > '' Group By F2", _
"Provider=Microsoft.ACE.OLEDB.12.0;Extended Properties='Excel 12.0;HDR=No';Data Source=" & ThisWorkbook.FullName ' ODBC Provider in case no ACE Provider