Книга VBStreets, Visual Basic и базы данных

Для работы с источниками данных существуют различные способы и технологии. Наиболее распространенные из них — DAO, RDO, ADO и ADO.NET. В настоящий момент используются преимущественно ADO и ADO.NET для .NET-приложений.

Асинхронность

При работе с базами данных часто возникает ситуация, когда выполняемая операция занимает относительно долгое время и в течении того времени, пока она выполняется, основная программа ожидает ее завершения, не давая возможности пользователям с ней работать. И тогда у программиста возникает вопрос: а как бы сделать так, чтобы длительные операции с БД не блокировали работу основной программы? Для этого в ADO предусмотрены асинхронные операции, а именно, асинхронное подключение, асинхронное выполнение команды и асинхронное получение данных.

Следует учитывать, что во-первых, асинхронность не ускоряет выполнение операций с БД, она просто делает работу пользователя более комфортной, и во-вторых, асинхронность усложняет логику программы. Если вы делаете асинхронный запрос, вы не можете в следующей строке крутить холостой бесконечный цикл с DoEvents до тех пор, пока статус объекта не изменится на "завершено". При использовании асинхронных операций поведение программы должно управляться событиями и флагами.

Асинхронное подключение — задается константой adAsyncConnect в методе ADODB.Connection.Close. При этом клиентская система не будет ждать, пока подключение будет завершено, а сразу будет работать дальше. Состояние подключения нужно отслеживать в событиях WillConnect (событие возникает перед тем, как начнет осуществляться подключение), ConnectComplete (подключение завершено, успешно или с ошибкой) и InfoMessage (в этом событии принимаются различные сообщения от СУБД).

Асинхронное выполнение — задается константой adAsyncExecute в методах, открывающих набор данных или выполняющих инструкции SQL, т.е. ADODB.Connection.Execute, ADODB.Recordset.Open и ADODB.Command.Execute. Статус операции можно отслеживать в событиях подключения (ADODB.Connection) WillExecute (событие возникает перед тем, как будет запущена операция) и ExecuteComplete (операция завершена, успешно или с ошибкой). Следует учитывать, что для одного подключения (ADODB.Connection) в один момент времени может выполняться только одна операция, независимо от того, асинхронно она выполняется или нет. Проверить, имеется ли для подключения какая-нибудь запущенная операция, можно с помощью свойства ADODB.Connection.State, adStateConnecting означает, что выполняется подключение, adStateExecuting означает, что выполняется операция и adStateFetching означает, что производится получение данных.

После того, как ваш асинхронный запрос отработает, вы можете использовать полученный набор данных (если запрос возвращает данные). Но прежде чем с этими данными можно будет работать, их вначале надо передать с серверной стороны на клиентскую сторону. И если этих данных много, то процесс может занять много времени. Чтобы в это время клиентская программа не блокировалась, используется асинхронное получение данных. Чтобы получать данные асинхронно, нужно указать константу adAsyncFetch или adAsyncFetchNonBlocking при открытии набора данных или выполнении команды (т.е. в ADODB.Connection.Execute, ADODB.Recordset.Open и ADODB.Command.Execute).

Действие adAsyncFetch и adAsyncFetchNonBlocking несколько различается. adAsyncFetch означает, после того, как будет получен первый блок данных (размер которых задается свойством CacheSize), остальные данные будут приниматься с сервера асинхронно, не блокируя клиентской программы. Но если клиентская программа попытается обратится к данным, которые еще не были получены, то она будет заблокирована до завершения операции. Указание adAsyncFetchNonBlocking задает, что клиентская программа никогда не будет блокироваться при получении данных; если запрошенные данные еще не были получены с сервера, то курсор будет установлен в конец набора записей (EOF). adAsyncFetchNonBlocking неприменим к таблицам, открываемым как adCmdTableDirect, и к наборам данных, открываемым из потока, содержащего сохраненный набор данных. Процесс получения данных можно отслеживать в событиях набора данных (ADODB.Recordset) FetchProgress (данные извлекаются) и FetchComplete (извлечение данных завершено).

Асинхронные операции также применимы к записям (ADODB.Record) и потокам (ADODB.Stream).

Подключение в фоновом режиме

Ссылка: Фоновый режим работы программы
Есть процедуру подключения к удаленному SQL серверу, которая просто вешает приложение на несколько минут. Возможно ли ее выполнить в фоновом режиме.

Описанная задача решается довольно элементарно. Самый простой код будет таким:

Асинхронное подключение к СУБД

Private Sub AsyncConnectToDatabase() Dim objADOConnection As New ADODB.Connection objADOConnection.Open "Provider=sqloledb;Data Source=dbserver;Initial Catalog=dbname;User Id=username;Password=userpassword;", , , adAsyncConnect Do While objADOConnection.State = adStateConnecting DoEvents Loop If objADOConnection.State = adStateOpen Then MsgBox "Соединение установлено." Else MsgBox "Соединение не установлено." End If End Sub

Преимуществом данного кода является его простота (при желании, его можно уместить в три строки). Но у него есть и ряд недостатков. Во-первых, имеется холостой цикл ожидания с DoEvents внутри, что само по себе неправильно. Во-вторых, нигде не задается время таймаута или другие параметры. И в-третьих, не отслеживаются ошибки подключения, их причина.

Более правильным был бы следующий код:

