2009-11-26 23 views
15

Ocasionalmente, tuve que crear una cadena SQL en VBA y ejecutarla con Docmd.RunSql(). Siempre he incorporado estas cadenas mediante la concatenación de las variables en la cadena, por ejemplo:Creación de cadenas SQL en Access/VBA

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 no parecen tener un operador de concatenación unario (como + =) y mientras esto no se ve ideales, al menos yo puedo comentar cada uno de mis parámetros y cambiarlos de forma independiente. Hace que sea más fácil leer y cambiar que una cadena concatenada de un monstruo. Pero todavía parece una forma terrible de construir cadenas de SQL. Tengo uno con aproximadamente 50 parámetros en el trabajo, entonces 50 líneas de mysqlstring = mysqlstring +.... No es lindo.

Dicho sea de paso, eso descarta el uso de continuación de línea para formatear la cadena, ya que hay un limit on the number of line-continuations you can use on a single string (sugerencia: menos de 50). Además, VBA no te deja poner un comentario después de la continuación de la línea, grr!

Hasta hace poco, pensaba que esta era la única forma de construir estas cadenas. Pero recientemente he visto un patrón diferente, inyectando los parámetros en la cadena como this question (VB.NET) en el que publiqué una respuesta, y me pregunté si habría un equivalente de Parameters.AddWithValue() para VBA, o si eso sería incluso mejor que el enfoque de concatenación de cadenas . Así que pensé que esto merece su propia pregunta. Tal vez hay algo que me falta aquí.

¿Pueden algunos de los expertos de Access aclarar cuáles son las mejores prácticas para crear cadenas de SQL en Access/VBA.

Respuesta

9

Tengo una aplicación de control de horas con un formulario de entrada de transacciones de trabajo libre complejo bastante complejo. Hay mucha validación de datos, cálculo de tasas y otro código. Decidí usar lo siguiente para crear mis campos Insertar/Actualizar de SQL.

Las variables strSQLInsert, strSQLValues, strSQLUpdate son cadenas de nivel de formulario.

Muchas líneas de los siguientes:

Call CreateSQLString("[transJobCategoryBillingTypesID]", lngJobCategoryBillingTypesID) 

seguido de:

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 

Tenga en cuenta que las líneas de mediana eliminar el principal "". lngTrans es el valor del autonumber 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 

Veo que los otros carteles utilizan el método Execute. El problema con DoCmd.RunSQL es que puede ignorar los errores. Cualquiera de los siguientes mostrará los mensajes de error recibidos por la consulta. Si utiliza DAO, utilice Currentdb.Execute strSQL, dbFailOnError .. Para usar ADO CurrentProject.Connection.Execute strCommand, lngRecordsAffected, adCmdText A continuación, puede eliminar las líneas DoCmd.SetWarnings.

Si usted va a utilizar DoCmd.SetWarnings hacer muy seguro de que usted pone la declaración verdadera en cualquier código de gestión de errores también. De lo contrario, pueden suceder cosas extrañas más adelante, especialmente mientras trabajas en la aplicación. Por ejemplo, ya no recibirá el mensaje "¿Desea guardar sus cambios?" Si cierra un objeto. Esto puede significar que los cambios, eliminaciones o adiciones no deseados se guardarán en su MDB.

También el rendimiento puede ser significativamente diferente entre los dos métodos. Una publicación indicó currentdb.execute tomó dos segundos, mientras que docmd.runsql tomó ocho segundos. Como siempre YMMV.

+0

Si usa CurrentDB, en lugar de asignar CurrentDB a una variable, no puede devolver los registros afectados. – Fionnuala

+0

Cierto, me olvido de ese detalle. Tengo un fragmento de código para manejar esa situación. –

+0

Y no puede usar SELECT @IDENTITY para obtener el PK de Autonumérico de la última inserción. –

4
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 
0

me gustaría utilizar el enfoque anterior, con cada parámetro en una línea separada que es agradable y fácil de depurar y añadir a.

Sin embargo, si realmente no le gustaba de esa manera, podría consultar una consulta de parámetros. Ligeramente menos flexible pero en algunos casos ligeramente más rápido.

O de otra manera sería definir una función pública para insertarla en esa tabla y pasarle los valores como parámetros.

que sin embargo se pegaba con lo que tiene, pero sería bueno si VBA entendería = +

+0

Existen más ventajas para los parámetros (tanto en el código SQL como en el middleware de acceso a datos) que el rendimiento, p. tipificación de datos sólida, valores de parámetros predeterminados, protección de inyección SQL, caracteres especiales de escape, etc. Además, estoy seguro de que hay casos en los que un PROCEDIMIENTO de motor de base de datos de acceso tiene un rendimiento peor que el SQL dinámico. – onedaywhen

+0

