Страницы: 1
RSS
ВПР большого массива к большому массиву, Самы быстрый способ собрать данные между двумя большими массивами
 
Всем добрый день.

На форуме есть похожие темы но не совсем то что нужно потому что, как правило, разговор идет о том что нужно подтянуть данные к маленькому массиву из большого.

В моем случае есть два массива
первый на 3+ млн строк в нем даты и номера - по нему нужно вести поиск
второй на 500 тыс. строк к нему нужно подтянуть по номерам даты из перового массива

Так как массив по которому ведется поиск очень большой я разбил его на несколько листов. Соответственно обычный  ВПР работает очень медленно, после просчета формулы по одному листу, делаю формулы значениями и прописываю новый впр для другого листа для тех значений которые не были найдены в первом.

Можно ли как-то ускорить весь это процесс? Может быть через какой-то хитрый макрос VBA, обычный цикл как я понял скорости не прибавит?
Изменено: Starik19 - 27.04.2024 10:39:34
 
Хитрости в макросе будет не много, но макрос такое может сделать.
Приложите файл, оставьте строк по 10 на каждом листе, чтоб понять, как у вас данные расположены.
 
Цитата
написал:
Приложите файл, оставьте строк по 10 на каждом листе, чтоб понять, как у вас данные расположены.
Добавил файлы примеры.
Из первого нужно тянуть данные во второй.

Дополнительно хотел спросить. Если оба файла открыты (и тот в который тянутся данные и тот из которого данные берутся) есть ли разница находятся они на локальном диске или на сетевом? Ускорит ли работу перемещения обоих файлов на локальный диск?
Изменено: Starik19 - 27.04.2024 10:48:38
 
Ключ в столбце 1?
Значения на разных листах разные. У одного ключа.
ВПР не так работает.
 
Цитата
написал:
Если оба файла открыты (и тот в который тянутся данные и тот из которого данные берутся) есть ли разница находятся они на локальном диске или на сетевом? Ускорит ли работу перемещения обоих файлов на локальный диск?
Если уже открыты оба, разницы нет. Если есть возможность выбора, открыть из сети или открыть локально, то лучше открывать локально.
 
МатросНаЗебре, Ключ в первом столбце. Данные на разных листах так как искать нужно в таблице размером более 1,4 млн строк т.е. все на один лист не помещается. Тянуть нужно данные во второй файл. Нужные ключи могут быть на любом из листов первого файла. Возвращать нужно 5-й столбец.
 
Starik19, здравствуйте
Подтягивание или агрегация чего-то по ключу — это всегда история про словари/Dictionary (если мы про VBA) или их аналоги.
Штатным аналогом словарей является коллекция, но она сильно медленнее и не такая удобная.
Самый лучший вариант (для VBA) — использование суперсловаря/карты (UnOrdered Map) от bedvit. У него нет ограничения на количество ключей.
У штатного словаря такого ограничения [как бы] тоже нет, но замечено, что на 100 тыс ключей он начинает тормозить. Если использовать "обычные словари", то я бы сделал массив типа словарь (Dim aDic() As Dictionary), наполняя каждый элемент (словарь) до 80-100 тыс, и, при поиске искал бы в цикле по всем заполненным элементам массива словарей (словарям).

Так как словари это подключаемая штатная библиотека (Microsoft Scripting Runtime), я рекомендую раннее связывание (галочка в Tools → References) — скорость и удобство возрастут.
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
 
Jack Famous, Спасибо. Попробую найти простые примеры - VBA не силен.
 
Starik19, поищите у меня, например (ссылка — не уверен, что сработает)
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
 
Starik19, power query вам в помощь - даже если не сталкивались - откройте любой урок для начинающих на ютубе с объединением таблиц - если ваши данные в обеих таблицах исключительно такие, как в примерах, то все будет летать на среднем ПК. Все делается за пару минут только мышкой.
Изменено: voler83 - 27.04.2024 11:55:19
 
