2009-11-26 54 views
15

Occasionalmente, ho dovuto creare una stringa SQL in VBA ed eseguirla con Docmd.RunSql(). Ho sempre costruito queste stringhe concatenando le variabili nella stringa, ad esempio:Creazione di stringhe SQL in 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 non sembra avere un operatore di concatenazione unario (come + =) e mentre questo non sembra l'ideale, almeno io può commentare ciascuno dei miei parametri e modificarli in modo indipendente. Rende più facile leggere e modificare di una stringa concatenata di mostri. Ma sembra ancora un modo terribile per costruire stringhe SQL. Ne ho uno con circa 50 parametri al lavoro, quindi 50 righe di mysqlstring = mysqlstring +.... Non carino.

Per inciso, che esclude l'uso di continuazioni di riga per formattare la stringa, in quanto vi è un limit on the number of line-continuations you can use on a single string (suggerimento: meno di 50). Inoltre, VBA non ti consente di inserire un commento dopo la continuazione della riga, grr!

Fino a poco tempo fa, ho pensato che fosse l'unico modo per costruire queste stringhe. Ma recentemente ho visto un modello diverso, iniettando i parametri nella stringa come this question (VB.NET) su cui ho postato una risposta, e mi chiedevo se ci fosse un equivalente di Parameters.AddWithValue() per VBA, o se fosse addirittura meglio dell'approccio di concatenazione della stringa . Quindi ho pensato che questo meritasse una sua domanda. Forse c'è qualcosa che mi manca qui.

Alcuni esperti di Access possono chiarire quali sono le migliori pratiche per la creazione di stringhe SQL in Access/VBA.

risposta

9

Possiedo un'app di scheda attività con un modulo di registrazione delle transazioni di lavoro non vincolato ragionevolmente complesso. C'è un sacco di convalida dei dati, calcolo della velocità e altro codice. Ho deciso di utilizzare quanto segue per creare i miei campi SQL Insert/Update.

Le variabili strSQLInsert, strSQLValues, strSQLUpdate sono stringhe a livello di modulo.

Molte linee della seguente:

Call CreateSQLString("[transJobCategoryBillingTypesID]", lngJobCategoryBillingTypesID) 

seguiti da:

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 

Si noti che le linee di media rimuovere il leader "". lngTrans è il valore del numero autonumatico 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 

Vedo che gli altri poster utilizzano tutti il ​​metodo Execute. Il problema con DoCmd.RunSQL è che può ignorare gli errori. Uno dei seguenti messaggi mostrerà tutti i messaggi di errore ricevuti dalla query. Se si utilizza DAO, utilizzare Currentdb.Execute strSQL, dbfailonerror .. Per ADO utilizzare CurrentProject.Connection.Execute strCommand, lngRecordsAffected, adCmdText È quindi possibile rimuovere le righe docmd.setwarnings.

Se si utilizza docmd.setwarnings, assicurarsi di inserire l'istruzione True in qualsiasi codice di gestione degli errori. Altrimenti cose strane possono accadere in seguito, specialmente mentre si sta lavorando sull'app. Ad esempio, non si riceverà più il messaggio "Desidera salvare le modifiche" se si chiude un oggetto. Ciò potrebbe significare che modifiche, eliminazioni o aggiunte indesiderate verranno salvate nel tuo MDB.

Anche le prestazioni possono essere significativamente diverse tra i due metodi. Una pubblicazione dichiarata currentdb.execute ha impiegato due secondi mentre docmd.runsql impiegava otto secondi. Come sempre YMMV.

+0

Se si utilizza CurrentDB, anziché assegnare CurrentDB a una variabile, non è possibile restituire i record interessati. – Fionnuala

+0

Abbastanza vero, continuo a dimenticare quel dettaglio. Ho uno snippet di codice per gestire questa situazione. –

+0

E non è possibile utilizzare SELECT @IDENTITY per ottenere il PK di composizione automatica dell'ultimo inserto. –

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

userei l'approccio di cui sopra, con ogni parametro su una riga separata è bello e facile per eseguire il debug e aggiungere a.

Se tuttavia non ti piacesse in quel modo, allora potresti guardare una query con parametri. Leggermente meno flessibile ma in alcuni casi leggermente più veloce.

In alternativa, sarebbe possibile definire una funzione pubblica per l'inserimento in tale tabella e passare i valori come parametri.

Io però vorrei restare con quello che avete ottenuto, ma sarebbe bello se VBA avrebbe capito = +

+0

