In my code, I am trying to use an input box to hold a user inputted value as a string, then use SQL to then update a field based on that.
My code is:
Dim strinput
Dim strsql
Call CreateRevisions
strinput = InputBox(prompt:="Name of revision")
If Not IsNull(strinput) Then
'DoCmd.SetWarnings False
strsql = " UPDATE tblJobDetails.RevisionName"
strsql = strsql & " SET tblJobDetails.RevisionName = '" & strinput & "'"
strsql = strsql & " WHERE (((tblJobDetails.JobID)=[Forms]![JobQuote]![JobID]));"
'DoCmd.RunSQL strsql
CurrentDb.Execute strsql
'docmd.setwarnings true
End If
End Sub
When it runs, I get the prompt that fields are about to be updated, then the input box appears and allows me to enter something. I checked the value of strinput and it is holding the value correctly, but when I try to actually execute the SQL statement I can an interesting error I haven't run into before.
The error is :
Run-time error '3024':
Could not find file 'C\....
I have run various statements like this before with no problem. This is the first time I have tried using the input box however. What is causing that error?
I have tried changing it to docmd as well as adding in debug.print, but it still results in the same error.
I have one table in my MS Access data base named COA_Map. I also have a form with a multi-select list box. When the values are selected, vba turns them into a string and updates a text box with the string. I want to use the textbox string as a variable in a query.
This is my query:SELECT * FROM COA_Map WHERE (COA_Map.ASL IN ( [Forms]![Multi-Select Search]![TextASL].Text ) );
This returns empty results. When I copy and paste the text box values into the query like this:
SELECT * FROM COA_Map WHERE (COA_Map.ASL IN ( 2.1,2.3,2.4 ) );
I get the expected results. I tried [Forms]![Multi-Select Search]![TextASL].Value and [Forms]![Multi-Select Search]![TextASL] but that gives an error "This expression is typed incorrectly, or it is too complex"
I also tried using "OR" clause instead of "IN". I changed the VBA to return this string:
to build this query: SELECT * FROM COA_Map WHERE COA_Map.ASL = [Forms]![Multi-Select Search]![TextASL] ;
This returns the same empty results. When I paste the textbox values into the query like this: SELECT * FROM COA_Map WHERE COA_Map.ASL = 2.1 OR COA_Map.ASL = 2.2 OR COA_Map.ASL = 2.3 ;
, I get expected results.
When only one value is selected in either version of the query, I get expected results.
I can't figure out why the the query will not return results when reading from the text box with multiple values selected.
Here is a generic example to get you going in the right direction.
Private Sub cmdOK_Click()
' Declare variables
Dim db As DAO.Database
Dim qdf As DAO.QueryDef
Dim varItem As Variant
Dim strCriteria As String
Dim strSQL As String
' Get the database and stored query
Set db = CurrentDb()
Set qdf = db.QueryDefs("qryMultiSelect")
' Loop through the selected items in the list box and build a text string
For Each varItem In Me!lstRegions.ItemsSelected
strCriteria = strCriteria & ",'" & Me!lstRegions.ItemData(varItem) & "'"
Next varItem
' Check that user selected something
If Len(strCriteria) = 0 Then
MsgBox "You did not select anything from the list" _
, vbExclamation, "Nothing to find!"
Exit Sub
End If
' Remove the leading comma from the string
strCriteria = Right(strCriteria, Len(strCriteria) - 1)
' Build the new SQL statement incorporating the string
strSQL = "SELECT * FROM tblData " & _
"WHERE tblData.Region IN(" & strCriteria & ");"
' Apply the new SQL statement to the query
qdf.SQL = strSQL
' Open the query
DoCmd.OpenQuery "qryMultiSelect"
' Empty the memory
Set db = Nothing
Set qdf = Nothing
End Sub
I want to use this code to find a value in field PID where it may exist anywhere in field MP_PID but my SQL is not catching them. No errors are thrown. What am I doing wrong?
It is comparing values from text fields in both recordsets.
Thanks
TMA
strPID = "DELETE * FROM tblPID WHERE PID Like '*" & !MP_PID & "*';"
If I use:
strPID = "DELETE * FROM tblPID WHERE PID = '" & !MP_PID & "';"
It works fine for exact matches.
Access 2007 DAO
What is wrong with my like statement? Thanks!
LIKE expressions are used to identify records where the value on the left side of the LIKE operator contains a string (or pattern of characters) represented by the wildcard string on the right side of the LIKE operator.
In your case
... '0123456A' LIKE '*0000623E 0123456A*'
will return False because the left side does not contain the entire string on the right side.
What you really want is
... '0000623E 0123456A' LIKE '*0123456A*'
which means that you need the MP_PID value on the left side of the LIKE operator.
To do that correctly (i.e., with a parameterized query) would look something like this:
Option Compare Database
Option Explicit
Sub LikeTest()
' test data
Dim MP_PID As String
MP_PID = "0000623E 0123456A"
Dim cdb As DAO.Database
Set cdb = CurrentDb
Dim qdf As DAO.QueryDef
Set qdf = cdb.CreateQueryDef("", _
"PARAMETERS [LongerText] TEXT(255);" & _
"DELETE * FROM tblPID WHERE [LongerText] LIKE '*' & PID & '*'")
qdf!LongerText = MP_PID
qdf.Execute dbFailOnError
Debug.Print qdf.RecordsAffected & " record(s) deleted."
Set qdf = Nothing
Set cdb = Nothing
End Sub
PID is most likely a numeric field and you use the LIKE operator which traverses string values. Try converting numeric value to string:
strPID = "DELETE * FROM tblPID WHERE CSTR(PID) Like '*" & !MP_PID & "*';"
Alternatively, concatenate an empty length string value to numeric value (similar to converting textbox numeric value to string by concatenating asterisks and quotes):
strPID = "DELETE * FROM tblPID WHERE PID & '' Like '*" & !MP_PID & "*';"
Ok Gord and Parfait. You both got me on the correct path and yes I did have it back asswards. Here is what I used to create a delete query:
strDelMP = "DELETE tblPID.* FROM tblPID INNER JOIN tblMgtPlan ON tblMgtPlan.MP_PID Like '*' + tblPID.PID + '*';"
db.Execute (strDelMP)
Thanks!
I have a Database that we use to create Bills of Materials from Tags in AutoCAD. Because of the nature of this, I need to create 3 separate queries. One for our "Steel", one for our
"Non-Steel", and one for our "Uncut Tubes".
The SQL for the Queries is as follows:
Steel:
SELECT DISTINCTROW Sum([CUT-LENGTH-WEIGHT]) AS [SumOfCUT-LENGTH-WEIGHT], Sum([CUT-SHEET-WEIGHT]) AS [SumOfCUT-SHEET-WEIGHT], Sum([TOTAL-SHEETING-WEIGHT]) AS [SumOfTOTAL-SHEETING-WEIGHT], Sum([TOTAL-ITEM-WEIGHT]) AS [SumOfTOTAL-ITEM-WEIGHT]
FROM [13-1302 Cut-Lengths];
Non-Steel:
SELECT tbl2013BOM.fJobID, Sum(tbl2013BOM.fWeight) AS SumOffWeight
FROM tbl2013BOM
GROUP BY tbl2013BOM.fJobID
HAVING (((tbl2013BOM.fJobID)=23));
Uncut Tubes:
SELECT DISTINCT [13-1302 Cut-Lengths].[TOTAL-LENGTH-WEIGHT], [13-1302 Cut-Lengths].MATERIAL, [13-1302 Cut-Lengths].ORDER
FROM [13-1302 Cut-Lengths]
ORDER BY [13-1302 Cut-Lengths].ORDER;
I have a ComboBox that chooses the Job Number (For Main and Uncut Tubes, e.g. 13-1302) and a Textbox that displays the JobID (For Non-Steel).
Is there a way that I can set up the SQL shown above to look at the ComboBox and TextBox Values, instead of me having to change them by hand?
EDIT
I figured it all out now. (Thank you Elias)
Basically, I cannot use a Field on a table as a RecordSource in SQL, in other words, Combo26 cannot be the Table in an SQL Query. HOWEVER, what CAN be done is to use VBA to inject that value into an SQL Definition, then use that definition as a Recordsource.
I will place the code for my Button below so anyone can use it and reference it:
Private Sub Command27_Click()
Dim dbs As Database
Dim rstSQL As DAO.Recordset
Dim strSQL As String
Dim strSQL2 As String
Dim strSQL3 As String
Dim Field As String
Set dbs = CurrentDb
Field = [Forms]![frmBOM_Combined]![Text26].[Value]
strSQL = "SELECT DISTINCTROW Sum([CUT-LENGTH-WEIGHT]) AS [SumOfCUT-LENGTH-WEIGHT], Sum([TOTAL-SHEETING-WEIGHT]) AS [SumOfTOTAL-SHEETING-WEIGHT], Sum([TOTAL-ITEM-WEIGHT]) AS [SumOfTOTAL-ITEM-WEIGHT] FROM " & "[" & [Forms]![frmBOM_Combined]![Text26].[Value] & "]" & ";"
strSQL2 = "SELECT tbl2013BOM.fJobID, Sum(tbl2013BOM.fWeight) AS SumOffWeight FROM tbl2013BOM GROUP BY tbl2013BOM.fJobID HAVING (((tbl2013BOM.fJobID)= " & [Forms]![frmBOM_Combined]![Combo25].[Value] & "));"
strSQL3 = "SELECT DISTINCT [TOTAL-LENGTH-WEIGHT], [MATERIAL], [ORDER] FROM " & "[" & [Forms]![frmBOM_Combined]![Text26].[Value] & "]" & " ORDER BY [ORDER];"
Debug.Print strSQL
Debug.Print strSQL2
Debug.Print strSQL3
DoCmd.OpenForm ("frmEstWeight")
Forms!frmEstWeight.RecordSource = strSQL
Forms!frmEstWeight.frmTestBomWeight.Form.RecordSource = strSQL2
Forms!frmEstWeight.frmTotalLengthWeight.Form.RecordSource = strSQL3
End Sub
This is working exactly as it should with no errors or anything.
This is within a form correct?
If so, replace the manual values you put in with
REST OF THE QUERY HERE " & Me!Controlname.value & " REST OF THE QUERY HERE
and if you are using something with a control source then just reset the control source value.
me!ControlWithResult.control source = "SELECT tbl2013BOM.fJobID, Sum (tbl2013BOM.fWeight) AS SumOffWeight
FROM tbl2013BOM
GROUP BY tbl2013BOM.fJobID
HAVING (((tbl2013BOM.fJobID)=" & me!controlname.value & "));"
For Non-Steel try:
On the VBA for the popup form
me!Combo25.rowsource = "SELECT tbl2013BOM.fJobID, Sum(tbl2013BOM.fWeight) AS SumOffWeight
FROM tbl2013BOM
GROUP BY tbl2013BOM.fJobID
HAVING (((tbl2013BOM.fJobID)=" & forms!MAINFORMNAME! &"));
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.