voler83, ну, как-то вы прям необъективно подошли.
Да, в PQ можно путём [относительно] несложных манипуляций [практически только] одной мышью сделать N запросов для N листов с данными таблицы 1. Потом сделать запрос, объединяющий предыдущие N запросов в один. Потом сделать запрос ко 2ой таблице. Потом объединить 1ю и 2ю таблицу, причесать полученный результат и построить сводную на его основе.
Да, для человек, который полный ноль и в VBA, и PQ, вариант с PQ будет гораздо проще.

Но вы сильно преувеличиваете насчёт "будет летать на среднем ПК. Все делается за пару минут только мышкой".
Обновление запросов займёт от нескольких секунд до минут (зависит от множества факторов).
О "паре минут только мышкой" я написал выше.
Изменено: Jack Famous - 27.04.2024 12:09:25
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
 
ВПР для 5 миллионов строк.
Код
Option Explicit

Private Const xKey = 1 'Столбец ключа
Private Const xValue = 5 'Столбец значения

Sub myVLOOKUP()
    Dim file1 As String
    file1 = ThisWorkbook.Path & "\Первый.xlsx"

    Dim file2 As String
    file2 = ThisWorkbook.Path & "\Второй.xlsx"
    
    Dim Application_Calculation As XlCalculation
    Application_Calculation = Application.Calculation
    Application.Calculation = xlCalculationManual

    Dim dic As Object
    Set dic = GetDic(file1)
    If Not dic Is Nothing Then
        If dic.Count > 0 Then
            FillFromDic file2, dic
        End If
    End If
    Application.Calculation = Application_Calculation
End Sub

Private Sub FillFromDic(sFull As String, dic As Object)
    Dim wb As Workbook
    Set wb = GetWb(sFull, True)
    If wb Is Nothing Then Exit Sub
        
    FillSheet wb.Sheets(1), dic
End Sub

Private Sub FillSheet(sh As Worksheet, dic As Object)
    Const nStep = 10000
    Dim iStep As Long
    
    Dim krr As Variant
    Dim vrr As Variant
    
    
    With sh
        Dim yMax As Long
        yMax = .Cells(.Rows.Count, 1).End(xlUp).Row
        Dim ys As Long
        iStep = nStep
        For ys = 1 To yMax Step nStep
            
            If ys + nStep - 1 > yMax Then
                iStep = yMax - ys + 1
            End If
            
            krr = GetArrayFromRange(.Cells(ys, 1).Resize(iStep))
            ReDim vrr(1 To UBound(krr, 1), 1 To 1)
            
            FillValueArray dic, krr, vrr
            
            .Cells(ys, 2).Resize(UBound(vrr, 1)).Value = vrr
        Next
    End With
End Sub

Private Sub FillValueArray(dic As Object, krr As Variant, vrr As Variant)
    Dim aKey As Variant
    Dim yk As Long
    For yk = 1 To UBound(krr, 1)
        If krr(yk, 1) <> "" Then
            aKey = GetKeyArray(CStr(krr(yk, 1)))
                 
            On Error Resume Next
            vrr(yk, 1) = dic.Item(aKey(1)).Item(aKey(2)).Item(aKey(3)).Item(aKey(4)).Keys()(0)
            On Error GoTo 0
        End If
    Next
End Sub

Private Function GetDic(sFull As String) As Object
    Dim dic As Object
    Set dic = CreateObject("Scripting.Dictionary")
    
    Dim wb As Workbook
    Set wb = GetWb(sFull, True)
    If Not wb Is Nothing Then
        Dim sh As Worksheet
        For Each sh In wb.Worksheets
            FillDicFromSheet dic, sh
        Next
        If wb.ReadOnly Then wb.Close False
    End If
    
    Set GetDic = dic
End Function

Private Sub FillDicFromSheet(dic As Object, sh As Worksheet)
    Dim tb As ListObject
    For Each tb In sh.ListObjects
        FillDicFromListObject dic, tb
    Next