Ci sono altri vantaggi rispetto ai parametri (sia nel codice SQL che nel middleware di accesso ai dati) rispetto alla performance, ad es. forte tipizzazione dei dati, valori dei parametri predefiniti, protezione dell'iniezione SQL, caratteri speciali di escape, ecc. Inoltre, sono sicuro che ci sono casi in cui una PROCEDURE del motore di database di Access ha prestazioni peggiori rispetto all'SQL dinamico. – onedaywhen

+0

Concordato che le query con parametri offrono molti vantaggi. Ho solo menzionato il lato prestazioni delle cose come un possibile effetto collaterale in quanto l'accesso avrebbe mantenuto il piano di query e non avrebbe dovuto risolverlo come farebbe con una dichiarazione SQL generata al volo. Detto questo con l'hardware moderno dubito che noterai qualche differenza quindi ci sono molti motivi per usare i parametri ma prestazioni molto basse nella lista –

+0

I wold suggeriscono di modificare il tuo post per cambiare la dicitura di "Userò l'approccio sopra , "per includere il nome del poster. A seconda di come le domande sono visualizzate, più recenti, più vecchie e votate, la sequenza delle risposte può cambiare. –

4

Aggiungendo a ciò @astander ha detto, è possibile creare un QueryDef (con parametri) e salvarlo come parte del database.

ad es.

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

assumere, è stato salvato con un nome myTableInsert, si potrebbe scrivere il codice, come di seguito

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

Nota: non ho ancora testato questo pezzo di codice. Ma, immagino che dovrebbe essere così.
Spero che questo ti dia abbastanza informazioni per iniziare.

+0

Interessante. Le query salvate su myTableInsert verranno visualizzate nell'elenco delle query? –

+0

Sì. Dovrai crearlo con la query INSERT con la clausola PARAMETERS, come mostrato sopra. – shahkalpesh

+0

Come ho dichiarato nel mio post, non è necessario creare una query, è possibile creare una query temporanea utilizzando solo l'argomento sql di CreateQueryDef. Si prega di consultare il link fornito per maggiori dettagli. – Fionnuala

0

Una delle cose che ho fatto in passato è creare un sistema per analizzare il codice SQL per trovare i parametri e memorizzare i parametri in una tabella. Scriverei le mie query MySQL al di fuori di Access. Quindi tutto quello che dovevo fare era aprire il file da Access e sarebbe pronto per essere aggiornato al volo ogni volta che volevo eseguirlo.

È stato un processo molto complicato, ma sarei felice di riprendere il codice la prossima settimana quando torno al lavoro se sei interessato.

1

Come altri hanno già detto, è probabilmente meglio utilizzare i parametri in primo luogo. Tuttavia, ...

Anch'io ho perso un operatore di concatenazione, essendosi abituato a. = In PHP. In alcuni casi, ho scritto una funzione per farlo, sebbene non sia specifico per concatenare stringhe SQL. Ecco il codice per quello che uso per la creazione di una stringa di query per 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 

E un esempio di dove l'ho chiamata:

... e così via.

Ora, per la costruzione di SQL WHERE clausole, si potrebbe considerare una cosa del genere come un wrapper 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 

Si potrebbe quindi chiamare che, come:

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 

... e il Debug.Print emetterebbe questa stringa:

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

Variazioni su quello potrebbe essere più utile a te, ad es., potresti voler avere un operatore di concatenazione opzionale (quindi potresti avere OR), ma probabilmente lo faremo costruendo una successione di stringhe WHERE e concatenandole con OR riga per riga nel codice, poiché probabilmente vorrai posizionare le parentesi con attenzione per assicurarsi che la priorità AND/OR sia eseguita correttamente.

Ora, niente di tutto ciò risolve veramente la concatenazione di VALUES per un'istruzione INSERT, ma mi chiedo quanto spesso si stiano effettivamente inserendo valori letterali in un'app Access. A meno che non si utilizzi un modulo non associato per l'inserimento di record, si utilizzerà un modulo per inserire record e quindi nessuna istruzione SQL. Quindi, per le clausole VALUES, sembra che in un'app Access non sia necessario questo molto spesso. Se ti stai trovando a dover scrivere clausole VALUES come queste, ti suggerirei di non utilizzare correttamente Access.

Detto questo, si potrebbe usare qualcosa di simile:

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 

... che concatenare la vostra lista valori, e quindi si potrebbe concatenare nella vostra stringa SQL intero (tra le parentesi dei valori() clausola).

Ma come altri hanno già detto, è probabilmente meglio utilizzare i parametri in primo luogo.

1

FWIW, utilizzo un formato leggermente diverso, utilizzando il carattere di interruzione di riga di Access "_". Uso anche l'operatore di concatenazione "&". Il motivo principale è la leggibilità:

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 
Problemi correlati