Duplicate declaration in current scope error in Access - sql

I'm writing a VBA code to submit records one by one in a recordset loop. Running into this error saying there's a "Duplicate declaration in current scope". I've run into this before when I've accidentally had duplicate variables, but this time I can't see why it's happening. I'm thinking it might have to do with the layout of the script and maybe that it's trying to repeatedly declare it? Maybe I could work around it by declaring the variables in a module? Not sure.
Private Sub submitButton_Click()
Const VmfgConnStr = "Connection string is here"
Dim qdf As QueryDef
Set qdf = CurrentDb.CreateQueryDef("")
Dim sqlString As String
sqlString = "INSERT INTO dbo.TRAINING_RECORDS (EMPLOYEE_ID, DOCUMENT_ID, REVISION, DATE_TRAINED, TRAINED_BY, STATUS, COMPETENCY, APPROVED, TYPE) " & _
"SELECT '" & rst![EMPLOYEE_ID] & "', '" & rst![DOCUMENT_ID] & "', '" & rst![LATEST_REV] & "', '" & dtTrained & "', '" & sprTrained & "', 'T', 'Not Verified', 'NO', 'Internal'"
Set objAD = CreateObject("ADSystemInfo")
Set objUser = GetObject("LDAP://" & objAD.UserName)
strDisplayName = objUser.DisplayName
Dim dtTrainedMsg As String
Dim sprTrainedMsg As String
Dim rst As Recordset
dtTrained = InputBox("Enter date trained as 'mm/dd/yyyy':", "", Format(Date, "mm/dd/yyyy"))
Debug.Print dtTrained
If StrPtr(dtTrained) = 0 Then
Exit Sub
Else
sprTrained = InputBox("Trained By:", "", strDisplayName)
Debug.Print sprTrained
If StrPtr(sprTrained) = 0 Then
Exit Sub
Else
Dim ConfirmMsg, ConfirmStyle, ConfirmTitle, ConfirmResponse
ConfirmMsg = "Continue?"
ConfirmStyle = vbYesNo
ConfirmTitle = " "
ConfirmResponse = MsgBox(ConfirmMsg, ConfirmStyle, ConfirmTitle)
If ConfirmResponse = vbYes Then
recSelect = "SELECT EMPLOYEE_ALL.EMPLOYEE_ID, TRAINING_DOCS_ALL.DOCUMENT_ID, TRAINING_DOCS_ALL.LATEST_REV " & _
"FROM TRAINING_DOCS_ALL, EMPLOYEE_ALL " & _
"WHERE EMPLOYEE_ALL.SELECTED = -1 AND TRAINING_DOCS_ALL.SELECTED = -1"
Set rst = CurrentDb.OpenRecordset(recSelect)
rst.MoveFirst
Do Until rst.EOF
Debug.Print rst![EMPLOYEE_NAME]; rst![DOCUMENT_ID]
qdf.sql = sqlString
qdf.ReturnsRecords = False
qdf.Connect = VmfgConnStr
qdf.Execute
rst.MoveNext
Loop
CurrentDb.Execute "DELETE * FROM TRAINING_RECORDS"
CurrentDb.Execute "INSERT INTO TRAINING_RECORDS (EMPLOYEE_ID, DOCUMENT_ID, REVISION, DATE_TRAINED, TRAINED_BY, STATUS) " & _
"SELECT * FROM uSysTRAINING_RECORDS " & _
"WHERE EMPLOYEE_ID = '" & EMPLOYEE_ID.Value & "'"
CurrentDb.Execute "DELETE FROM TRAINING_NEEDED " & _
"WHERE EMPLOYEE_ID LIKE '" & EMPLOYEE_ID.Value & "' AND DOCUMENT_ID LIKE '" & DOCUMENT_ID.Value & "'"
Else
End If
End If
End If
End Sub