Асинхронное подключение к СУБД

Option Explicit ... Private WithEvents objADOConnection As ADODB.Connection ... Private Sub ConnectionDone(pConnection As ADODB.Connection) If pConnection.State = ADODB.ObjectStateEnum.adStateOpen Then MsgBox "Подключение выполнено. Версия: " & pConnection.Version 'Далее выполняются необходимые операции, загрузка справочников и данных и т.п. Else MsgBox "Подключение было закрыто." End If End Sub Private Sub objADOConnection_ConnectComplete(ByVal pError As ADODB.Error, adStatus As ADODB.EventStatusEnum, ByVal pConnection As ADODB.Connection) Select Case adStatus Case ADODB.EventStatusEnum.adStatusOK 'Подключение выполнено успешно If pError Is Nothing Then ConnectionDone pConnection Else MsgBox "В процессе подключения произошла ошибка." & vbNewLine & pError.Description & vbNewLine & "Подключение не выполнено." End If Case ADODB.EventStatusEnum.adStatusCancel 'Подключение было отменено в WillConnect MsgBox "Подключение было отменено." Case ADODB.EventStatusEnum.adStatusErrorsOccurred 'При подключении произошла ошибка If Not pError Is Nothing Then MsgBox "В процессе подключения произошла ошибка." & vbNewLine & pError.Description & vbNewLine & "Подключение не выполнено." End If End Select End Sub Private Sub objADOConnection_WillConnect(ConnectionString As String, UserID As String, Password As String, Options As Long, adStatus As ADODB.EventStatusEnum, ByVal pConnection As ADODB.Connection) 'Если имя пользователя locked-user или БД открывается в эксклюзивном режиме, отменить подключение If UserID = "locked-user" Or pConnection.Mode = adModeShareExclusive Then End Sub Private Sub AsyncConnectToDatabase() Dim sConnectionString As String sConnectionString = "Provider=sqloledb;Data Source=dbserver;Initial Catalog=dbname;User Id=username;Password=userpassword;" Set objADOConnection = New ADODB.Connection objADOConnection.ConnectionTimeout = 10 objADOConnection.CommandTimeout = 10 objADOConnection.Mode = adModeReadWrite objADOConnection.Open sConnectionString, , , adAsyncConnect End Sub

Данный код, несмотря на его громоздкость и избыточность, имеет то преимущество, что обрабатывает большинство ситуаций, которые могут возникнуть при подключении к СУБД. В практическом использовании можно удалить ненужные фрагменты кода.

Напоминание. Нельзя выполнять операцию, если ранее запущенная асинхронная операция еще не завершилась. Проверяйте состояние ADODB.Connection.State.

Выполнение операций в фоновом режиме

Ссылка: Асинхронная обработка в ADO
Как можно сделать, чтобы при вызове AODDB.Connection.execute "my_procedure" чтобы прога не висла.

Ссылка: MS SQL Асинхронная команда
Visual Basic 6 в программе выполняю SQL запрос с помощью ExecuteWithResults программа выполняет его минут за 10 все это время окно "дохлое" - типа зависшее нужно запустить запрос асинхронно, чтобы прога и дальше "жила", а запрос выполнялся реально ли это?

Задача решается аналогично предыдущей, только вместо асинхронного подключения теперь необходимо асинхронное выполнение. Самый простой код:

Асинхронное выполнение

objADOConnection.Execute "delete * from [table1]", , ADODB.ExecuteOptionEnum.adAsyncExecute

Этот код выполнит удаление всех записей в таблице table1, не блокируя при этом основной поток. Но у данного кода есть ряд ограничений. Такой код можно использовать только для запросов, не возвращающих данных (SQL DDL, INSERT, UPDATE, DELETE), для SELECT-запросов этот код неприменим. Кроме того, при использовании этого кода невозможно узнать, когда операция будет завершена. Например, если следующей строкой потребуется удалить данные в другой таблице, придется использовать цикл, ожидая пока objADOConnection.State не перестанет быть adStateExecuting.

Следующий код более удобен:

Асинхронное выполнение с событиями

Option Explicit Private WithEvents objADOConnection As ADODB.Connection ... Sub AsyncExecute() objADOConnection.Execute "delete * from [table1]", , ADODB.ExecuteOptionEnum.adAsyncExecute End Sub ... Private Sub objADOConnection_ExecuteComplete(ByVal RecordsAffected As Long, ByVal pError As ADODB.Error, adStatus As ADODB.EventStatusEnum, ByVal pCommand As ADODB.Command, ByVal pRecordset As ADODB.Recordset, ByVal pConnection As ADODB.Connection) Select Case adStatus Case ADODB.EventStatusEnum.adStatusOK 'Запрос выполнен успешно If pError Is Nothing Then MsgBox "Запрос выполнен. Количество записей, к которым был применен запрос: " & RecordsAffected Else MsgBox "В процессе выполнения запроса произошла ошибка." & vbNewLine & pError.Description & vbNewLine & "Запрос не выполнен." End If Case ADODB.EventStatusEnum.adStatusCancel 'Запрос был отменен в WillExecute MsgBox "Выполнение было отменено." Case ADODB.EventStatusEnum.adStatusErrorsOccurred 'При выполнении произошла ошибка If Not pError Is Nothing Then MsgBox "В процессе выполнения запроса произошла ошибка." & vbNewLine & pError.Description & vbNewLine & "Запрос не выполнен." End If End Select End Sub