End Sub

Private Sub FillDicFromListObject(dic As Object, tb As ListObject)
    Const nStep = 10000
    Dim iStep As Long
    Dim krr As Variant
    Dim vrr As Variant
    Dim yt As Long
    iStep = nStep
    For yt = 1 To tb.DataBodyRange.Rows.Count Step nStep
        If yt + nStep - 1 > tb.DataBodyRange.Rows.Count Then
            iStep = tb.DataBodyRange.Rows.Count - yt + 1
        End If
    
        krr = GetArrayFromRange(tb.DataBodyRange.Cells(yt, xKey).Resize(iStep))
        vrr = GetArrayFromRange(tb.DataBodyRange.Cells(yt, xValue).Resize(iStep))
        
        FillDicFromArrays dic, krr, vrr
    Next
End Sub

Private Sub FillDicFromArrays(dic As Object, krr As Variant, vrr As Variant)
    Dim yk As Long
    For yk = 1 To UBound(krr, 1)
        If krr(yk, 1) <> "" Then
            AddDicItem dic, krr(yk, 1), vrr(yk, 1)
        End If
    Next
End Sub

Private Sub AddDicItem(dic As Object, ByVal sKey As String, vValue As Variant)
    
    Dim aKey As Variant
    aKey = GetKeyArray(sKey)
    
    ReDim Preserve aKey(LBound(aKey) To UBound(aKey) + 1)
    aKey(UBound(aKey)) = vValue
    
    DicAdd dic, aKey
End Sub

Private Function GetKeyArray(sKey As String) As Variant
    Dim longKey As String
    longKey = GetKey(sKey)
    
    Dim arr As Variant
    ReDim arr(1 To 4)
    Dim ya As Long
    For ya = LBound(arr) To UBound(arr) - 1
        arr(ya) = Mid(longKey, 1 + 4 * (ya - 1), 4)
    Next
    arr(ya) = Mid(longKey, 1 + 4 * (ya - 1), Len(longKey))
    
    GetKeyArray = arr
End Function

Private Function GetKey(ByVal sKey As String) As String
    Dim ss As String
    If Len(sKey) < 16 Then
        ss = sKey & String(15, "_")
        ss = Left(ss, 16)
    Else
        ss = sKey
    End If
    GetKey = ss
End Function

Private Function GetWb(ByVal sFull As String, bReadOnly As Boolean) As Workbook
    Dim fso As Object
    Set fso = CreateObject("Scripting.FileSystemObject")
    
    If Not fso.FileExists(sFull) Then Exit Function
    Dim sName As String
    sName = fso.GetFileName(sFull)
    
    Dim wb As Workbook
    On Error Resume Next
    Set wb = Workbooks(sName)
    On Error GoTo 0
    If Not wb Is Nothing Then
        If LCase(wb.FullName) <> LCase(sFull) Then
            wb.Close False
            Set wb = Nothing
        End If
    End If
    If wb Is Nothing Then
        Set wb = Workbooks.Open(sFull, False, bReadOnly)
    End If
    
    Set GetWb = wb
End Function

Private Function GetArrayFromRange(rr As Range) As Variant
    Dim arr As Variant
    If rr.Cells.CountLarge = 1 Then
        ReDim arr(1 To 1, 1 To 1)
        arr(1, 1) = rr.Value
    Else
        arr = rr.Value
    End If
    ClearArray arr
    GetArrayFromRange = arr
End Function

Private Sub ClearArray(arr As Variant)
    Dim ya As Long
    Dim xa As Long
    For ya = LBound(arr, 1) To UBound(arr, 1)
        For xa = LBound(arr, 2) To UBound(arr, 2)
            If IsError(arr(ya, xa)) Then
                arr(ya, xa) = Empty
            End If
        Next
    Next
End Sub

Private Sub DicAdd(dic As Object, arr As Variant)
    DicAddRecu dic, LBound(arr), arr