Consider best practices in VBA and SQL generally and MS Access specifically:
VBA Tips
Use Option Explicit at the top of every module. In fact, set it as a global setting in IDE (Tools \ Options \ Require Variable Declaration) which declares line in all VBA projects going forward. This option will raise compile error if variables are used before its corresponding Dim call as many instances emerge in posted code.
Unlike SQL with its lexical order that differs from logical order (i.e., first written clause, SELECT, is usually last clause run), VBA like many other languages runs code in order of how lines are written. Therefore, Dim must come before Set or = assignment .
Related to above, place all Dim calls as top level lines before any application code. This reads better and avoids issues of variable declaration before use.
Const VmfgConnStr = "Connection string is here"
Dim rst As Recordset
Dim qdf As QueryDef
Dim recSelect As String, sqlString As String
Dim dtTrainedMsg As String, dtTrained As String
Dim sprTrainedMsg As String, sprTrained As String
Dim ConfirmMsg As String, ConfirmStyleAs String
Dim ConfirmTitle As String, ConfirmResponse As String
Dim objAd, objUser As Object
Dim strDisplayName As String
Dim Employee_ID_value As String, DOCUMENT_ID_value As String
Consistently indent your code with any blocks such as If, With, For, Do, etc. This facilitates readability and maintainability.
If StrPtr(dtTrained) = 0 Then
...
Else
...
If StrPtr(sprTrained) = 0 Then
...
Else
...
If ConfirmResponse = vbYes Then
...
Do Until rst.EOF ' REDUNDANT WITH insert-select
...
Loop
...
Else ' REDUNDANT IF EMPTY BLOCK
End If
End If
End If
SQL Tips
Use table aliases to avoid re-writing long identifiers.
Avoid looped queries and run insert-select if possible. (See below if parameterization is needed if dtTrained and sprTrained are VBA variables).
INSERT INTO dbo.TRAINING_RECORDS (EMPLOYEE_ID, DOCUMENT_ID, REVISION, DATE_TRAINED,
TRAINED_BY, STATUS, COMPETENCY, APPROVED, TYPE)
SELECT e.EMPLOYEE_ID, t.DOCUMENT_ID, t.LATEST_REV, 'dtTrained', 'sprTrained',
'T', 'Not Verified', 'NO', 'Internal'
FROM TRAINING_DOCS_ALL t, EMPLOYEE_ALL e
WHERE e.SELECTED = -1
AND e.SELECTED = -1
Be careful of cross joins as above uses due to cartesian product that can have performance issues with large tables. (e.g., 1,000 rows X 1,000 rows = 1,000,000 result rows).
If wildcards (*) are not used, don't use LIKE operator which runs a different even slower query process. Instead use equality, =.
Avoid selecting all columns with SELECT * FROM (especially in append queries). See Why is SELECT * considered harmful?.
MS Access Tips
Avoid scripting SQL strings inside VBA. Instead use stored queries with parameterization. Doing so you avoid messy concatenation and punctuation of quotes. This helps in code readability and maintainability, even efficiency as query compiler optimizes saved queries and not string queries run on the fly.
Specifically, replace CurrentDb.Execute ... with the following set up.
SQL (save as stored objects - no concatenation or line breaks)
PARAMETERS [e_id_param] TEXT;
INSERT INTO TRAINING_RECORDS (EMPLOYEE_ID, DOCUMENT_ID, REVISION,
DATE_TRAINED, TRAINED_BY, STATUS)
SELECT EMPLOYEE_ID, DOCUMENT_ID, REVISION, DATE_TRAINED, TRAINED_BY, STATUS
FROM uSysTRAINING_RECORDS
WHERE EMPLOYEE_ID = [e_id_param]
PARAMETERS [e_id_param] TEXT, [doc_id_param] TEXT;
DELETE FROM TRAINING_NEEDED
WHERE EMPLOYEE_ID = [e_id_param] AND DOCUMENT_ID = [doc_id_param]
VBA (call QueryDefs, bind param(s), and execute - no SQL shows in code)
Set qdef = CurrentDb.QueryDefs("mySavedAppendQuery")
qdef![e_id_param] = EMPLOYEE_ID.Value
qdef.Execute
Set qdef = Nothing
Set qdef = CurrentDb.QueryDefs("mySavedDeleteQuery")
qdef![e_id_param] = EMPLOYEE_ID.Value
qdef![doc_id_param] = DOCUMENT_ID.Value
qdef.Execute
Set qdef = Nothing

Related

access vba concatenate single column query into a single line result