De acuerdo en que las consultas de parámetros ofrecen muchos beneficios. Solo mencioné el lado del rendimiento de las cosas como un posible efecto secundario, ya que el acceso mantendría el plan de consulta y no tendría que resolverse como lo haría con una declaración SQL generada sobre la marcha. Habiendo dicho eso con el hardware moderno, dudo que notara alguna diferencia así que hay muchas razones para usar parámetros, pero el rendimiento es muy bajo en la lista –

+0

Le sugiero que edite su publicación para cambiar la redacción de "Usaría el enfoque anterior , "para incluir el nombre de los carteles. Dependiendo de cómo se vean las preguntas, las más nuevas, las más antiguas y los votos, la secuencia de las respuestas puede cambiar. –

4

Agregando a lo @astander ha dicho, se podría crear una definición de consulta (con parámetros) y guardarlo como parte de la base de datos.

p. Ej.

Parameters dtBegin DateTime, dtEnd DateTime; 
INSERT into myTable (datebegin, dateend) values (dtBegin, dtEnd) 

Supongamos, lo guardó con un nombre myTableInsert, podría escribir el código de la siguiente manera

dim qd as QueryDef 
set qd = CurrentDB.QueryDefs("myTableInsert") 
qd.Parameters("dtBegin").Value = myTextFieldHavingBeginDate 
qd.Parameters("dtEnd").Value = myTextFieldHavingEndDate  
qd.Execute 

Nota: No he probado esta pieza de código. Pero, supongo que esto debería ser.
Espero que esto le brinde suficiente información para comenzar.

+0

Interesante. ¿Aparecerá el querydef guardado myTableInsert en la lista de consultas? –

+0

Sí. Tendrá que crearlo con la consulta INSERT con la cláusula PARAMETERS, como se muestra arriba. – shahkalpesh

+0

Como dije en mi publicación, no es necesario crear una consulta, puede crear una consulta temporal utilizando solo el argumento sql de CreateQueryDef. Por favor, consulte el enlace proporcionado para más detalles. – Fionnuala

0

Una de las cosas que he hecho en el pasado es crear un sistema para analizar código SQL para encontrar parámetros y almacenar los parámetros en una tabla. Escribiría mis consultas MySQL fuera de Access. Entonces todo lo que tenía que hacer era abrir el archivo de Access y estaría listo para actualizarse sobre la marcha cada vez que quisiera ejecutarlo.

Fue un proceso realmente complicado, pero estaría encantado de desenterrar el código la próxima semana cuando regrese al trabajo si está interesado.

1

Como han dicho otros, probablemente sea mejor utilizar parámetros en primer lugar. Sin embargo, ...

Yo también he perdido un operador de concatenación, habiéndome acostumbrado a. = En PHP. En algunos casos, he escrito una función para hacerlo, aunque no específica para concatenar cadenas de SQL. Aquí está el código para que yo uso para la creación de una cadena de consulta para un GET HTTP:

Public Sub AppendQueryString(strInput As String, _ 
     ByVal strAppend As String, Optional ByVal strOperator As String = "&") 
    strAppend = StringReplace(strAppend, "&", "&amp;") 
    strInput = strInput & strOperator & strAppend 
    End Sub 

Y un ejemplo de que la he llamado:

... y así sucesivamente.

Ahora, para la construcción de las cláusulas WHERE de SQL, se podría considerar algo así como una envoltura alrededor de 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 

A continuación, llamar a eso como:

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 

... y Debug.Print daría salida a esta cadena:

tblInventory.InventoryID=10036 AND tblInventory.OtherAuthors Like "*Einstein*" 

Variaciones sobre eso podrían ser más útil para ti, es decir, es posible que desee tener un operador de concatenación opcional (para que pueda tener O), pero probablemente lo haga al construir una sucesión de cadenas WHERE y concatenarlas con O línea por línea en el código, ya que es probable que desee coloque sus paréntesis cuidadosamente para asegurarse de que la prioridad Y/O se ejecute correctamente.

Ahora, nada de esto realmente aborda la concatenación de VALORES para una instrucción INSERT, pero me pregunto con qué frecuencia está realmente insertando valores literales en una aplicación de Access. A menos que esté usando un formulario independiente para insertar registros, usará un formulario para insertar registros y, por lo tanto, no tendrá ninguna declaración SQL. Entonces, para las cláusulas VALUES, parece que en una aplicación de Access no debería necesitar esto muy a menudo. Si cree que necesita escribir cláusulas VALUES como esta, le sugiero que no use Access correctamente.

Dicho esto, podría utilizar algo como esto:

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 

... que concatenar su lista de valores, y entonces se podría concatenar en su cadena SQL conjunto (entre los parens de los valores() cláusula).

Pero como han dicho otros, es mejor utilizar parámetros en primer lugar.

1

FWIW, utilizo un formato ligeramente diferente, utilizando el carácter de salto de línea de Access "_". También utilizo el operador de concatenación "&". La razón principal es la legibilidad:

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 
Cuestiones relacionadas