Страницы: 1
RSS
UDF получить данные из закрытой книги по номеру договора
 
Друзья, доброго времени суток!

  Хочу создать функцию, получения плательщика по номеру договора из закрытого файла, которая будет вызываться с листа (аналог ВПР, получить необходимо только первое значение, даже если оно не одно, сейчас в функции, если значение не одно оно просто красится желтым).
  Сделал свой вариант через ADO (во вложении, положить оба файла в одну папку, в книге MAIN функция GetPayer). Работает очень долго, в рабочем файле source порядка 100к строк. При каждом вызове функции формируется recordset.
  Также при каком-либо изменении на листе, связи, я так понимаю, пересчитываются...вообщем пользоваться невозможно.
  Прошу указать, что мне изменить, чтобы работало пошустрей (если возможен шустрый вариант через ADO, то прошу подсказать в этом направлении). Если по ADO в любом случае тормознуто будет получаться, то какой вариант будет для меня самым быстрым.
  Возможно, есть какие-то функции, работающие с закрытыми книгами, которые функция просто будет прописывать.
  Как-то делал нечто похожее, но не из закрытой книги и книга была одна. Здесь мне подсказали сделать Static Dictionary, для первой ячейки словарь формируется какое-то время, зато при протягивании формулы, остальные позиции подтягиваются быстро. Но в данном случае у меня, в рабочей ситуации, файл source не один, их много, файл будет определяться по условию в макросе, не делать же для каждого файла свой словарь.
  Подскажите, как бы Вы реализовали эту задачу. Благодарен любым советам.

  Спасибо.
 
я бы сделал процедуру, а не функцию. Нажал кнопочку (либо Alt+F8) - данные обновились - всё, никаких тормозов в файле
P.S. Если нажать кнопку "Сохранить" в вашем файле "Source.xlsm", то выскакивает ошибка №9 - Subscript out of range
Изменено: New - 28.10.2021 00:19:16
 
New, спасибо, посмотрю, что за ошибка.
  Нужна именно функция, делаю для женщин-бухгалтеров, им, во-первых, даже кнопку будет сложно нажать, во-вторых у всех файл (MAIN в моем случае) имеет разную структуру и не угадать куда и чего вставлять в каждом конкретном случае.
  А функция, удобно, возвращает значение туда же куда вводится, к работе с формулами многие приучены.
  Хотелось бы получить такое, функция GetPayer(Банк, Год, Месяц)...и функция идет в папку с банком, там в папку с годом, далее в папку с месяцем и там из файла получает значение.
  Вопрос в том какой способ быстрый и очень бы хотелось через ADO, по возможности, нравится этот способ, книга не открывается (дада, сейчас скажут, что она где-то как-то все-таки открывается  :) )
Изменено: whateverlover - 28.10.2021 00:28:53
 
Ну, если надоедят тормоза с функциями, то вот вам процедура
Код
Sub Get_Data_Dictionary()
    Dim SourceWB As Workbook, FSO As Object, Dict As Object, sSourceFileName As String, sPathToSourceFile As String
    Dim arrSource, arrResult, arrTemp, i As Long, iPos As Long

    sSourceFileName = "Source.xlsm" 'название файла источника
    
    sPathToSourceFile = ThisWorkbook.Path & Application.PathSeparator & sSourceFileName
    Set FSO = CreateObject("Scripting.FileSystemObject")
    If Not FSO.FileExists(sPathToSourceFile) Then
        MsgBox "Файл '" & sSourceFileName & "' не найден!", vbExclamation, "Внимание"
        Exit Sub
    End If
    Set FSO = Nothing
    Application.ScreenUpdating = False
    Set SourceWB = Workbooks.Open(sPathToSourceFile, UpdateLinks:=False, ReadOnly:=True)
    With SourceWB.Worksheets(1)
        arrSource = .Range("C2:F" & .Cells(.Rows.Count, 3).End(xlUp).Row).Value 'все данные из базы
    End With
    SourceWB.Close (False)
    With ThisWorkbook.Worksheets(1)
        If .AutoFilterMode = True Then .ShowAllData
        arrTemp = .Range("A1:A" & .Cells(.Rows.Count, 1).End(xlUp).Row).Value 'по этим данным будем вести поиск
        ReDim arrResult(1 To UBound(arrTemp), 1 To 1)
        Set Dict = CreateObject("Scripting.Dictionary")
        For i = 1 To UBound(arrSource)
            If Not Dict.Exists(arrSource(i, 1)) Then Dict.Item(arrSource(i, 1)) = i
        Next
        For i = 1 To UBound(arrTemp)
            If Dict.Exists(arrTemp(i, 1)) Then
                iPos = Dict.Item(arrTemp(i, 1))
                arrResult(i, 1) = arrSource(iPos, 4) '4 - номер столбца, из которого берём данные
            Else
                arrResult(i, 1) = CVErr(xlErrNA) '"Данных нет"
            End If
        Next
        .Range("C1").Resize(UBound(arrResult, 1), UBound(arrResult, 2)) = arrResult
    End With
    Set Dict = Nothing
    Application.ScreenUpdating = True
    'MsgBox "Данные подтянуты", vbInformation, "Конец"