I have a new database to help produce documents for order processing.
I have a query set up with only one column of results (Product Codes) determined by the order selected on the main form.
I need to be able to use this information to name my file aka
(Customer) (Product1)+(Product2)+(Product....) (Location)
I have the code to generate the line (Customer) (Product1) (Location) and am trying to get either a concatenate function or a loop function or something to get all the products to line up with a "+" in between each "line".
I have a query (Query1) set up to give me the exact data I need...
SELECT tblREF_Chemical.Abbr
FROM qry_AX_LineItems_DISTINCT INNER JOIN tblREF_Chemical ON
qry_AX_LineItems_DISTINCT.ItemId = tblREF_Chemical.[Item Number]
GROUP BY tblREF_Chemical.Abbr, qry_AX_LineItems_DISTINCT.SALESID,
tblREF_Chemical.[Proper Shipping Name]
HAVING (((qry_AX_LineItems_DISTINCT.SALESID)=[Forms]![frm_SalesOrderEntry]!
[Combo617]) AND ((tblREF_Chemical.[Proper Shipping Name]) Is Not Null));
I have a button set up on my main form to test the data output and then I intend to add the code to my code for DoCmd.Output file name.
So far the only code that has worked is...
Private Sub Command1492_Click()
Dim i As Integer
Dim db As DAO.Database
Dim rst As DAO.Recordset
Dim SQL As String
Set db = CurrentDb
SL = [Forms]![frm_SalesOrderEntry]![Combo617]
SQL = "SELECT * FROM ALL_SalesOrderItemsLineDates WHERE
ALL_SalesOrderItemsLineDates.SALESID = '" & SL & "';"
Set rst = db.OpenRecordset(SQL)
For i = 0 To DCount("*", "ALL_SalesOrderItemsLineDates",
"ALL_SalesOrderItemsLineDates.SALESID = '" & [Forms]![frm_SalesOrderEntry]!
[Combo617] & "'") - 1
Debug.Print DLookup("[Abbr]", "[tblREF_Chemical]", "[Item Number]= '" &
rst.Fields("ItemID") & "'")
rst.MoveNext
Next i
rst.Close
End Sub
I can't seem to add additional where statements within this code or use my actual query or the system presents errors at the db.OpenRecordset line of code (Errors 3061 and 3078).
Even ignoring those problems the output is multi-line and I need it to be used in a single string of text for the document name.
UPDATE1:
I am working with the code to use my query directly...
Dim i As Integer
Dim db As DAO.Database
Dim rst As DAO.Recordset
Dim SQL As String
Set db = CurrentDb
SL = [Forms]![frm_SalesOrderEntry]![Combo617]
SQL = "SELECT tblREF_Chemical.Abbr "
SQL = SQL & "FROM qry_AX_LineItems_DISTINCT INNER JOIN tblREF_Chemical ON qry_AX_LineItems_DISTINCT.ItemId = tblREF_Chemical.[Item Number] "
SQL = SQL & "GROUP BY tblREF_Chemical.Abbr, qry_AX_LineItems_DISTINCT.SALESID, tblREF_Chemical.[Proper Shipping Name] "
SQL = SQL & "HAVING ((qry_AX_LineItems_DISTINCT.SALESID)='" & SL & "'"
SQL = SQL & "AND ((tblREF_Chemical.[Proper Shipping Name]) Is Not Null)); "
Set rst = db.OpenRecordset(SQL)
Dim s As String
Do While rst(0) Is Not Null
s = s & "+" & rst(0)
rst.MoveNext
Loop
rst.Close
Debug.Print s
Unfortunately I'm now getting a run-time error 3061 - Too few parameters. Expected 1.
I have double checked my spellings and ran the query just to be sure and no matter how many results the query is getting (functioning as expected) I am still getting this error.
UPDATE2:
Through more research I learned that queries can have, for lack of better words, invisible coding. I am updating my code to remove the inner query from my query to simplify the amount of "research" my VBA has to do.
Private Sub Command1492_Click()
Dim i As Integer
Dim db As DAO.Database
Dim rst As DAO.Recordset
Dim SQL As String
Set db = CurrentDb
SL = [Forms]![frm_SalesOrderEntry]![Combo617]
SQL = "SELECT tblREF_Chemical.Abbr "
SQL = SQL & "FROM ALL_SalesOrderItemsLineDates INNER JOIN tblREF_Chemical ON ALL_SalesOrderItemsLineDates.ItemId = tblREF_Chemical.[Item Number] "
SQL = SQL & "GROUP BY tblREF_Chemical.Abbr, ALL_SalesOrderItemsLineDates.SALESID, tblREF_Chemical.[Proper Shipping Name]"
SQL = SQL & "HAVING ((ALL_SalesOrderItemsLineDates.SALESID)='" & SL & "'"
SQL = SQL & "AND ((tblREF_Chemical.[Proper Shipping Name]) Is Not Null)); "
Set rst = db.OpenRecordset(SQL)
Dim s As String
Do While rst(0) Is Not Null 'Debug error here!
s = s & "+" & rst(0)
rst.MoveNext
Loop
rst.Close
Debug.Print s
End Sub
Unfortunately I'm still getting a run-time error, but now it is 424 Object required and the debug takes me to the "Do While" line.
I think this is a step forward, but still a little stuck.
Update3:
Since the debug was taking me to the "Do While" line I returned to my functioning code and replaced the loop function with an integer based code.
Thank you #Harassed Dad! Your code was a giant help! Using your idea for a string rather than going straight to a debug.print was genius.
The below replaces my code starting where I was having issues.
Dim s As String
For i = 0 To DCount("*", "ALL_SalesOrderItemsLineDates", "ALL_SalesOrderItemsLineDates.SALESID = '" & SL & "'") - 1
s = s & "+" & rst.Fields("Abbr")
rst.MoveNext
Next i
rst.Close
Debug.Print s
My results are displaying with only one hiccup.
+CHA+DEEA+EEP+MEC+PERC+PM+PROP
There is an extra "+" at the beginning, but I'm sure I can find the solution to this tiny problem.
I hope these notes can help someone in the future. Thank you all for your help!
Private Sub Command1492_Click()
Dim i As Integer
Dim db As DAO.Database
Dim rst As DAO.Recordset
Dim SQL As String
Set db = CurrentDb
SL = [Forms]![frm_SalesOrderEntry]![Combo617]
SQL = "SELECT tblREF_Chemical.Abbr "
SQL = SQL & "FROM qry_AX_LineItems_DISTINCT INNER JOIN tblREF_Chemical ON "
SQL = SQL & "qry_AX_LineItems_DISTINCT.ItemId = tblREF_Chemical.[Item Number] "
SQL = SQL & "GROUP BY tblREF_Chemical.Abbr, qry_AX_LineItems_DISTINCT.SALESID, "
SQL = SQL & "tblREF_Chemical.[Proper Shipping Name] "
SQL = SQL & "HAVING (((qry_AX_LineItems_DISTINCT.SALESID)='" & SL & "'" 'edit here
SQL = SQL & "AND ((tblREF_Chemical.[Proper Shipping Name]) Is Not Null)); "
Set rst = db.OpenRecordset(SQL)
Dim s as string
Do While rst(0) is not null
s = s & "+" & rst(0)
rst.MoveNext
Loop
rst.Close
Debug.print s
End Sub
Your main issue is a missing space before the AND in HAVING clause.
For this very reason of readability and maintainability, consider using QueryDefs for parameterized queries (an industry best practice) to run your saved query in VBA for several reasons:
You avoid the need to concatenate or enclose quotes or escape literals by effectively divorcing SQL code from VBA (application layer) code.
MS Access will not allow you to save a query with syntax issues but VBA string queries can have such issues found at runtime.
MS Access's engine compiles and caches saved queries to best execution plan which especially helps for aggregate queries with joins. This is why saved queries are usually more efficient than VBA string queries run on the fly.
SQL (save below as a saved query)
Query now uses table aliases and HAVING conditions are moved to WHERE since no aggregate is being used.
PARAMETERS idparam LONG;
SELECT t.Abbr
FROM qry_AX_LineItems_DISTINCT q
INNER JOIN tblREF_Chemical t ON q.ItemId = t.[Item Number]
WHERE (((q.SALESID) = [idparam])
AND ((t.[Proper Shipping Name]) Is Not Null))
GROUP BY t.Abbr, q.SALESID, t.[Proper Shipping Name];
VBA
Dim db As DAO.Database, qdef AS DAO.QueryDef, rst As DAO.Recordset
Dim SQL As String, s As String
Set db = CurrentDb
' INITIALIZE SAVED QUERY
Set qdef = db.QueryDefs("mySavedQuery")
' BIND PARAMETER
qdef![idparam] = [Forms]![frm_SalesOrderEntry]![Combo617]
' OPEN RECORDSET
Set rst = qdef.OpenRecordset()
Do While rst(0) Is Not Null
s = s & "+" & rst(0)
rst.MoveNext
Loop
rst.Close
Debug.Print s
Set rst = Nothing: Set qdef = Nothing: Set db = Nothing