В ряде случаев бывает удобнее пользоваться не ADODB.Connection.Execute, а ADODB.Recordset.Open (для получения набора данных) или ADODB.Command.Execute (для выполнения хранимых процедур, параметрических запросов и многого другого). При этом меняться будет только код AsyncExecute, но сами события по прежнему следует обрабатывать в событиях ADODB.Connection.

Асинхронное выполнение с событиями

Option Explicit Private WithEvents objADOConnection As ADODB.Connection Private WithEvents objADORecordset As ADODB.Recordset Private objADOCommand As ADODB.Command ... Private Const sqlGetData As String = "select * from [table1]" Private Const sqlClearData As String = "delete * from [table1]" ... Private ExecuteMode As Long ... Sub AsyncExecute() ExecuteMode = 1 Set objADORecordset = New ADODB.Recordset Set objADORecordset.ActiveConnection = objADOConnection objADORecordset.CursorLocation = adUseClient objADORecordset.Open Source:=sqlGetData, Options:=adAsyncExecute End Sub ... Sub RecordsetOpsDone() MsgBox "Данные получены. Всего записей: " & objADORecordset.RecordCount ExecuteMode = 2 Set objADOCommand = New ADODB.Command Set objADOCommand.ActiveConnection = objADOConnection objADOCommand.CommandType = adCmdText objADOCommand.CommandText = sqlClearData objADOCommand.Execute Options:=adAsyncExecute End Sub ... Sub CommandOpsDone(ByVal RecordsAffected As Long) MsgBox "Таблица очищена, удалено строк: " & RecordsAffected End Sub ... Private Sub objADOConnection_ExecuteComplete(ByVal RecordsAffected As Long, ByVal pError As ADODB.Error, adStatus As ADODB.EventStatusEnum, ByVal pCommand As ADODB.Command, ByVal pRecordset As ADODB.Recordset, ByVal pConnection As ADODB.Connection) Select Case adStatus Case ADODB.EventStatusEnum.adStatusOK 'Запрос выполнен успешно If pError Is Nothing Then Select Case ExecuteMode Case 1: RecordsetOpsDone Case 2: CommandOpsDone RecordsAffected End Select Else MsgBox "В процессе выполнения запроса произошла ошибка." & vbNewLine & pError.Description & vbNewLine & "Запрос не выполнен." End If Case ADODB.EventStatusEnum.adStatusCancel 'Запрос был отменен в WillExecute MsgBox "Выполнение было отменено." Case ADODB.EventStatusEnum.adStatusErrorsOccurred 'При выполнении произошла ошибка If Not pError Is Nothing Then MsgBox "В процессе выполнения запроса произошла ошибка." & vbNewLine & pError.Description & vbNewLine & "Запрос не выполнен." End If End Select End Sub

Следует учитывать, что при выборке данных (SELECT-запросы) требуется время не только на выполнение запроса (Execute), но и на передачу данных от сервера клиенту (Fetch). При больших объемах данных это хорошо заметно; для Fetch времени может потребоваться гораздо больше, чем для Execute.

Следует отметить, что даже если вы выполняете запрос (получаете набор данных) не с помощью ADODB.Connection.Execute, а с помощью ADODB.Recordset.Open или ADODB.Command.Execute, события все-равно будут вызываться в связанном с ними (свойство .ActiveConnection) подключении ADODB.Connection.

Работа с данными в фоновом режиме

Ссылка: Долговременные Sql запросы
Здравствуйте, проблема в следующем: В программе используются Sql запросы, возвращающие большие объемы данных, один запрос может работать в раойне 15-30 сек., во время работы программа зависает и невозможно нажать отмену, выход, выводить счетчик времеи выполнения запроса или просто перейти к окну программы по alt+tab, можно ли как-то решить эту проблему?

Ссылка: Асинхронный запрос
Запускаю запрос: ... По идее ведь прогрмма должна "жить", на самом деле она замирает (Not responding) и висит так пока запрос не выполнится (минут 5), после чего вываливает данные в датагрид.

Ссылка: Проследить процесс записи BLOB (тела файла) в базу
Действия: Записываю файл в базу данных ACCESS. Тело файла в районе от 10 до 80 мегабайт. Через AppendChunk добавляю данные. Но после вызова метода UPDATE процесс сохранения в базу зависает. Вопрос: Можно ли как нибудь процесс записи в базу сделать параллельным ? Так чтобы он продолжался "фоново" ? Или показать пользователю на прогрессбар с информацие о результате сохранения ?

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

Для большей наглядности будем исходить из того, что на форме есть список List1, который заполняется данными при нажатии на кнопку Command1. В окно Debug выводится вспомогательная информация.

Асинхронное получение данных

