VBA Excel Заполнение ячеек на основе предыдущего существования
Я еще не видел этот адрес, но я думаю, что это может быть потому, что я не знаю, как сформулировать мою проблему кратко. Вот пример того, что я хотел бы попробовать и сделать:
Учитывая столбец, который содержит инициалы состояния, проверьте выходной лист, если это состояние было найдено ранее. Если этого не произошло, заполните новую ячейку инициалами этого состояния и инициализируйте счетчик (количество обнаруженных состояний) в единицу. Если инициалы состояния находятся в ячейке на выходном листе, увеличьте счетчик на единицу.
Таким образом, если у нас есть 50 000 (или сколько угодно) листов Excel, которые имеют состояния в случайном порядке (состояния могут или не могут повторяться), мы сможем создать чистую таблицу, которая выводит, какие состояния находятся в исходной таблице данных. и сколько раз они появлялись. Еще один способ думать об этом - кодирование сводной таблицы, но с меньшим количеством информации.
Я думал о том, как это сделать, несколькими способами, лично я думаю, что ни одна из этих идей не очень хорошая, но посмотрим.
Алгоритм 1, все 50 состояний:
- Создайте 50 строковых переменных для каждого состояния, создайте 50 длинных переменных для подсчета
- Циклически перебирайте таблицу необработанных данных, если определенное состояние найдено, тогда увеличьте соответствующее число (это потребовало бы 50 операторов if-else)
- Выходные результаты
В общем..... ужасная идея
Алгоритм 2, триггер:
- Не создавайте переменные
- Если в необработанном листе данных найдено состояние, просмотрите выходной лист, чтобы проверить, было ли это состояние найдено ранее.
- Если состояние было найдено ранее, увеличить ячейку рядом с одним
- Если состояние не было найдено ранее, измените следующую доступную пустую ячейку на инициалы состояния и инициализируйте ячейку рядом с единицей
- Вернуться к необработанным данным
В целом..... это может сработать, но я чувствую, что это будет длиться вечно, даже с необработанными таблицами данных, которые не очень большие, но имеют то преимущество, что не тратят память, как алгоритм 50 состояний и меньше строк кода
Кстати, можно ли получить доступ к ячейкам рабочей книги (или рабочей таблицы) без активации этой рабочей книги? Я спрашиваю, потому что это заставило бы второй алгоритм работать намного быстрее.
Спасибо,
Джесси Смотермон
4 ответа
Пара моментов, которые ускорят ваш код:
Вам не нужны активные рабочие книги, рабочие таблицы или диапазоны для доступа к ним, например
DIM wb as workbook DIM ws as worksheet DIM rng as range Set wb = Workbooks.OpenText(Filename:=filePath, Tab:=True) ' or Workbooks("BookName") Set ws = wb.Sheets("SheetName") Set rng = ws.UsedRange ' or ws.[A1:B2], or many other ways of specifying a range
Теперь вы можете обратиться к книге / листу / диапазону как
rng.copy
for each cl in rng.cells
etc
Цикл по клеткам очень медленный. Намного быстрее сначала скопировать данные в вариантный массив, а затем перебрать массив. Кроме того, при создании большого количества данных на листе лучше сначала создать его в массиве вариантов, а затем скопировать его на лист за один раз.
DIM v As Variant v = rng
например, если rng ссылается на диапазон 10 строк на 5 столбцов, v становится массивом от dim 1 до 10, от 1 до 5. Упомянутые вами 5 минут, вероятно, будут сокращены не более чем до секунд
Sub CountStates()
Dim shtRaw As Excel.Worksheet
Dim r As Long, nr As Long
Dim dict As Object
Dim vals, t, k
Set dict = CreateObject("scripting.dictionary")
Set shtRaw = ThisWorkbook.Sheets("Raw")
vals = Range(shtRaw.Range("C2"), _
shtRaw.Cells(shtRaw.Rows.Count, "C").End(xlUp)).Value
nr = UBound(vals, 1)
For r = 1 To nr
t = Trim(vals(r, 1))
If Len(t) = 0 Then t = "Empty"
dict(t) = dict(t) + 1
Next r
For Each k In dict.keys
Debug.Print k, dict(k)
Next k
End Sub
Я реализовал свой второй алгоритм, чтобы посмотреть, как он будет работать. Приведенный ниже код, я оставил небольшие детали в реальной проблеме, чтобы попытаться быть более понятным и добраться до основной проблемы, извините за это. С кодом ниже я добавил другие "части".
Код:
' this number refers to the raw data sheet that has just been activated
totalRow = ActiveSheet.Range("A1").End(xlDown).Row
For iRow = 2 To totalRow
' These are specific to the company needs, refers to addresses
If (ActiveSheet.Cells(iRow, 2) = "BA") Then
badAddress = badAddress + 1
ElseIf (ActiveSheet.Cells(iRow, 2) = "C") Then
coverageNoListing = coverageNoListing + 1
ElseIf (ActiveSheet.Cells(iRow, 2) = "L") Then
activeListing = activeListing + 1
ElseIf (ActiveSheet.Cells(iRow, 2) = "NC") Then
noCoverageNoListing = noCoverageNoListing + 1
ElseIf (ActiveSheet.Cells(iRow, 2) = "NL") Then
inactiveListing = inactiveListing + 1
ElseIf (ActiveSheet.Cells(iRow, 2) = "") Then
noHit = noHit + 1
End If
' Algorithm beginning
' If the current cell (in state column) has something in it
If (ActiveSheet.Cells(iRow, 10) <> "") Then
' Save value into a string variable
tempState = ActiveSheet.Cells(iRow, 10)
' If this is also in a billable address make variable true
If (ActiveSheet.Cells(iRow, 2) = "C") Or (ActiveSheet.Cells(iRow, 2) = "L") Or (ActiveSheet.Cells(iRow, 2) = "NL") Then
boolStateBillable = True
End If
' Output sheet
BillableWorkbook.Activate
For tRow = 2 To endOfState
' If the current cell is the state
If (ActiveSheet.Cells(tRow, 9) = tempState) Then
' Get the current hit count of that state
tempStateTotal = ActiveSheet.Cells(tRow, 12)
' Increment the hit count by one
ActiveSheet.Cells(tRow, 12) = tempStateTotal + 1
' If the address was billable then increment billable count
If (boolStateBillable = True) Then
tempStateBillable = ActiveSheet.Cells(tRow, 11)
ActiveSheet.Cells(tRow, 11) = tempStateBillable + 1
End If
Exit For
' If the tempState is unique to the column
ElseIf (tRow = endOfState) Then
' Set state, totalCount
ActiveSheet.Cells(tRow - 1, 9) = tempState
ActiveSheet.Cells(tRow - 1, 12) = 1
' Increment the ending point of the column
endOfState = endOfState + 1
' If it's billable, indicate with number
If (boolStateBillable = True) Then
tempStateBillable = ActiveSheet.Cells(tRow - 1, 11)
ActiveSheet.Cells(tRow - 1, 11) = tempStateBillable + 1
End If
End If
Next
' Activate raw data workbook
TextFileWorkbook.Activate
' reset boolean
boolStateBillable = False
Next
Я запустил его один раз, и, похоже, сработало. Проблема в том, что это заняло примерно пять минут или около того, исходный код занимает 0,2 (грубое предположение). Я думаю, что единственный способ заставить код работать быстрее - как-то иметь возможность не активировать две книги снова и снова. Это означает, что ответ не полный, но я отредактирую, если выясню остальное.
Заметьте, что я еще вернусь к сводным таблицам, чтобы посмотреть, смогу ли я сделать в них все, что мне нужно, на данный момент похоже, что есть пара вещей, которые я не смогу изменить, но я проверю
Спасибо,
Джесси Смотермон
Я придерживался второго алгоритма. Есть вариант словаря, который я забыл, но мне все еще не очень удобно, как он работает, и я, как правило, еще не совсем понимаю. Я немного поиграл с кодом и кое-что изменил, теперь он работает быстрее.
Код:
' In output workbook (separate sheet)
Sheets.Add.Name = "Temp_Text_File"
' Opens up raw data workbook (originally text file
Application.DisplayAlerts = False
Workbooks.OpenText Filename:=filePath, Tab:=True
Application.DisplayAlerts = True
Set TextFileWorkbook = ActiveWorkbook
totalRow = ActiveSheet.Range("A1").End(xlDown).Row
' Copy all contents of raw data workbook
Cells.Select
Selection.Copy
BillableWorkbook.Activate
' Paste raw data into "Temp_Text_File" sheet
Range("A1").Select
ActiveSheet.Paste
ActiveWorkbook.Sheets("Billable_PDF").Select
' Populate long variables
For iRow = 2 To totalRow
If (ActiveWorkbook.Sheets("Temp_Text_File").Cells(iRow, 2) = "BA") Then
badAddress = badAddress + 1
ElseIf (ActiveWorkbook.Sheets("Temp_Text_File").Cells(iRow, 2) = "C") Then
coverageNoListing = coverageNoListing + 1
ElseIf (ActiveWorkbook.Sheets("Temp_Text_File").Cells(iRow, 2) = "L") Then
activeListing = activeListing + 1
ElseIf (ActiveWorkbook.Sheets("Temp_Text_File").Cells(iRow, 2) = "NC") Then
noCoverageNoListing = noCoverageNoListing + 1
ElseIf (ActiveWorkbook.Sheets("Temp_Text_File").Cells(iRow, 2) = "NL") Then
inactiveListing = inactiveListing + 1
ElseIf (ActiveWorkbook.Sheets("Temp_Text_File").Cells(iRow, 2) = "") Then
noHit = noHit + 1
End If
If (ActiveWorkbook.Sheets("Temp_Text_File").Cells(iRow, 10) <> "") Then
tempState = ActiveWorkbook.Sheets("Temp_Text_File").Cells(iRow, 10)
If (ActiveWorkbook.Sheets("Temp_Text_File").Cells(iRow, 2) = "C") Or (ActiveWorkbook.Sheets("Temp_Text_File").Cells(iRow, 2) = "L") Or (ActiveWorkbook.Sheets("Temp_Text_File").Cells(iRow, 2) = "NL") Then
boolStateBillable = True
End If
'BillableWorkbook.Activate
For tRow = 2 To endOfState
If (ActiveSheet.Cells(tRow, 9) = tempState) Then
tempStateTotal = ActiveSheet.Cells(tRow, 12)
ActiveSheet.Cells(tRow, 12) = tempStateTotal + 1
If (boolStateBillable = True) Then
tempStateBillable = ActiveSheet.Cells(tRow, 11)
ActiveSheet.Cells(tRow, 11) = tempStateBillable + 1
End If
Exit For
ElseIf (tRow = endOfState) Then
ActiveSheet.Cells(tRow, 9) = tempState
ActiveSheet.Cells(tRow, 12) = 1
endOfState = endOfState + 1
If (boolStateBillable = True) Then
tempStateBillable = ActiveSheet.Cells(tRow, 11)
ActiveSheet.Cells(tRow, 11) = tempStateBillable + 1
End If
End If
Next
'stateOneTotal = stateOneTotal + 1
'If (ActiveSheet.Cells(iRow, 2) = "C") Or (ActiveSheet.Cells(iRow, 2) = "L") Or (ActiveSheet.Cells(iRow, 2) = "NL") Then
' stateOneBillable = stateOneBillable + 1
'End If
'ElseIf (ActiveSheet.Cells(iRow, 10) = "FL") Then
'stateTwoTotal = stateTwoTotal + 1
'If (ActiveSheet.Cells(iRow, 2) = "C") Or (ActiveSheet.Cells(iRow, 2) = "L") Or (ActiveSheet.Cells(iRow, 2) = "NL") Then
' stateTwoBillable = stateTwoBillable + 1
'End If
End If
'TextFileWorkbook.Activate
If (ActiveWorkbook.Sheets("Temp_Text_File").Cells(iRow, 2) = "C") Or (ActiveWorkbook.Sheets("Temp_Text_File").Cells(iRow, 2) = "L") Or (ActiveWorkbook.Sheets("Temp_Text_File").Cells(iRow, 2) = "NL") Then
billableCount = billableCount + 1
End If
boolStateBillable = False
Next
' Close raw data workbook and raw data worksheet
Application.DisplayAlerts = False
TextFileWorkbook.Close
ActiveWorkbook.Sheets("Temp_Text_File").Delete
Application.DisplayAlerts = True
Спасибо за комментарии и предложения. Это очень ценится как всегда.
Джесси Смотермон