Run Time error 3061 Too Few parameters. Expected 6. Unable to update table from listbox

All,
I am running the below SQL and I keep getting error 3061. Thank you all for the wonderful help! I've been trying to teach myself and I am 10 days in and oh my I am in for a treat!
Private Sub b_Update_Click()
Dim db As DAO.Database
Set db = CurrentDb
strSQL = "UPDATE Main" _
& " SET t_Name = Me.txt_Name, t_Date = Me.txt_Date, t_ContactID = Me.txt_Contact, t_Score = Me.txt_Score, t_Comments = Me.txt_Comments" _
& " WHERE RecordID = Me.lbl_RecordID.Caption"
CurrentDb.Execute strSQL
I am not sure but, you can try somethink like that
if you knom the new value to insert in the database try with a syntax like this one
UPDATE table
SET Users.name = 'NewName',
Users.address = 'MyNewAdresse'
WHERE Users.id_User = 10;
Now, if you want to use a form (php)
You have to use this
if(isset($_REQUEST["id_user" ])) {$id_user = $_REQUEST["id_user" ];}
else {$id_user = "" ;}
if(isset($_REQUEST["name" ])) {$name= $_REQUEST["name" ];}
else {$name = "" ;}
if(isset($_REQUEST["address" ])) {$address= $_REQUEST["adress" ];}
else {$adress= "" ;}
if you use mysql
UPDATE table
SET Users.name = '$name',
Users.address = '$adress'
WHERE Users.id_User = 10;
i don't know VBA but I will try to help you
Going on from my comment, you first need to declare strSQL as a string variable.
Where your error expects 6 values and access doesn't know what they are. This is because form objects need to be outside the quotations of the SQL query, otherwise (as in this case) it will think they are variables and obviously undefined. The 6 expected are the 5 form fields plus 'strSQL'.
Private Sub b_Update_Click()
Dim db As DAO.Database
dim strSQL as string
Set db = CurrentDb
strSQL = "UPDATE Main" & _
" SET t_Name = '" & Me.txt_Name & "'," & _
" t_Date =#" & Me.txt_Date & "#," & _
" t_ContactID =" & Me.txt_Contact & "," & _
" t_Score =" & Me.txt_Score & "," & _
" t_Comments = '" & Me.txt_Comments & "'," & _
" WHERE RecordID = '" & Me.lbl_RecordID.Caption & "';"
CurrentDb.Execute strSQL
end sub
Note how I have used double quotes to put the form fields outside of the query string so access knows they aren't variables.
If your field is a string, it needs encapsulating in single quotes like so 'string'. If you have a date field it needs encapsulating in number signs like so #date# and numbers/integers don't need encapsulating.
Look at the code I have done and you can see I have used these single quotes and number signs to encapsulate certain fields. I guessed based on the names of the fields like ID's as numbers. I may have got some wrong so alter where applicable... Or comment and I will correct my answer.