Option Explicit Private WithEvents objADOConnection As ADODB.Connection Private WithEvents objADORecordset As ADODB.Recordset ... Private Sub Command1_Click() Debug.Print "Connection..." Set objADOConnection = New ADODB.Connection objADOConnection.Open "Provider=sqloledb;Data Source=dbserver;Initial Catalog=dbname;User Id=username;Password=userpassword;" Debug.Print "Preparing..." Set objADORecordset = New ADODB.Recordset Set objADORecordset.ActiveConnection = objADOConnection objADORecordset.CursorLocation = adUseClient objADORecordset.Properties("Initial Fetch Size") = 0 objADORecordset.Properties("Background Fetch Size") = 100 objADORecordset.Open Source:="select * from [big_table]", Options:=ADODB.ExecuteOptionEnum.adAsyncExecute Or ADODB.ExecuteOptionEnum.adAsyncFetchNonBlocking Debug.Print "Execute..." End Sub ... Private Sub objADOConnection_ExecuteComplete(ByVal RecordsAffected As Long, ByVal pError As ADODB.Error, adStatus As ADODB.EventStatusEnum, ByVal pCommand As ADODB.Command, ByVal pRecordset As ADODB.Recordset, ByVal pConnection As ADODB.Connection) Debug.Print "Execute Complete" List1.Clear End Sub Private Sub objADORecordset_FetchComplete(ByVal pError As ADODB.Error, adStatus As ADODB.EventStatusEnum, ByVal pRecordset As ADODB.Recordset) Debug.Print "Fetch Complete" End Sub Private Sub objADORecordset_FetchProgress(ByVal Progress As Long, ByVal MaxProgress As Long, adStatus As ADODB.EventStatusEnum, ByVal pRecordset As ADODB.Recordset) Static StoredPosition As Long Debug.Print "Fetching... " & " (" & Progress & "/" & MaxProgress & ")" If StoredPosition > 0 Then pRecordset.AbsolutePosition = StoredPosition + 1 Do Until pRecordset.EOF List1.AddItem pRecordset.Fields(0) If pRecordset.AbsolutePosition < Progress Then pRecordset.MoveNext If pRecordset.AbsolutePosition = Progress And Progress = MaxProgress Then Exit Do Else Exit Do End If Loop StoredPosition = pRecordset.AbsolutePosition End Sub

Код, показанный в примере, избыточен, для практического применения можно убрать до 50% кода, в зависимости от конкретных нужд. Кроме того, в большинстве случаев гораздо проще (и удобнее) бывает загружать данные в FetchComplete, а в FetchProgress просто отображать индикатор прогресса.

Следует помнить о следующих моментах. Когда сервер возвращает запрос, на клиенте принимается первый блок данных, равный Initial Fetch Size + Background Fetch Size, после чего создается новый поток, ожидающий нового блока данных, размер которых будет равен Background Fetch Size. Сделано это для того, чтобы уменьшить время отклика (не ждать, пока будет создан асинхронный поток). Но при этом может случится так, что этот первый блок данных вернет все записи, передаваемые сервером. В этом случае события FetchProgress и FetchComplete могут и не произойти.

Для того, чтобы события FetchProgress и FetchComplete происходили всегда, размер начального буфера Initial Fetch Size надо задать равным 0. Размер фонового буфера Background Fetch Size может быть любым, если он больше нуля; его надо подбирать, исходя из ожидаемого объема данных. При большом объеме данных слишком маленький буфер будет несколько снижать производительность, при маленьких объемах данных большой буфер сведет на нет все удобства асинхронной выборки данных.

При размере фонового буфера, равного 1, я столкнулся с непонятным поведением курсора. В этом случае позиция курсора (.AbsolutePosition) может не сохраняться в промежутке между вызовами FetchProgress. Именно поэтому в коде используется статическая переменная StoredPosition, при размере фонового буфера большем, чем 1, эту переменную можно убрать.

Иногда перед программистом возникает обраткая задача — не фоновая загрузка данных, а фоновое сохранение данных на сервер. К сожалению, мне неизвестено, каким образом можно организовать асинхронную выгрузку данных на сервер подобно тому, как это происходит при adAsyncFetchNonBlocking. Но можно поступить по другому, загружать данные на сервер блоками, с обработкой событий между блоками. Но для этого требуется серверный курсор.

Асинхронное сохранение данных

Option Explicit Private objADOConnection As ADODB.Connection Private objADORecordset As ADODB.Recordset ... Private Sub AsyncPutData() Set objADOConnection = New ADODB.Connection objADOConnection.Open "Provider=sqloledb;Data Source=dbserver;Initial Catalog=dbname;User Id=username;Password=userpassword;" Set objADORecordset = New ADODB.Recordset Set objADORecordset.ActiveConnection = objADOConnection objADORecordset.CursorLocation = adUseServer objADORecordset.Open "bulk_data", objADOConnection, adOpenDynamic, adLockOptimistic, ADODB.CommandTypeEnum.adCmdTable Debug.Print "Reading file..." Dim buff() As Byte, FN As Integer, P As Long, L As Long Const BuffSize As Long = 1024& * 256& FN = FreeFile Open "big_file" For Binary Access Read As #FN Debug.Print "Prepare recordset..." P = 1 With objADORecordset Debug.Print "Add record..." .AddNew With .Fields("FILE") Do Until P > LOF(FN) L = BuffSize If P + L - 1 > LOF(FN) Then L = LOF(FN) - P + 1 ReDim buff(1 To L) Get #FN, P, buff() .AppendChunk buff() Debug.Print "Chunk (" & Format$(P / LOF(FN), "0.0%") & ") -- " & Format$(P, "#,0") & "/" & Format$(LOF(FN), "#,0") DoEvents P = P + L Loop End With Debug.Print "Put record..." .Update Debug.Print "Put complete." End With Close #FN End Sub

