Построение строк SQL в Access/VBA
Иногда мне приходилось создавать строку SQL в VBA и выполнять ее с Docmd.RunSql()
, Я всегда строил эти строки путем объединения переменных в строку, например:
Dim mysqlstring as String
mysqlstring = "INSERT INTO MyTable (Field1, Field2, Field3 ...) VALUES ("
mysqlstring = mysqlstring + Me.TextMyField1 + ", " 'parameter comments
mysqlstring = mysqlstring + Me.TextMyField2 + ", "
mysqlstring = mysqlstring + Me.TextMyField3 + ", "
...
mysqlstring = mysqlstring + ");"
Docmd.RunSql mysqlstring
В VBA, похоже, нет оператора унарной конкатенации (например, + =), и хотя это не выглядит идеально, по крайней мере, я могу комментировать каждый из моих параметров и изменять их независимо. Это облегчает чтение и изменение, чем одна строка монстров. Но это все еще кажется ужасным способом построения строк SQL. У меня есть один с около 50 параметров на работе, поэтому 50 строк mysqlstring = mysqlstring +...
, Не милый.
Кстати, это исключает использование продолжений строки для форматирования строки, поскольку существует ограничение на количество продолжений строки, которые вы можете использовать для одной строки (подсказка: менее 50). Кроме того, VBA не позволяет оставлять комментарии после продолжения строки, grr!
До недавнего времени я думал, что это единственный способ создать эти строки. Но недавно я увидел другую схему, в которой параметры в строке вводились, как этот вопрос (VB.NET), на который я отправил ответ, и задавался вопросом, существует ли эквивалент Parameters.AddWithValue()
для VBA, или если это даже лучше, чем подход конкатенации строк. Поэтому я решил, что это заслуживает отдельного вопроса. Может быть, я чего-то здесь упускаю.
Могут ли некоторые из экспертов по Access уточнить, каковы лучшие практики для построения строк SQL в Access/VBA.
7 ответов
У меня есть приложение с расписанием с достаточно сложной формой ввода несвязанных трудовых транзакций. Существует много данных проверки, расчета ставок и другого кода. Я решил использовать следующее для создания полей SQL Insert/Update.
Переменные strSQLInsert, strSQLValues, strSQLUpdate являются строками уровня формы.
Многие строки следующие:
Call CreateSQLString("[transJobCategoryBillingTypesID]", lngJobCategoryBillingTypesID)
с последующим:
If lngTransID = 0 Then
strSQL = "INSERT into Transactions (" & Mid(strSQLInsert, 3) & ") VALUES (" & Mid(strSQLValues, 3) & ")"
Else
strSQL = "UPDATE Transactions SET " & Mid(strSQLUpdate, 3) & " WHERE transID=" & lngTransID & ";"
End If
conn.Open
conn.Execute strSQL, lngRecordsAffected, adCmdText
Обратите внимание, что средние строки удаляют ведущие ", ". lngTrans - это значение автонумера primamy kay.
Sub CreateSQLString(strFieldName As String, varFieldValue As Variant, Optional blnZeroAsNull As Boolean)
' Call CreateSQLString("[<fieldName>]", <fieldValue>)
Dim strFieldValue As String, OutputValue As Variant
On Error GoTo tagError
' if 0 (zero) is supposed to be null
If Not IsMissing(blnZeroAsNull) And blnZeroAsNull = True And varFieldValue = 0 Then
OutputValue = "Null"
' if field is null, zero length or ''
ElseIf IsNull(varFieldValue) Or Len(varFieldValue) = 0 Or varFieldValue = "''" Then
OutputValue = "Null"
Else
OutputValue = varFieldValue
End If
' Note that both Insert and update strings are updated as we may need the insert logic for inserting
' missing auto generated transactions when updating the main transaction
' This is an insert
strSQLInsert = strSQLInsert & ", " & strFieldName
strSQLValues = strSQLValues & ", " & OutputValue
' This is an update
strSQLUpdate = strSQLUpdate & ", " & strFieldName & " = " & OutputValue
On Error GoTo 0
Exit Sub
tagError:
MsgBox "Error " & Err.Number & " (" & Err.Description & ") in procedure CreateSQLString of VBA Document Form_LabourEntry"
Exit Sub
End Sub
Я вижу, что все другие постеры используют метод Execute. Проблема с DoCmd.RunSQL заключается в том, что он может игнорировать ошибки. Любое из следующего покажет любые сообщения об ошибках, полученные запросом. Если используется DAO, используйте Currentdb.Execute strSQL,dbfailonerror. Для ADO используйте CurrentProject.Connection.Execute strCommand, lngRecordsActed, adCmdText. Затем можно удалить строки docmd.setwarnings.
Если вы собираетесь использовать docmd.setwarnings, убедитесь, что вы поместили выражение True также в любой код обработки ошибок. В противном случае странные вещи могут случиться позже, особенно когда вы работаете над приложением. Например, вы больше не будете получать сообщение "Вы хотите сохранить свои изменения", если закроете объект. Это может означать, что нежелательные изменения, удаления или добавления будут сохранены на вашем MDB.
Также производительность может существенно отличаться между двумя методами. В одной публикации сообщалось, что currentdb.execute занял две секунды, а docmd.runsql - восемь секунд. Как всегда YMMV.
В дополнение к тому, что сказал @astander, вы можете создать querydef (с параметрами) и сохранить его как часть базы данных.
например
Parameters dtBegin DateTime, dtEnd DateTime;
INSERT into myTable (datebegin, dateend) values (dtBegin, dtEnd)
Предположим, вы сохранили его с именем myTableInsert
Вы можете написать код, как показано ниже
dim qd as QueryDef
set qd = CurrentDB.QueryDefs("myTableInsert")
qd.Parameters("dtBegin").Value = myTextFieldHavingBeginDate
qd.Parameters("dtEnd").Value = myTextFieldHavingEndDate
qd.Execute
Примечание. Я не проверял этот фрагмент кода. Но я предполагаю, что так и должно быть.
Надеюсь, что это даст вам достаточно информации, чтобы начать.
Private Sub Command0_Click()
Dim rec As Recordset2
Dim sql As String
Dim queryD As QueryDef
'create a temp query def.
Set queryD = CurrentDb.CreateQueryDef("", "SELECT * FROM [Table] WHERE Val = @Val")
'set param vals
queryD.Parameters("@Val").Value = "T"
'execute query def
Set rec = queryD.OpenRecordset
End Sub
Как уже говорили другие, вероятно, лучше использовать параметры в первую очередь. Тем не мение,...
Я тоже пропустил оператор конкатенации, привыкший к.= В PHP. В некоторых случаях я написал для этого функцию, хотя и не относящуюся к конкатенации строк SQL. Вот код для одного я использую для создания строки запроса для HTTP GET:
Public Sub AppendQueryString(strInput As String, _
ByVal strAppend As String, Optional ByVal strOperator As String = "&")
strAppend = StringReplace(strAppend, "&", "&")
strInput = strInput & strOperator & strAppend
End Sub
И пример того, где я это назвал:
AppendQueryString strOutput, "InventoryID=" & frm!InventoryID, vbNullstring
AppendQueryString strOutput, "Author=" & URLEncode(frm!Author)
...и так далее.
Теперь для конструирования предложений SQL WHERE вы можете рассмотреть что-то вроде этого как обертку вокруг Application.BuildCriteria:
Public Sub ConcatenateWhere(ByRef strWhere As String, _
strField As String, intDataType As Integer, ByVal varValue As Variant)
If Len(strWhere) > 0 Then
strWhere = strWhere & " AND "
End If
strWhere = strWhere & Application.BuildCriteria(strField, _
intDataType, varValue)
End Sub
Затем вы бы назвали это как:
Dim strWhere As String
ConcatenateWhere strWhere,"tblInventory.InventoryID", dbLong, 10036
ConcatenateWhere strWhere,"tblInventory.OtherAuthors", dbText, "*Einstein*"
Debug.Print strWhere
strSQL = "SELECT tblInventory.* FROM tblInventory"
strSQL = strSQL & " WHERE " & strWhere
... и Debug.Print выведет эту строку:
tblInventory.InventoryID=10036 AND tblInventory.OtherAuthors Like "*Einstein*"
Вариации этого могут быть более полезными для вас, т. Е. Вы можете захотеть иметь необязательный оператор конкатенации (чтобы вы могли иметь ИЛИ), но я бы, вероятно, сделал это путем построения последовательности строк WHERE и конкатенации их с помощью строки ИЛИ путем в строке кода, так как вы, вероятно, захотите аккуратно поместить скобки, чтобы убедиться, что приоритет AND/OR выполнен правильно.
Теперь, ничего из этого действительно не касается объединения VALUES для оператора INSERT, но я спрашиваю, как часто вы фактически вставляете литеральные значения в приложение Access. Если вы не используете несвязанную форму для вставки записей, вы будете использовать форму для вставки записей, и, следовательно, никакого оператора SQL вообще. Таким образом, для предложений VALUES кажется, что в приложении Access вам не нужно это часто. Если вы обнаружите, что вам нужно написать такие предложения VALUES, как это, я бы посоветовал вам не использовать Access должным образом.
Тем не менее, вы можете использовать что-то вроде этого:
Public Sub ConcatenateValues(ByRef strValues As String, _
intDatatype As Integer, varValue As Variant)
Dim strValue As String
If Len(strValues) > 0 Then
strValues = strValues & ", "
End If
Select Case intDatatype
Case dbChar, dbMemo, dbText
' you might want to change this to escape internal double/single quotes
strValue = Chr(34) & varValue & Chr(34)
Case dbDate, dbTime
strValue = "#" & varValue & "#"
Case dbGUID
' this is only a guess
strValues = Chr(34) & StringFromGUID(varValue) & Chr(34)
Case dbBinary, dbLongBinary, dbVarBinary
' numeric?
Case dbTimeStamp
' text? numeric?
Case Else
' dbBigInt , dbBoolean, dbByte, dbCurrency, dbDecimal,
' dbDouble, dbFloat, dbInteger, dbLong, dbNumeric, dbSingle
strValue = varValue
End Select
strValues = strValues & strValue
End Sub
... который объединит ваш список значений, а затем вы сможете объединить всю строку SQL (между символами предложения VALUES()).
Но, как говорили другие, в первую очередь, вероятно, лучше использовать параметры.
FWIW, я использую немного другой формат, используя символ разрыва строки Access "_". Я также использую оператор конкатенации "&". Основная причина заключается в удобочитаемости:
Dim db as Database: Set db = Current Db
Dim sql$
sql= "INSERT INTO MyTable (Field1, Field2, Field3 ...Fieldn) " & _
"VALUES (" & _
Me.TextMyField1 & _
"," & Me.TextMyField2 & _
"," & Me.TextMyField3 & _
...
"," & Me.TextMyFieldn & _
");"
db.Execute s
Set db = nothing
Одна из вещей, которые я делал в прошлом, - это создание системы для синтаксического анализа кода SQL для поиска параметров и сохранения параметров в таблице. Я написал бы мои запросы MySQL вне Access. Тогда все, что мне нужно было сделать, это открыть файл из Access, и он будет готов к обновлению на лету каждый раз, когда я захочу его запустить.
Это был действительно сложный процесс, но я был бы рад выкопать код на следующей неделе, когда вернусь к работе, если вам интересно.
Я бы использовал описанный выше подход, с каждым параметром в отдельной строке, который легко и просто отлаживать и добавлять.
Однако, если вам действительно не нравится этот способ, вы можете посмотреть на запрос параметров. Немного менее гибкий, но в некоторых случаях немного быстрее.
Или другой способ - определить публичную функцию для вставки в эту таблицу и передать ей значения в качестве параметров.
Однако я бы придерживался того, что вы получили, но было бы хорошо, если бы VBA понял =+