Store a sql select statement, ran from VBA, in a numeric variable

I'm working on creating a dynamic pass-through query and in order to do so I first need to query my local db and get an ID.
This ID is what I will put into my pass-through query for my WHERE clause of the query.
My string:
getCorpID = "SELECT corpID " & _
"FROM dbo_corp " & _
"WHERE name = " & Forms.frmMain.Combo4.Value
I'm then trying to do something akin to:
CorpID (integer) = docmd.runsql (getCorpID)
I realize, however that docmd runsql doesn't work with select statements, or return a value even. What can I use to run my string
getCorpId
as sql and store the result (It will only be one result, every time.. one number) in my variable CorpID
Thank you.
Consider about using Recordset :
Dim dbs As DAO.Database
Dim rsSQL As DAO.Recordset
Dim getCorpID As String
Dim CorpID
Set dbs = CurrentDb
getCorpID = "SELECT corpID " & _
"FROM dbo_corp " & _
"WHERE name = " & Forms.frmMain.Combo4.Value
Set rsSQL = dbs.OpenRecordset(getCorpID , dbOpenSnapshot)
rsSQL.MoveFirst
CorpID = rsSQL.Fields("corpID")

ACCESS/SQL - Too Few Parameters

I am using the code below to create a new record in the "transactions table" the second line of the insert statement is throwing an error: Too few parameters. I have double checked and all of the field names are correct. What else could cause this type of error?
' Modify this line to include the path to Northwind
' on your computer.
Set dbs = CurrentDb
Dim vblCustomerID As String
Dim vblMealType As String
Dim Charge As Currency
Dim vblDate As String
vblDate = Format(Date, "yyyy-mm-dd")
txtCustomerID.SetFocus
vblCustomerID = txtCustomerID.Text
txtMealType.SetFocus
vblMealType = txtMealType.Text
txtCharge.SetFocus
vblCharge = txtCharge.Text
dbs.Execute "INSERT INTO dbo_Transactions" _
& "(CustomerID, MealID, TransactionAmount, TransactionDate) VALUES " _
& "(" & vblCustomerID & ", " & vblMealType & ", " & vblCharge & ", " & vblDate & ");"
dbs.Close
As others have suggested, using a parameterized query is a much better way of doing what you're attempting to do. Try something like this:
Dim qdf As DAO.QueryDef
Set qdf = dbs.CreateQueryDef("", _
"PARAMETERS prmCustomerID Long, prmMealID Long, prmTransactionAmount Currency, prmTransactionDate DateTime;" & _
"INSERT INTO dbo_Transactions (CustomerID, MealID, TransactionAmount, TransactionDate) " & _
"VALUES ([prmCustomerID], [prmMealID], [prmTransactionAmount], [prmTransactionDate]) ")
qdf!prmCustomerID = txtCustomerID.Value
qdf!prmMealID = txtMealType.Value
qdf!prmTransactionAmount = txtCharge.Value
qdf!prmTransactionDate = Date()
qdf.Execute dbFailOnError
Set qdf = nothing
Do any of the text fields you're loading into your vbl fields contain special characters like these?
, ' "
All of those in a text field in a perfectly good SQL Insert command could screw things up, I bet that's what happening here.
It would be better if you actually use parameters here to, rather than loading the text in textboxes directly into your SQL queries, since you're opening yourself up to SQL Injections. What if someone types
"; Drop Table dbo_Transactions;
in one of your textboxes and you run this query? Your database is then totally screwed up because someone just deleted one of your tables.
A few links to info on using Parameters to prevent this issue, which I'll bet will also fix the too few parameters issue you're having.
http://forums.asp.net/t/886691.aspx
http://sqlmag.com/blog/t-sql-parameters-and-variables-basics-and-best-practices

