VBA: запрос доступа с помощью Excel. Почему так медленно?

Я нашел этот код в Интернете для запроса Access и ввода данных в Excel (2003), но он намного медленнее, чем должен быть:

Sub DataPull(SQLQuery, CellPaste)
Dim Con As New ADODB.Connection
Dim RST As New ADODB.Recordset
Dim DBlocation As String, DBName As String
Dim ContractingQuery As String

If SQLQuery = "" Then

Else
    DBName = Range("DBName")
    If Right(DBName, 4) <> ".mdb" Then DBName = DBName + ".mdb"

    DBlocation = ActiveWorkbook.Path
    If Right(DBlocation, 1) <> "\" Then DBlocation = DBlocation + "\"

    Con.ConnectionString = DBlocation + DBName
    Con.Provider = "Microsoft.Jet.OLEDB.4.0"
    Con.Open

    Set RST = Con.Execute(SQLQuery)
    Range(CellPaste).CopyFromRecordset RST

    Con.Close
End If

End Sub

Проблема в том, что этот код занимает очень много времени. Если я открою Access и просто запущу там запрос, это займет примерно 1/10 времени. Есть ли способ ускорить это? Или по какой-то причине это может занять так много времени? Все мои запросы представляют собой простые запросы на выборку с простыми операторами where и без объединений. Даже запрос select * from [test] занимает гораздо больше времени, чем следовало бы.

РЕДАКТИРОВАТЬ: я должен указать, что строка

Range(CellPaste).CopyFromRecordset RST

был тот, который занимал много времени.


person Dan    schedule 15.10.2009    source источник
comment
При выполнении в пошаговом режиме, какая строка кода занимает больше времени?   -  person shahkalpeshp    schedule 15.10.2009
comment
Диапазон(CellPaste).CopyFromRecordset RST   -  person Dan    schedule 16.10.2009
comment
Сколько записей вы извлекаете?   -  person Thorsten    schedule 16.10.2009
comment
Хм. Это все меняет. Я не думаю, что вы станете быстрее, чем CopyFromRecorset. Кроме того, вам следует серьезно подумать о том, чтобы переименовать эту ветку и удалить все ненужное из вашего сообщения. Доступ явно к проблеме не имеет никакого отношения, ИМХО.   -  person Ryan Shannon    schedule 16.10.2009


Ответы (10)


Я не эксперт, но я запускаю почти точно такой же код с хорошими результатами. Одно отличие состоит в том, что я использую объект Command так же, как и объект Connection. Где ты

Set RST = Con.Execute(SQLQuery)

I

Dim cmd As ADODB.Command
Set cmd.ActiveConnection = con
cmd.CommandText = SQLQuery
Set RST = cmd.Execute

Я не знаю, может ли это помочь и почему, но, может быть, это поможет? :-)

person Ryan Shannon    schedule 15.10.2009

Я не думаю, что вы сравниваете подобное с подобным.

В Access, когда вы просматриваете представление данных запроса, происходит следующее:

  • используется существующее открытое соединение (и остается открытым);
  • набор записей частично заполняется только несколькими первыми строками (и остается открытым);
  • частичный набор результатов отображается в сетке, посвященной задаче и оптимизированной для собственного метода доступа к данным, который использует Access (вероятно, прямое использование библиотек DLL Access Database Engine).

В вашем коде VBA:

  • открывается новое соединение (затем закрывается и освобождается);
  • набор записей полностью заполняется всеми строками (позже закрывается и освобождается);
  • весь набор результатов считывается в общий пользовательский интерфейс Excel с использованием сторонних компонентов доступа к данным.

Я думаю, что наиболее важным моментом является то, что представление данных в Access не извлекает весь набор результатов, пока вы не попросите его об этом, обычно путем перехода к последней строке в наборе результатов. ADO всегда будет извлекать все строки в результирующем наборе.

Вторым наиболее значительным будет время, затрачиваемое на чтение выбранных строк (при условии полного набора результатов) в элемент пользовательского интерфейса, и тот факт, что Excel не оптимизирован для этой работы.

Открытие, закрытие и освобождение соединений и наборов записей должно быть незначительным, но все же фактором.

Я думаю, вам нужно сделать несколько таймингов на каждом этапе процесса, чтобы найти узкое место. При сравнении с Access убедитесь, что вы получаете полный набор результатов, например. проверьте количество возвращенных строк.

person onedaywhen    schedule 16.10.2009

Поскольку вы используете Access 2003, используйте вместо него DAO, это будет быстрее с движком Jet.

См. http://www.erlandsendata.no/english/index.php?d=envbadacexportdao для примера кода.

Обратите внимание, что вы никогда не должны использовать ключевое слово «Как новое», так как это приведет к неожиданным результатам.