Этот код сохраняет в базу данных файл big_file (в таблицу bulk_data), при этом основной поток приложения не замораживается. Обработка необработанных событий происходит при выполнении DoEvents, там можно указать и Me.Refresh (или что-то аналогичное), если нужно просто отобразить индикатор прогресса. На всякий случай повторю, что курсор обязательно должен быть серверным (adUseServer), если указать клиентский курсор, то процедура работать будет, но при выполнении .Update (между "Put record" и "Put complete") провайдер источника данных начнет передавать на сервер данные и приложение будет заморожено, пока процесс не завершиться. Способа сделать этот процесс (.Update) асинхронным я не знаю.

Другие вопросы

RecordsAffected. В событии ExecuteComplete (объект ADODB.Connection) есть аргумент RecordsAffected. Также этот аргумент имеется в методе Execute (объекты ADODB.Connection, ADODB.Command). В этом аргументе возвращается количество записей, к которым была применена операция. Под операцией понимаются INSERT, DELETE и UPDATE запросы; SELECT запросы не возвращают значение RecordsAffected. Для определения количества записей используйте запрос SELECT COUNT(*) или свойство ADODB.Recordset.RecordCount (для клиентского курсора).

Объект ADODB.Command

ADODB.Command представляет собой объект-команду. Данный объект характеризуется следующими основными свойствами: CommandText, CommandType, Parameters, Prepared. Довольно часто ADODB.Command используется для вызова параметрических запросов (запросов с параметрами), но есть и другие причины его использования, например, необходимость вызова хранимых процедур и получения возвращаемого значения и выходных параметров. Также ADODB.Command удобен, когда многократно вызывается один и тот же запрос с различными параметрами.

Свойство CommandText содержит в себе текст команды и интерпретируется в зависимости от типа команды. Тип команды указывается в свойстве CommandType, основные типы это таблица (представление), хранимая процедура и SQL-запрос. В коллекции Parameters содержаться параметры (аргументы) запроса. Свойство Prepared используется, когда предполагается часто вызывать один и тот же запрос с различными параметрами. Установка данного свойства в True указывает, что данный запрос надо подготовить (скомпилировать) и сохранить перед первым использованием. Это замедлит первый запуск запроса, но все последующие будут исполняться быстрее.

Отдельно можно остановиться на параметрах запроса. Эти параметры храняться в коллекции Parameters. Основные свойства параметра: Name (имя параметра), Type (тип параметра), Value (значение параметра), Direction (направление параметра). Среди направлений параметров стоит отметить adParamReturnValue, которое указывает, что данный параметр принимает значения, возвращаемые хранимой процедурой.

Также следует помнить, что типы данных, используемые в ADO, не всегда соответствуют типам данных, используемых в VB и не все из них можно привести друг к другу. Кроме того, в конкретных СУБД могут быть свои типы данных, которые обычно можно привести к типам данных ADO, но которые могут называться по разному.