Building SQL strings in Access/VBA

Occasionally, I have had to build a SQL string in VBA and execute it with Docmd.RunSql(). I have always built these strings by concatenating variables into the string, e.g:
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 doesn't seem to have a unary concatenation operator (like +=) and while this doesn't look ideal, at least I can comment each of my parameters and change them independently. It makes it easier to read and to change than one monster concatenated string. But it still seems like a terrible way to build SQL strings. I have one with about 50 parameters at work, so 50 lines of mysqlstring = mysqlstring +.... Not cute.
Incidentally, that rules out the use of line-continuations to format the string, as there is a limit on the number of line-continuations you can use on a single string (hint: less than 50). Also, VBA doesn't let you put a comment after the line-continuation, grr!
Up until recently, I thought this was the only way to build these strings. But recently I have seen a different pattern, injecting the parameters in the string like this question (VB.NET) that I posted an answer on, and wondered if there was an equivalent of Parameters.AddWithValue() for VBA, or if that would even be any better than the string concatenation approach. So I figured that this deserves its own question. Maybe there's something I'm missing here.
Can some of the Access experts please clarify what are the best practices for building SQL strings in Access/VBA.
I have a timesheet app with a reasonably complex unbound labour transaction entry form. There is a lot of data validation, rate calculation and other code. I decided to use the following to create my SQL Insert/Update fields.
The variables strSQLInsert, strSQLValues, strSQLUpdate are form level strings.
Many lines of the following:
Call CreateSQLString("[transJobCategoryBillingTypesID]", lngJobCategoryBillingTypesID)
followed by:
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
Note that the Mid lines remove the leading ", ". lngTrans is the value of the 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
I see that the other posters are all using the Execute method. The problem with DoCmd.RunSQL is that it can ignore errors. Either of the following will display any error messages received by the query. If using DAO, use Currentdb.Execute strSQL,dbfailonerror.. For ADO use CurrentProject.Connection.Execute strCommand, lngRecordsAffected, adCmdText You can then remove the docmd.setwarnings lines.
If you're going to use docmd.setwarnings make very sure you put the True statement in any error handling code as well. Otherwise weird things may happen later on especially while you are working on the app. For example you will no longer get the "Do you wish to save your changes" message if you close an object. This may mean that unwanted changes, deletions or additions will be saved to your MDB.
Also performance can be significantly different between the two methods. One posting stated currentdb.execute took two seconds while docmd.runsql took eight seconds. As always YMMV.
Adding to what #astander has said, you could create a querydef (with parameters) and save it as part of the database.
e.g.
Parameters dtBegin DateTime, dtEnd DateTime;
INSERT into myTable (datebegin, dateend) values (dtBegin, dtEnd)
Assume, you saved it with a name myTableInsert, you could write the code as below
dim qd as QueryDef
set qd = CurrentDB.QueryDefs("myTableInsert")
qd.Parameters("dtBegin").Value = myTextFieldHavingBeginDate
qd.Parameters("dtEnd").Value = myTextFieldHavingEndDate
qd.Execute
Note: I have not tested this piece of code. But, I am guessing this should be it.
Hope this gives you enough info to get started.
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
As others have said, it's probably better to utilize parameters in the first place. However, ...
I, too, have missed a concatenation operator, having become accustomed to .= in PHP. In a few cases, I've written a function to do it, though not specific to concatenating SQL strings. Here's the code for one I use for creating a query string for an 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
And an example of where I've called it:
AppendQueryString strOutput, "InventoryID=" & frm!InventoryID, vbNullstring
AppendQueryString strOutput, "Author=" & URLEncode(frm!Author)
...and so forth.
Now, for constructing SQL WHERE clauses, you might consider something like that as a wrapper around 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
You would then call that as:
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
...and the Debug.Print would output this string:
tblInventory.InventoryID=10036 AND tblInventory.OtherAuthors Like "*Einstein*"
Variations on that might be more useful to you, i.e., you might want to have an optional concatenation operator (so you could have OR), but I'd likely do that by constructing a succession of WHERE strings and concatenating them with OR line by line in code, since you'd likely want to place your parentheses carefully to make sure the AND/OR priority is properly executed.
Now, none of this really addresses the concatenation of VALUES for an INSERT statement, but I question how often you're actually inserting literal values in an Access app. Unless you're using an unbound form for inserting records, you will be using a form to insert records, and thus no SQL statement at all. So, for VALUES clauses, it seems that in an Access app you shouldn't need this very often. If you are finding yourself needing to write VALUES clauses like this, I'd suggest you're not using Access properly.
That said, you could use something like this:
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
...which would concatenate your values list, and then you could concatenate into your whole SQL string (between the parens of the VALUES() clause).
But as others have said, it's probably better to utilize parameters in the first place.
FWIW, I use a slightly different format, using Access's line break character "_". I also use the concatenation operator "&". The main reason is for readability:
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
I would use the approach above, with each parameter on a separate line it is nice and easy to debug and add to.
If however you really did not like that way then you could look at a parameter query. Slightly less flexible but in some cases slightly quicker.
Or another way would be to define a public function for inserting into that table and pass the values to it as parameters.
I however would stick with what you have got but it would be nice if VBA would understand =+
One of the things I've done in the past is create a system for parsing SQL code to find parameters and storing the parameters in a table. I would write my MySQL queries outside of Access. Then all I had to do was open the file from Access and it would be ready to be updated on the fly each time I wanted to run it.
It was a really complicated process, but I'd be happy to dig up the code next week when I get back to work if you're interested.