person JimmyPena    schedule 15.10.2009
comment
Это проблематично, потому что не у всех включен DAO, поэтому будет сложнее передавать этот файл другим людям. - person Dan; 16.10.2009
comment
То же самое может относиться к ADO. Насколько я понимаю, DAO является частью или тесно связана с движком Jet, используемым в Access 2003. Поэтому маловероятно, что он не будет присутствовать вместе с Access. Но вы всегда можете проверить его в следующем местоположении: C:\Program Files\Common Files\Microsoft Shared\DAO\dao360.dll (при условии DAO 3.6). Вам также потребуется преобразовать свой код в позднюю привязку и использовать только DAO код, если файл присутствовал. - person JimmyPena; 16.10.2009
comment
Он также присутствует в Excel, поскольку движок Jet db является компонентом Office, и Excel довольно тесно с ним интегрирован. - person David-W-Fenton; 16.10.2009
comment
ОП сказал, что запрос там занимает примерно 1/10 времени. Вы же не думаете, что переход на DAO даст 1000% прирост производительности, не так ли? - person onedaywhen; 16.10.2009
comment
Нет, но при прочих равных будет быстрее. - person JimmyPena; 16.10.2009
comment
@JP: Может быть, и так (или, может быть, не значительное увеличение), но это скорее отвлечение от проблемы, чем ее решение. - person onedaywhen; 19.10.2009
comment
Проблема в том, что код медленный. Я предлагаю другой метод, который может быть быстрее. Вместо того, чтобы принимать его существующий код как должное и пытаться сделать в нем микрооптимизацию, совершенно новый подход к его цели может дать более быстрые результаты. - person JimmyPena; 19.10.2009

Я бы порекомендовал вам создать Recordset явно, а не неявно, используя метод Execute. При явном создании вы можете установить его свойства CursorType и LockType, которые влияют на производительность.

Насколько я вижу, вы загружаете данные в Excel, а затем закрываете набор записей. Вам не нужно обновлять, подсчитывать записи и т. д. Поэтому я бы посоветовал создать Recordset с CursorType = adOpenForwardOnly & LockType = adLockReadOnly:

...
RST.Open SQLQuery, Con, adOpenForwardOnly, adLockReadOnly
Range(CellPaste).CopyFromRecordset RST
...

Recordset Object (ADO)

person manji    schedule 15.10.2009
comment
Я попробовал это. Это сработало, но не привело к разнице во времени выполнения. Спасибо, в любом случае. - person Dan; 16.10.2009

Я использовал ваш код и вытащил таблицу из 38 столбцов и 63780 строк менее чем за 7 секунд — примерно то, что я ожидал — и меньшие наборы записей были завершены почти мгновенно.

Это то, что вы испытываете? Если это так, то это согласуется с тем, что я ожидаю от подключения ADO из Excel к серверной части MDB.

Если вы видите гораздо более низкую производительность, чем это, то должны быть какие-то локальные условия окружающей среды, которые влияют на вещи.

person Lunatik    schedule 16.10.2009

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

Это должно немного ускорить его, но все еще не устраняет основную проблему.

person Dan    schedule 20.10.2009

Если вы извлечете много записей, это объяснит, почему Range(CellPaste) занимает так много времени. (Если вы выполните запрос в Access, он не получит все записи, но если вы выполните CopyFromRecordset, для этого потребуются все записи.)

Для CopyFromRecordset есть параметр MaxRows:

Public Function CopyFromRecordset ( _
    Data As Object, _
    <OptionalAttribute> MaxRows As Object, _
    <OptionalAttribute> MaxColumns As Object _
) As Integer

Попробуйте, если установка этого значения на низкое значение (например, 10 или около того) изменит производительность.

person Thorsten    schedule 15.10.2009

Как насчет следующих изменений или улучшений:

  1. После открытия сохраните набор записей в виде XML-файла (rst.saveToFile xxx), а затем повторно откройте его в Excel.
  2. После открытия поместите данные набора записей в массив (rst.getRows xxx) и скопируйте массив на активный лист.
  3. И в любое время сведите к минимуму все требования к памяти/доступу: откройте набор записей только для чтения, только для пересылки, закройте соединение, как только данные будут на вашей стороне, и т. д.
person Philippe Grondier    schedule 15.10.2009

Я не знаю, поможет ли это, но я использую VBA и ADO для подключения к электронной таблице Excel.

Он извлекал записи молниеносно (‹5 секунд), но затем внезапно стал ужасно медленным (15 секунд для извлечения одной записи). Это то, что привело меня к вашему посту.

Я понял, что случайно открыл файл Excel (я редактировал его).

Как только я закрыл его, все снова стало молниеносно.

person RIF    schedule 13.05.2013

Проблема в 9 случаях из 10 связана с типом/местоположением курсора, который вы используете.

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

ЕСЛИ вы хотите очень быстро получать большие объемы данных, вам нужно использовать CursorLocation = adUseClient в вашем соединении. Это означает, что у вас будет только статический локальный курсор, поэтому вы не будете получать живые обновления от других пользователей.

Однако, если вы только читаете данные, вы сохраните ADO, возвращаясь к БД для каждой отдельной записи, чтобы проверить изменения.

Недавно я изменил это, так как у меня был простой цикл, заполняющий элемент списка, и каждый цикл занимал около 0,3 с. Не для замедления, но даже для 1000 записей это 30 секунд! Изменение только местоположения курсора позволяет завершить весь процесс менее чем за 1 секунду.

person SwiftJr    schedule 29.04.2015