End Sub
Private Sub DicAddRecu(dic As Object, level As Long, arr As Variant)
    If Not dic.Exists(arr(level)) Then
        Set dic.Item(arr(level)) = CreateObject("Scripting.Dictionary")
    End If
    If level < UBound(arr) Then
        Dim bic As Object
        Set bic = dic.Item(arr(level))
        DicAddRecu bic, level + 1, arr
        
        Set dic.Item(arr(level)) = bic
    End If
End Sub
 
Jack Famous, про пару минут - кажется, всего 2 таблицы, по два столбца, может не вник
про скорость - должно именно летать, т.к. тормоза на join (по опыту на работе с таблицами на миллионы строк и сотни столбцов) не зависят от кол-ва строк, а зависят от кол-ва столбцов, т.е. в целом от "тяжести" соединяемых таблиц (только так поверхностно могу описать) и от типа данных и их кол-ва в каждой "ячейке" (если говорим например о типе "текст"), т.е. по сути тоже от "тяжести" таблиц. У автора один столбец в целевой таблице с одним типом данных, и неск. столбцов с типом "число" в таблице, по кот. делается поиск - должно все летать.
Может я не оч. прав, но по моим экспериментам кол-во строк в общем случае значения не имеет.
Изменено: voler83 - 27.04.2024 12:24:44
 
Странно, был на 100% уверен что видел среди ответов файлы с макросом и даже вроде как пробовал их и все работало, а сейчас найти не могу.
 
МатросНаЗебре, Скопировал ваш код в файл, запустил. Код отработал без ошибок но почему-то не выдал никакого результата.

Прошу прощения, чтобы макрос заработал я сохранил файл "Второй" с другим расширением (.xlsm) а в переменных забит .xlsx
Код
Option Explicit

Private Const xKey = 1 'Столбец ключа
Private Const xValue = 5 'Столбец значения

Sub myVLOOKUP()
    Dim file1 As String
    file1 = ThisWorkbook.Path & "\Первый.xlsx"

    Dim file2 As String
    file2 = ThisWorkbook.Path & "\Второй.xlsx"
Изменено: Starik19 - 08.05.2024 17:16:16
 
МатросНаЗебре, Подскажите, пожалуйста, какая строчка кода отвечает за то в какую ячейку нужно возвращать результат найденный по ключу.
 
Starik19,  какой результат нужен?
Может такой:
https://i.imgur.com/CHfppYk.png


Если мешают лишние столбцы - можно в коде их убрать, но наверное потеряется универсальность UDF. Или придётся ещё один массив с листа брать в аргумент с перечнем что выкидывать...
Или просто делать строго на два столбца - с критерием и с номером ещё одного допаргумента.
Изменено: Hugo - 08.05.2024 18:44:22
 
Цитата
Jack Famous написал:
voler83 , ну, как-то вы прям необъективно подошли.
Да отличный подход.
Более того, данные на 1+ млн строк тогда хранить в csv без ограничения числа строк.
Одна из таблиц маленькая, джоин шустро отработает.

Возможно, быстрее будет из "маленькой" (к которой "ВПРим") сделать запить rec = [xxxx_xxxx = true, xxxx_xxxy = true, ...] и фильтровать строки "большой" таблицы
Код
Table.SelectRows ( data, (r) => Record.FieldOrDefault ( rec, r[1], false ) )
.

*Если ТС согласится, что данные будут в csv, то реализуем.
Изменено: surkenny - 08.05.2024 19:30:01
 
Цитата
Starik19 написал:
первый на 3+ млн
Чисто гипотетически Select ... LEFT ... JOIN  ....UNION должно прокатить без проблем на простом QUERY.
По вопросам из тем форума, личку не читаю.
 
Забыл добавить - у меня там выше на скрине всего одна формула, а не 500к ВПР().
И она просматривает весь массив всего один раз, а не 500к раз.
Страницы: 1
Наверх