End Sub

По поводу ADO
1. я бы убрал окраску ячеек
2. вынес бы объявление переменных ADO из функции
3. прописал бы проверку перед созданием CN и RS, если они Nothing, то создавал бы их
4. название файла "Source.xlsm" взял бы в константу и вынес бы из функции
Потестируйте, может ускорится...

Код
Dim Conn As ADODB.Connection, RS As ADODB.Recordset
Const FileName = "Source.xlsm"

Function GetPayer(Rng As Range) As Variant
    Dim FilePath As String
            
    FilePath = ThisWorkbook.Path & Application.PathSeparator

    If Conn Is Nothing Then Set Conn = New ADODB.Connection
    Conn.ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;" & _
        "Data Source=" & FilePath & FileName & ";" & _
        "Extended Properties='Excel 12.0 Macro;HDR=YES';"
    Conn.Open

    If RS Is Nothing Then Set RS = New ADODB.Recordset
    With RS
        .ActiveConnection = Conn
        .Source = "SELECT [Плательщик] FROM [Лист1$] WHERE [Договор] = '" & Rng.Cells(1).Value & "'"
        .CursorLocation = 3 'adUseClient
        .Open
    End With
            
    Select Case RS.RecordCount
        Case 0
            GetPayer = CVErr(xlErrNA)
        Case 1
            GetPayer = RS.Fields("Плательщик").Value
        Case Is > 1
            GetPayer = RS.Fields("Плательщик").Value
    End Select
    
    RS.Close
    Conn.Close
End Function
Изменено: New - 28.10.2021 02:32:23
 
New, огромное спасибо! Учусь по всем кодам, которые мне скидывают  :)
По поводу Вашего примера с ADO, у меня ведь, как я писал, файл source будет не один, а разный, в зависимости от аргументов функции. Соответственно имя файла в константу не имеет смысла выносить. И тогда, если я правильно понял, CN и RS тоже не стоит проверять, т.к. получил я RS из файла source1, а при следующем вызове функции мне уже надо из source2...а функция проверит, что RS не nothing, и не будет recordset собирать из файла source2.
Еще раз спасибо.  :)
А может ли функция вызвать в себе же процедуру, которая значение ячейки, из которой вызывается функция, вставит как значение?
Изменено: whateverlover - 28.10.2021 10:08:23
 
whateverlover, чтобы использовать ADO для получения данных из файлов, нужно ОЧЕНЬ хорошо знать все тонкости и нюансы его использования, потому как, если что-то не учтёте, он вернёт не всё или не так и проверить это будет очень непросто (не говоря уже о том, что все проверки сожрут выгоду от использования), а ошибки он может и не показать
Используйте FileSystemObject
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
 
Jack Famous, благодарю)
Статью читал, остановился на ADO, для себя прикрутить не смог пример по ссылке, переделал под себя вариант с ютуб канала WiseOwl, но тормознуто получилось.
Попробую все остальные тогда, авось они побыстрее.
 
Цитата
whateverlover написал:
А может ли функция вызвать в себе же процедуру, которая значение ячейки, из которой вызывается функция, вставит как значение?
у меня такой код не работает

Код
Function ПРОВЕРКА(Rng As Range) As Variant
    Call KillCell(Application.Caller)
End Function

Private Sub KillCell(iCell As Range)
    iCell.Value = iCell.Value
End Sub
Страницы: 1
Наверх