Добрый день, уважаемые коллеги! Столкнулся со следующей проблемой. Есть 2 листа (№1 и №2) Excel с разным (но фиксированным) количеством столбцов и очень большим (и не фиксированным) количеством строк. В листе №1 количество строк 50000-150000, в листе №2 - до 100000. В каждом листе есть заполненный столбец уникальных идентификаторов (строка из 12 цифр), уникальность и наличие значения в нужном формате в этом столбце гарантируются. Есть макрос, который проверяет наличие идентификаторов из Листа№2 в Листе№1 и при наличии совпадения пишет сводные данные в Лист №3. Макрос работает, но адски медленно: при 118000 строк в Листе №1 и 50000 строк в Листе №2 выполнение макроса занимает у меня около 1.5-2 часов. Я попробовал переписать его так, чтобы в цикле заполнялся двумерный динамический массив, и потом этот массив вставлялся целым куском в Лист №3, однако, сколько-нибудь заметного прироста производительности это не дало, т.к. из-за невозможности динамического изменения первой размерности динамического массива с сохранением данных, получившийся массив приходится транспонировать перед вставкой - тоже в цикле, т.к. Application.transpose() с такими количествами строк не работает. Пробовал отключать/останавливать фоновые процессы вроде обновления экрана - вот до 1,5-2 часов получилось упасть, было дольше. Вопрос, можно ли разогнать макрос ещё больше, хотя бы в 2-3 раза? В этой теме http://www.planetaexcel.ru/forum/index.php?PAGE_NAME=message&FID=1&TID=77861&... подсмотрел идею поделить массив на блоки и работать с ними, но в моём случае это не прокатит, т.к. строки между собой не группируются, и все являются уникальными.
Буду признателен за любые дельные советы по ускорению алгоритма. Сам сейчас думаю в направлении того, чтобы отсортировать самый большой из списков идентификаторов и проходить по нему алгоритмом быстрого поиска, а не перебором.
P.S. В примере, разумеется, все данные вымышленные и длина списков минимальная. Макрос там же.
Application.Transpose() может не потянуть большие объёмы. И ReDim Preserve ворует время, если часто и помногу... Обычно если возможно мы используем тот же исходный или проверяемый массив, складывая всё в начало массива, затем эту шапку легко выгрузить. Т.к. файл взять не могу - без кода...
Sub qq()
Dim ar1, ar2, ar3
With Worksheets("Лист №1").Cells(1, 1).CurrentRegion: ar1 = .Offset(1).Resize(.Rows.Count - 1).Value: End With
With Worksheets("Лист №2").Cells(1, 1).CurrentRegion: ar2 = .Offset(1).Resize(.Rows.Count - 1).Value: End With
With CreateObject("Scripting.Dictionary")
If UBound(ar1) > UBound(ar2) Then
ReDim ar3(1 To UBound(ar1), 1 To 7)
For i = 1 To UBound(ar1)
.Item(ar1(i, 2)) = i
Next
For i = 1 To UBound(ar2)
If .exists(ar2(i, 2)) Then
k = k + 1
ar3(k, 1) = ar1(.Item(ar2(i, 2)), 1)
ar3(k, 2) = "'" & ar2(i, 2)
ar3(k, 3) = ar1(.Item(ar2(i, 2)), 3)
ar3(k, 4) = ar2(i, 3)
ar3(k, 5) = ar2(i, 1)
ar3(k, 6) = ar2(i, 4)
ar3(k, 7) = ar1(.Item(ar2(i, 2)), 4)
End If
Next
Else
ReDim ar3(1 To UBound(ar2), 1 To 7)
For i = 1 To UBound(ar2)
.Item(ar2(i, 2)) = i
Next
For i = 1 To UBound(ar1)
If .exists(ar1(i, 2)) Then
k = k + 1
ar3(k, 5) = ar2(.Item(ar1(i, 2)), 1) '5
ar3(k, 2) = "'" & ar1(i, 2)
ar3(k, 4) = ar2(.Item(ar1(i, 2)), 3) '4
ar3(k, 3) = ar1(i, 3) '3
ar3(k, 1) = ar1(i, 1) '1
ar3(k, 7) = ar1(i, 4) '7
ar3(k, 6) = ar2(.Item(ar1(i, 2)), 4) '6
End If
Next
End If
End With
Worksheets("Лист №3").Cells(2, 1).Resize(k, 7) = ar3
End Sub
Sanja, спасибо, идея со словарём мне нравится. Во всяком случае, замена итераций в цикле поиском по коллекции может дать некоторый бонус в скорости. Как протестирую - напишу результат.
Однако, Application.Transpose(), к сожалению, не вариант, т.к. не работает более чем с 65536 элементами (писал об этом выше). А я не могу быть уверен в том, сколько строк у меня совпадёт - может ни одной, может все ~100000. Реализовать транспонирование динамического массива собственной функцией, а не Transpose() пробовал, но это ещё один перебор циклом, который почти сводит на нет преимущества замены записи прямо на лист вставкой динамического массива. Ну, или каким-то образом попытаться вставлять по <= 65536 элементов %)...
RAN, skais675, спасибо, попробую - потом отпишусь о результатах теста.
Hugo, самого посещают такие мысли, и в связи с этим думаю: а если сразу объявить динамический массив максимальной возможной длины (у меня, например, по определению, не может быть больше строк вывода, чем строк в самом маленьком списке) и потом его один раз обрезать до заполненной части (или даже не обрезать, хоть это и грязновато, зато сэкономим на транспонировании), не получится ли гораздо быстрее, чем с ReDim Preserve 100k раз?
Option Explicit
Sub test()
Dim dic As Object
Dim arr(), i&, j&, x&
Set dic = CreateObject("Scripting.Dictionary")
arr = Лист1.UsedRange.Value
For i = 1 To UBound(arr)
dic.Item(CStr(arr(i, 2))) = 0
Next i
arr = Лист2.UsedRange.Value
For i = 1 To UBound(arr)
If dic.Exists(CStr(arr(i, 2))) Then
j = j + 1
For x = 1 To UBound(arr, 2)
arr(j, x) = arr(i, x)
Next
End If
Next i
With Лист3
.Cells.ClearContents
.Columns(2).NumberFormat = "@"
.Range("a1").Resize(j, UBound(arr, 2)) = arr
.Cells.EntireColumn.AutoFit
End With
End Sub
Nordheim Если Вы сравнивали одни и те же строки (100k), то к чему это все, на боевом согласен - нужно проверить и после этого радоваться. Не проверял, но думаю sql все равно быстрее будет.
Тоже сделал. 200000 в одной таблице и столько же в другой. Общих по Идентификатору 99551. Excel 2010 32bit. Power Query создал таблицу за 11 секунд, SQL 24 секунды. На 100000 и 50000 (общих 12420), PQ 3 секунды, SQL 5 секунд.
Irregular Expression написал: Есть макрос, который проверяет наличие идентификаторов из Листа№2 в Листе№1 и при наличии совпадения пишет сводные данные в Лист №3.
Код из четвертого сообщения в столбец район вставляет фамилии. Если таблицы идентичны, то итоговый лист должен быть с идентичными столбцами, тогда и сложный макрос не нужен
"Все гениальное просто, а все простое гениально!!!"
Вот еще вариант решения без использования дополнительных средств и надстроек. При количестве записей "Лист №1" ~100000, "Лист №2" ~ 50000 - на доступных машинах результат был от 1 до 2 секунд.
Итак, отчитываюсь по порядку. Для теста мною была взята копия рабочей базы, где в Листе №1 было 157899 строк, а в листе №2 - 30991 строка.
Макрос от Nordheim 1) 5,88 секунд (это круто, имхо!) 2) Найдено 1167 совпадений (все имеющиеся). 3) Выгружены не все данные (только 2 из 6 столбцов), + потёрлась шапка, но это мелочи .
Макрос от RAN, 1) 5,5 секунд (глазам не поверил !) 2) Найдено 1167 совпадений (все имеющиеся). 3) Выгружены данные по всем 7 столбцам, шапка листа №3 уцелела .
Макрос от Sanja, 1) 1,84 секунды 2) Не найдено и не выгружено ничего . Отключив команду On Error Resume Next, видно, что строка
кидает Subscript out of range error. В деталях надо разбираться
Решение от skais675, 1) 34 секунды (засекать пришлось по часам, а не таймером VBA, так что погрешность выше) 2) Данные выгрузились полностью, правда, идентификаторы по 2 раза, но это мелочи .
Решение от Андрей VG протестировать не удалось Excel стал ругаться и требовать админский доступ, которого у меня на работе нет. Впрочем, по ряду причин (сводимые базы насквозь кривые и надо иметь возможность быстро адаптировать к ним код, а не их к листу с соединением) решение нужно именно макросом.
Таким образом, самый быстрый и качественный результат показал макрос RAN, которому от меня большой респект и искренняя признательность ! Всем спасибо!