Типы данных в ADO и их соответствие с другими типами данных
Тип ADO Описание Соответствие типов данных
OLEDB VB ANSI SQL MS Access MS SQL Server
adArray Флаг, комбинируемый с другими типами данных, указывающий, что параметр является массивом. - - - - -
adEmpty - DBTYPE_EMPTY Variant (Null) - - -
adBoolean Логический тип (булево значение). DBTYPE_BOOL Boolean BIT Логический bit
adTinyInt Короткое целое (один байт, знаковое). DBTYPE_I1 - TINYINT - -
adSmallInt Целое (два байта, знаковое). DBTYPE_I2 Integer SMALLINT Числовой (Целое) smallint
adInteger Длинное целое (четыре байта, знаковое). DBTYPE_I4 Long INTEGER Числовой (Длинное целое) int
adBigInt Сверхдлинное целое (восемь байт, знаковое). DBTYPE_I8 - BIGINT - bigint
adUnsignedTinyInt Короткое беззнаковое целое (один байт, беззнаковое). DBTYPE_UI1 Byte - Числовой (байт) tinyint
adUnsignedSmallInt Беззнаковое целое (два байта, беззнаковое). DBTYPE_UI2 - - - -
adUnsignedInt Беззнаковое длинное (четыре байта, беззнаковое). DBTYPE_UI4 - - - -
adUnsignedBigInt Беззнаковое сверхдлинное (восемь байт, беззнаковое). DBTYPE_UI8 - - - -
adDecimal Число с фиксированной запятой. DBTYPE_DECIMAL - DECIMAL Числовой (Действительное) decimal
adNumeric Число с фиксированной запятой. DBTYPE_NUMERIC - NUMERIC Числовой (Действительное) numeric
adVarNumeric Аналогично adNumeric, но значение не имеет фиксированного размера. Обычно используют внутри ADODB.Command. - - NUMERIC Числовой (Действительное) numeric
adSingle Вещественное число с плавающей запятой (четыре байта, знаковое). DBTYPE_R4 Single REAL Числовой (Одинарное с плавающей запятой) real
adDouble Вещественное число с плавающей запятой повышенной точности (восемь байта, знаковое). DBTYPE_R8 Double FLOAT Числовой (Двойное с плавающей запятой) float
adCurrency Валютные единицы, числа с фиксированной запятой. DBTYPE_CY Currency - Денежный money, smallmoney
adDate Значение даты (число дней, прошедших с 30 декабря 1899 г. DBTYPE_DATE Date - Дата/время datetime, smalldatetime
adDBDate Дата в формате yyyymmdd. DBTYPE_DBDATE - DATE Дата/время datetime, smalldatetime
adDBTime Дата в формате hhmmss. DBTYPE_DBTIME - TIME Дата/время datetime, smalldatetime
adDBTimeStamp Дата в формате yyyymmddhhmmss. DBTYPE_DBTIMESTAMP - TIMESTAMP Дата/время datetime, smalldatetime
adFileTime Значение даты (число сотых наносекунды, прошедших со времени 1 января 1601 г. DBTYPE_FILETIME - - - -
adBinary Бинарные данные. Обычно файлы или шифрованные файлы. DBTYPE_BYTES Byte() BINARY- Поле объекта OLE binary
adVarBinary Аналогично adBinary, но значение не имеет фиксированного размера. Обычно используют внутри ADODB.Command. - Byte() VARBINARY Поле объекта OLE varbinary
adLongVarBinary Аналогично adBinary, но значение не имеет фиксированного размера. Обычно используют внутри ADODB.Command. - Byte() BLOB Поле объекта OLE image
adChar Строковое значение в ANSI. DBTYPE_STR String CHAR Текстовый char
adVarChar Аналогично adChar, но значение не имеет фиксированного размера. Обычно используют внутри ADODB.Command. - String VARCHAR Текстовый varchar
adWChar Строковое значение в Unicode. DBTYPE_WSTR String - Текстовый nchar
adVarWChar Аналогично adWChar, но значение не имеет фиксированного размера. Обычно используют внутри ADODB.Command. - String - Текстовый nvarchar
adLongVarChar Аналогично adBinary, но значение не имеет фиксированного размера. Обычно используют внутри ADODB.Command. - String CLOB Поле МЕМО text
adLongVarWChar Аналогично adWChar, но значение не имеет фиксированного размера. Обычно используют внутри ADODB.Command. - String - Поле МЕМО ntext
adBSTR Строковое значение. DBTYPE_BSTR String - Текстовый nchar, nvarchar, ntext
adVariant Произвольное значение. DBTYPE_VARIANT Variant - - sql_variant
adGUID Значение глобального уникального илентификатора. DBTYPE_GUID String - - uniqueidentifier
adChapter Четырехбайтовые данные. DBTYPE_HCHAPTER - - - -
adIUnknown Указатель на интерфейс IUnknown. Не используется. DBTYPE_IUNKNOWN - - - -
adIDispatch Указатель на интерфейс IDispatch. Не используется. DBTYPE_IDISPATCH - - - -
adError Значение 32-битного кода ошибки. DBTYPE_ERROR - - - -
adPropVariant Произвольное значение. DBTYPE_PROP_VARIANT - - - -
adUserDefined Пользовательский тип данных. DBTYPE_UDT - - - -

Запрос с параметрами

Ссылка: сравнение по дате (access)
в таблице есть поле (тип Краткая дата: dd.mm.yyyy) делаю выборку так: ask = ask & " AND [Date_Insert] => '" & Start_Date & "' AND [Date_Insert] <= '" & Finish_Date & "'" , где Start_Date-начальная дата; Finish_Date-конечная дата выдаёт ошибку: ошибка выражения почему?

Этот вопрос очень типичный. Многие начинающие формируют запросы именно таким образом (формируя динамический SQL из фрагментов) и сталкиваются с проблемами, когда в тексте запроса нужно указать текстовые выражения или передать дату. В случае с текстовыми параметрами сложности относятся к необходимости экранирования специальных символов (кавычек, escape-символов) и необходимости исключить инъекцию кода (SQL-injection). В случае с параметрами типа "дата" сложности связаны с тем, что в разных БД дата представляется в разных форматах. Данный вопрос можно решить таким образом:

Простой запрос

Start_Date = #2001-12-31# Finish_Date = Format$(Now(), "\#yyyy\-mm\-dd\#") ask = ask & " AND [Date_Insert] => '" & Start_Date & "' AND [Date_Insert] <= '" & Finish_Date & "'"

Данный код будет работать именно так, как это нужно программисту. Связано это с тем, что Access (вернее провайдер Jet) умеет правильно интерпретировать дату, передаваемую в виде строки в формате #yyyy-mm-dd#, #yyyy/mm/dd#. Также можно использовать формат #mm/dd/yyyy#, но делать это не рекомендуется, т.к. в некоторых случаях Access может перепутать месяц и день. При использовании этого кода в других СУБД он может оказаться неработоспособным, т.к. СУБД будет ожидать дату в другом формате.

Параметрический запрос

Dim cmd As ADODB.Command Set cmd = New ADODB.Command Set cmd.ActiveConnection = Connection cmd.CommandType = adCmdText cmd.CommandText = "PARAMETERS parStartDate DateTime, parFinishDate DateTime; " & vbNewLine & _ "SELECT * FROM [Table1] WHERE [Date_Insert] BETWEEN [parStartDate] AND [parFinishDate]; " cmd.NamedParameters = True cmd.Parameters.Append cmd.CreateParameter("parStartDate", adDate, adParamInput, , #12/31/2001#) cmd.Parameters.Append cmd.CreateParameter("parFinishDate", adDate, adParamInput, , Now()) Set rs = cmd.Execute()

В данном примере использованы именованные параметры (NamedParameters = True). Если этого не указывать, тогда параметры должны передаваться в набор параметров в том порядке, в котором они встречаются в тексте запроса. Еще один способ выполнения запроса:

Параметрический запрос

Dim rs As ADODB.Recordset Dim cmd As ADODB.Command Set cmd = New ADODB.Command cmd.CommandType = adCmdText cmd.CommandText = "PARAMETERS parStartDate DateTime, parFinishDate DateTime; " & vbNewLine & _ "SELECT * FROM [Table1] WHERE [Date_Insert] BETWEEN [parStartDate] AND [parFinishDate]; " cmd.NamedParameters = True cmd.Parameters.Append cmd.CreateParameter("parStartDate", adDate, adParamInput, , #12/31/2001#) cmd.Parameters.Append cmd.CreateParameter("parFinishDate", adDate, adParamInput, , Now()) Set rs = New ADODB.Recordset rs.CursorLocation = adUseClient rs.Open cmd, Connection, adOpenStatic, adLockReadOnly

Такой способ выполнения запроса бывает нужен, когда имеются какие-либо требования к набору данных (серверный или клиентский курсор, тип курсора, тип блокировки и т.п.). Кроме того, это один из способов вызова хранимой процедуры, если она возвращает набор данных и выходные параметры.

Следует отменить, что метод Execute можен возвращать набор данных (ADODB.Recordset) либо поток (ADODB.Stream). Если в аргументе Options указать флаг adExecuteStream, поток будет доступен из свойства .Properties("Output Stream").

Вызов хранимой процедуры

Ссылка: хранимая процедура MS SQL Server
Подскажите пожалуйста, как обратиться и передать параметры к хранимой процедуре созданной в MS SQL Server из VB.

Ссылка: output parameter
Подскажите пожалуйста как вызвать хранимую процедуру с output parameter из vb с использованием ADO

Ссылка: output параметры процедуры и RecordSet
... как получить и RecordSet и выходные параметры с помощью команды Execute

В простейшем случае для вызова хранимой процедуры можно использовать метод Execute объекта ADODB.Connection. Но этот метод применим не во всех случаях, гораздо удобнее для этого использовать объект ADODB.Command. Вот один из наиболее простых способов использования:

Вызов хранимой процедуры с параметрами

Dim cmd As ADODB.Command Set cmd = New ADODB.Command Set cmd.ActiveConnection = Connection cmd.CommandType = adCmdStoredProc cmd.CommandText = "sp_myproc1" cmd.NamedParameters = True cmd.Parameters.Append cmd.CreateParameter("arg1", adInteger, adParamInput, , 1234) cmd.Parameters.Append cmd.CreateParameter("arg2", adBSTR, adParamInput, , "sample text") cmd.Execute

Для хранимых процедур и параметрических запросов есть один нюанс. Если объекту ADODB.Command уже было присвоено активное подключение, тип команды и текст команды, то можно использовать метод cmd.Parameters.Refresh, чтобы получить список параметров, вместо того, чтобы формировать этот список параметров вручную (cmd.Parameters.Append).

Кроме того, возможен и более простой вариант использования:

Упрощенный вызов хранимой процедуры с параметрами

Dim cmd As ADODB.Command Set cmd = New ADODB.Command Set cmd.ActiveConnection = Connection cmd.CommandType = adCmdStoredProc cmd.CommandText = "sp_myproc1" cmd.Execute , Array(1234, "sample text"), adExecuteNoRecords

Для того, чтобы получить возвращаемое хранимой процедурой значение и выходные параметры, можно указывать соответствующее свойство параметра. Для выходных параметров используется adParamOutput, для значения, возвращаемого процедурой, используется adParamReturnValue.

Хранимые процедуры с параметрами

Dim cmd As ADODB.Command Set cmd = New ADODB.Command cmd.ActiveConnection = Connection cmd.CommandType = adCmdStoredProc cmd.CommandText = "mysp1" cmd.Parameters.Append cmd.CreateParameter("RETURN_VALUE", adInteger, adParamReturnValue) cmd.Parameters.Append cmd.CreateParameter("@Arg1", adSmallInt, adParamInput, , 1234) cmd.Parameters.Append cmd.CreateParameter("@Arg2", adBSTR, adParamInput, , "test string") cmd.Parameters.Append cmd.CreateParameter("@Arg3", adDate, adParamInput, , Now()) cmd.Parameters.Append cmd.CreateParameter("@Result1", adBSTR, adParamOutput) cmd.Parameters.Append cmd.CreateParameter("@Result2", adBoolean, adParamOutput) cmd.Execute , , adExecuteNoRecords MsgBox "Код возврата процедуры: " & cmd.Parameters("RETURN_VALUE").Value & vbNewLine & _ "Параметр Result1 равен: " & cmd.Parameters("@Result1").Value & vbNewLine & _ "Параметр Result2 равен: " & cmd.Parameters("@Result2").Value Set cmd = Nothing

Хранимая процедура может принимать один или несколько аргументов. Хранимая процедура также может возвращать один или несколько аргументов, может возвращать код возврата процедуры и может возвращать один или несколько наборов данных. Если процедура возвращает и параметры, и набор(ы) данных, то вызывать ее надо следующим способом:

Хранимая процедура с параметрами, возвращающая набор данных

Dim rs As ADODB.Recordset Dim cmd As ADODB.Command Set cmd = New ADODB.Command cmd.ActiveConnection = Connection cmd.CommandType = adCmdStoredProc cmd.CommandText = "mysp1" cmd.Parameters.Append cmd.CreateParameter("RETURN_VALUE", adInteger, adParamReturnValue) cmd.Parameters.Append cmd.CreateParameter("@Arg1", adSmallInt, adParamInput, , 1234) cmd.Parameters.Append cmd.CreateParameter("@Arg2", adBSTR, adParamInput, , "test string") cmd.Parameters.Append cmd.CreateParameter("@Arg3", adDate, adParamInput, , Now()) cmd.Parameters.Append cmd.CreateParameter("@Result1", adBSTR, adParamOutput) cmd.Parameters.Append cmd.CreateParameter("@Result2", adBoolean, adParamOutput) Set rs = New ADODB.Recordset rs.CursorLocation = adUseClient rs.CursorType = adOpenStatic rs.LockType = adLockReadOnly rs.Open cmd If cmd.Parameters("RETURN_VALUE").Value < 0 Then MsgBox "Процедура вернула ошибку" & vbNewLine & _ "Параметр Result1 равен: " & cmd.Parameters("@Result1").Value & vbNewLine & _ "Параметр Result2 равен: " & cmd.Parameters("@Result2").Value Else MsgBox "Процедура отработала успешно. Получено строк: " rs.RecordCount & vbNewLine & _ "Параметр Result1 равен: " & cmd.Parameters("@Result1").Value & vbNewLine & _ "Параметр Result2 равен: " & cmd.Parameters("@Result2").Value Do Until rs.EOF Debug.Print " - строка " & rs.AbsolutePosition & "; " & rs.Fields(0).Value rs.MoveNext Loop End If

Другие вопросы

Подготовленные запросы. Подготовленные (prepared) запросы удобны, когда надо многократно вызывать одну и ту же процедуру или параметрический запрос, передавая ему разные параметры. Для того, чтобы использовать подготовленный запрос, ему надо задать свойство Prepared = True. Это свойство указывает, что провайдер данных должен будет сохранить подготовленную (скомпилированную) версию запроса перед первым выпонением запроса. Это несколько замедлит первый вызов запроса, зато дальнейшие вызовы будут производиться быстрее, т.к. будет использоваться подготовленный (скомпилированный) запрос.

Сравнение подготовленного и неподготовленного запроса (пример из MSDN)

'BeginPreparedVB 'To integrate this code 'replace the data source and initial catalog values 'in the connection string Public Sub PreparedX() Dim Cnxn As ADODB.Connection Dim cmd1 As ADODB.Command Dim cmd2 As ADODB.Command Dim strCnxn As String Dim strCmd As String Dim sngStart As Single Dim sngEnd As Single Dim sngNotPrepared As Single Dim sngPrepared As Single Dim intLoop As Integer ' Open a connection strCnxn = "Provider=sqloledb;Data Source=MyServer;Initial Catalog=Pubs;User Id=sa;Password=; " Set Cnxn = New ADODB.Connection Cnxn.Open strCnxn ' Create two command objects for the same ' command - one prepared and one not prepared strCmd = "SELECT title, type FROM Titles ORDER BY type" Set cmd1 = New ADODB.Command Set cmd1.ActiveConnection = Cnxn cmd1.CommandText = strCmd Set cmd2 = New ADODB.Command Set cmd2.ActiveConnection = Cnxn cmd2.CommandText = strCmd cmd2.Prepared = True ' Set a timer, then execute the unprepared ' command 20 times sngStart = Timer For intLoop = 1 To 20 cmd1.Execute Next intLoop sngEnd = Timer sngNotPrepared = sngEnd - sngStart ' Reset the timer, then execute the prepared ' command 20 times sngStart = Timer For intLoop = 1 To 20 cmd2.Execute Next intLoop sngEnd = Timer sngPrepared = sngEnd - sngStart ' Display performance results MsgBox "Performance Results:" & vbCr & _ " Not Prepared: " & Format(sngNotPrepared, _ "##0.000") & " seconds" & vbCr & _ " Prepared: " & Format(sngPrepared, _ "##0.000") & " seconds" ' clean up Cnxn.Close Set Cnxn = Nothing End Sub 'EndPreparedVB

CommandStream. Текст команды можно задавать в свойстве CommandText или CommandStream. Свойства эти взаимоисключающие, при установке значения CommandStream свойство CommandText присваивается к пустой строке. ADODB.Stream чаще всего используется в ASP-страницах, но может использоваться и в обычных запросах, когда объем принимаемых или передаваемых данных большой и требуется фрагментирование.

Использование потоков ADO

Dim cmd As New ADODB.Command Dim StreamIn As New ADODB.Stream Dim StreamOut As New ADODB.Stream Dim SQL As String ... SQL = ... ... StreamIn.Open StreamIn.WriteText SQL StreamIn.Position = 0 Set cmd.ActiveConnection = Connection Set cmd.CommandStream = StreamIn StreamOut.Open StreamOut.LineSeparator = adCRLF cmd.Properties("Output Stream").Value = StreamOut cmd.Execute , , adExecuteStream StreamOut.Position = 0 Debug.Print StreamOut.ReadText

При составлении данной статьи были использованы вопросы и примеры с форума VBStreets.

Hosted by uCoz