Form / ListBox "Insert Into" Syntax Error - ms-access

I have a database that I am trying to send information contained on a form combined with selected items in a listbox to a table when the user clicks a Send button. I have the code setup that should copy my information but get a syntax error and I am not sure why... I have tried several different things and can't get it to work. I have included the code below:
Private Sub ctrSend_Click()
Dim intI As Integer
Dim lst As ListBox
Dim varItem As Variant
Set lst = Me![lstShipping]
With lst
If .ItemsSelected.count = 0 Then Exit Sub
For Each varItem In .ItemsSelected
CurrentDb.Execute "INSERT INTO ShipInv ([Order], [ShipDate], [BIN], [SKU], [Lot], [QtyProd])" _
"VALUES ('" & Me.[ctrSOrder] & "'," & Me.[ctrSDate] & ",'" & .Column(0, varItem) & "'," & .Column(1, varItem) & "," & .Column(2, varItem) & "," & .Column(3, varItem) & ");", dbFailOnError
Next
End With
End Sub

For a situation like this, I always reccommend using a string to hold the constructed SQL so that you can easily print the string to the immediate window to check how certain values have broken your SQL.
So, try adding
Dim strSQL As String
strSQL = "INSERT INTO ShipInv ([Order], [ShipDate], [BIN], [SKU], [Lot], [QtyProd])" _
"VALUES ('" & Me.[ctrSOrder] & "'," & Me.[ctrSDate] & ",'" & .Column(0, varItem) & "'," & .Column(1, varItem) & "," & .Column(2, varItem) & "," & .Column(3, varItem) & ");"
Debug.Print strSQL
CurrentDb.Execute strSQL 'remove dbFailOnError temporarily so that failure will stop code
My blind guess is that if ShipDate is a date field(not text), you'll need to format that value with Format(Me.[ctrSDate], "\#mm\/dd\/yyyy\#" before pasting it into the SQL.

I used a different approach and it works out great...
Private Sub ctrSend_Click()
Dim intI As Integer
Dim lst As ListBox
Dim varItem As Variant
Dim rst As DAO.Recordset
Set lst = Me![lstShipping]
Set rst = CurrentDb.OpenRecordset("ShipInv", dbOpenTable)
With lst
If .ItemsSelected.count = 0 Then Exit Sub
For Each varItem In .ItemsSelected
rst.AddNew
rst!Order = Me.[ctrSOrder]
rst!EntDate = Date
rst!ShipDate = Me.[ctrSDate]
rst!BIN = .Column(0, varItem)
rst!SKU = .Column(1, varItem)
rst!Lot = .Column(2, varItem)
rst!QtyProd = .Column(3, varItem)
rst.Update
Next
End With
rst.Close
Set rst = Nothing
MsgBox "Warehouse Inventory Updated", vbOKOnly, "Inventory Confirmation"
End Sub

Related

Searching function for textbox and letting my function still run when there are none entries in for the textbox and listbox

All I really need to know is how to make it where I can make selections in multiple multi-select listboxes, but leave any number of them blank and still have the macro/query work without having to put in an error message about it.
This also includes doing the same with the textboxes. The textboxes would function the same as the listboxes where they would search for anything in a data table to matches what I am looking for in the records and display what I am looking for in a table.
Here is my code
Private Sub Command62_Click()
Dim db As DAO.Database
Dim qdf As DAO.QueryDef
Dim varItem As Variant
Dim District As String
Dim Circumstance As String
Dim Location As String
Dim Method As String
Dim Point As String
Dim Rank As String
Dim strSQL As String
Set db = CurrentDb()
Set qdf = db.QueryDefs("qryMultiselect")
For Each varItem In Me!District.ItemsSelected
District = District & ",'" & Me!District.ItemData(varItem) & "'"
Next varItem
If Len(District) = 0 Then
MsgBox "You did not select anything in the Distrcit field." _
, vbExclamation, "Nothing to find!"
Exit Sub
End If
District = Right(District, Len(District) - 1)
For Each varItem In Me!Circumstance.ItemsSelected
Circumstance = Circumstance & ",'" & Me!Circumstance.ItemData(varItem) &
"'"
Next varItem
If Len(Circumstance) = 0 Then
MsgBox "You did not select anything in the Circumstance field." _
, vbExclamation, "Nothing to find!"
Exit Sub
End If
Circumstance = Right(Circumstance, Len(Circumstance) - 1)
For Each varItem In Me!Location.ItemsSelected
Location = Location & ",'" & Me!Location.ItemData(varItem) & "'"
Next varItem
If Len(Location) = 0 Then
MsgBox "You did not select anything in the Location field." _
, vbExclamation, "Nothing to find!"
Exit Sub
End If
Location = Right(Location, Len(Location) - 1)
For Each varItem In Me!Method.ItemsSelected
Method = Method & ",'" & Me!Method.ItemData(varItem) & "'"
Next varItem
If Len(Method) = 0 Then
MsgBox "You did not select anything in the Method field." _
, vbExclamation, "Nothing to find!"
Exit Sub
End If
Method = Right(Method, Len(Method) - 1)
For Each varItem In Me!Point.ItemsSelected
Point = Point & ",'" & Me!Point.ItemData(varItem) & "'"
Next varItem
If Len(Point) = 0 Then
MsgBox "You did not select anything in the Point field." _
, vbExclamation, "Nothing to find!"
Exit Sub
End If
Point = Right(Point, Len(Point) - 1)
For Each varItem In Me!Rank.ItemsSelected
Rank = Rank & ",'" & Me!Rank.ItemData(varItem) & "'"
Next varItem
If Len(Rank) = 0 Then
MsgBox "You did not select anything in the Rank field." _
, vbExclamation, "Nothing to find!"
Exit Sub
End If
Rank = Right(Rank, Len(Rank) - 1)
strSQL = "SELECT * FROM tblDataEntry " & _"WHERE tblDataEntry.District
IN(" & District & ") AND tblDataEntry.Circumstance IN(" & Circumstance &
") AND tblDataEntry.Location IN(" & Location & ") AND tblDataEntry.Method
IN (" & Method & ") AND tblDataEntry.Point IN (" & Point & ") AND
tblDataEntry.Rank IN(" & Rank & ");"
qdf.SQL = strSQL
DoCmd.OpenQuery "qryMultiselect"
Set db = Nothing
Set qdf = Nothing
End Sub
I still need to add the textboxes, but I'm not sure where. (Please note that I'm still learning VBA).
Firstly, since you are repeatedly performing the same operation for each form control (in this case, constructing a comma-delimited string from the selected items), you can abstract this operation away into a function, and pass such function each List Box function.
For example, you could define a function such as:
Function SelectedItems(objBox As ListBox) As String
Dim strRtn As String, varItm
For Each varItm In objBox.ItemsSelected
strRtn = strRtn & ",'" & objBox.ItemData(varItm) & "'"
Next varItm
If strRtn <> vbNullString Then SelectedItems = Mid(strRtn, 2)
End Function
Which could then be evaluated with a List Box control argument, and would return either a null string ("") or a comma-delimited string of the selected items in the list box, e.g. something like:
?SelectedItems(Forms!Form1!List1)
'A','B'
Furthermore, since your form controls appear to be named consistently relative to the fields in your table, you could further condense your code to something along the following lines:
Private Sub Command62_Click()
Dim strSQL As String
Dim strArr As String
Dim varItm
For Each varItm In Array("District", "Circumstance", "Location", "Method", "Point", "Rank")
strArr = SelectedItems(Me.Controls(varItm))
If strArr <> vbNullString Then
strSQL = strSQL & "t." & varItm & " in (" & strArr & ") and "
End If
Next varItm
If strSQL <> vbNullString Then strSQL = "where " & Left(strSQL, Len(strSQL) - 5)
With CurrentDb.QueryDefs("qryMultiselect")
.SQL = "select * from tblDataEntry t " & strSQL
End With
DoCmd.OpenQuery "qryMultiselect"
End Sub
Note that the above is entirely untested.
Here, the main for each loop iterates over an array of strings corresponding to the names of your form controls and the names of your table fields.
For each form control in this array, the function obtains a comma-delimited string of the selected items in the control, and concatenates this with the existing SQL code only if one or more items have been selected.
As such, if not items are selected, the field will not feature in the SQL where clause.
If any filter has been selected, the trailing five characters (and) are trimmed from the end of the SQL string, and the where keyword is concatenated to the start of the SQL string - this ensures that if no filter has been selected, the resulting SQL code will not include a where clause.
Finally, the SQL for the query definition is updated and the query is opened, per your original code.
Where textboxes are concerned, the task merely need to skip the call to SelectedItems and obtain the value of the textbox directly.
Here is an example incorporating both listboxes & textboxes:
Private Sub Command62_Click()
Dim strSQL As String
Dim strArr As String
Dim varItm
For Each varItm In Array("District", "Circumstance", "Location", "Method", "Point", "Rank")
strArr = vbNullString
Select Case Me.Controls(varItm).ControlType
Case acListBox
strArr = SelectedItems(Me.Controls(varItm))
Case acTextBox
If Not IsNull(Me.Controls(varItm).Value) Then
strArr = "'" & Me.Controls(varItm).Value & "'"
End If
End Select
If strArr <> vbNullString Then
strSQL = strSQL & "t." & varItm & " in (" & strArr & ") and "
End If
Next varItm
If strSQL <> vbNullString Then strSQL = "where " & Left(strSQL, Len(strSQL) - 5)
With CurrentDb.QueryDefs("qryMultiselect")
.SQL = "select * from tblDataEntry t " & strSQL
End With
DoCmd.OpenQuery "qryMultiselect"
End Sub
I hope this helps, but please note that the above is untested and only theory.

Looping through folder with SQL query

strQuery = _
"SELECT * FROM [Sheet1$A15:E999] " & _
"IN '" & ThisWorkbook.Path & "\Source1.xlsx' " & _
"[Excel 12.0;Provider=Microsoft.ACE.OLEDB.12.0;Mode=Read;ExtendedProperties='HDR=YES;'] " & _
"UNION " & _
"SELECT * FROM [Sheet1$A15:E999] " & _
"IN '" & ThisWorkbook.Path & "\Source2.xlsx' " & _
"[Excel 12.0;Provider=Microsoft.ACE.OLEDB.12.0;Mode=Read;Extended Properties='HDR=YES;'] " & _
"UNION " & _
"SELECT * FROM [Sheet1$A15:E999] " & _
"IN '" & ThisWorkbook.Path & "\Source3.xlsx' " & _
"[Excel 12.0;Provider=Microsoft.ACE.OLEDB.12.0;Mode=Read;Extended Properties='HDR=YES;'] " & _
"ORDER BY A;"
Good morning,
I have one last nail to go on this coding I have and any help is much appreciated. I am gathering numerous files from a single folder and file names are different (although data order and data are same).
Question is:
Is it possible to get all files via the 'strQuery' without slowing down the code? How do I go on to do this? (eg: I think maybe loop but it might slow down? - see below)
Is it possible to get (say) 100 excel file data read at once? (although I do not know names of it?)
I can modify strQuery (via assigning it a text string) and input a loop to go through every file but I recon this would require me to create a connection for every single file rather than all at once?
Any help is appreciated!
Thanks in advance.
--
Full Code below (I didn't know where to put this in a visible manner)
Sub SqlUnionTest()
Dim strConnection As String
Dim strQuery As String
Dim objConnection As Object
Dim objRecordSet As Object, qText As String
strConnection = _
"Provider=Microsoft.ACE.OLEDB.12.0;" & _
"User ID=Admin;" & _
"Data Source='" & ThisWorkbook.FullName & "';" & _
"Mode=Read;" & _
"Extended Properties=""Excel 12.0 Macro;"";"
Dim sFile As String
sFile = Dir(ThisWorkbook.Path & "\*.xlsx")
Do While sFile <> ""
strQuery = _
"SELECT * FROM [Sheet1$A15:E999] " & _
"IN '" & ThisWorkbook.Path & "\" & sFile & _
"[Excel 12.0;Provider=Microsoft.ACE.OLEDB.12.0;Mode=Read;ExtendedProperties='HDR=YES;'] " & _
"UNION "
sFile = Dir()
Loop
strQuery = Left(strQuery, Len(strQuery) - 7) 'to remove last UNION which is not necessary
Set objConnection = CreateObject("ADODB.Connection")
objConnection.Open strConnection
Set objRecordSet = objConnection.Execute(strQuery)
RecordSetToWorksheet Sheets(1), objRecordSet
objConnection.Close
End Sub
Sub RecordSetToWorksheet(objSheet As Worksheet, objRecordSet As Object)
Dim i As Long
With objSheet
.Cells.Delete
For i = 1 To objRecordSet.Fields.Count
.Cells(1, i).Value = objRecordSet.Fields(i - 1).Name
Next
.Cells(2, 1).CopyFromRecordset objRecordSet
.Cells.Columns.AutoFit
End With
End Sub
You can use the DIR() function to loop through all the .xlsx files in the folder without knowing the specific file names. If you need to weed out any files, you can place conditional testing inside the loop.
Code untested
Dim sFile As String, strQuery As String
sFile = Dir(ThisWorkbook.Path & "\*.xlsx")
Do While sFile <> ""
strQuery = strQuery & _
"SELECT * FROM [Sheet1$A15:E999] " & _
"IN '" & ThisWorkbook.Path & "\" & sFile & _
"[Excel 12.0;Provider=Microsoft.ACE.OLEDB.12.0;Mode=Read;ExtendedProperties='HDR=YES;'] " & _
"UNION;"
sFile = Dir()
Loop
strQuery = Left(strQuery, Len(strQuery) - 7) 'to remove last UNION which is not necessary

Bringing a second column of data into Recordset

I have picked up a project that a prior DBA built before in Access 2010. Currently the coding builds the report, then outputs it in PDF format. Below is the coding in it's current form:
Private Sub Command0_Click()
Dim rst As DAO.Recordset
Set rst = CurrentDb.OpenRecordset("SELECT DISTINCT [Portfolio Code] FROM [General] ORDER BY [Portfolio Code];", dbOpenSnapshot)
Do While Not rst.EOF
strRptFilter = "[Portfolio Code] = " & Chr(34) & rst![Portfolio Code] & Chr(34)
DoCmd.OutputTo acOutputReport, "Main Report", acFormatPDF, "O:\Annual Review\AnnualReviewReport" & "\" & rst![Portfolio Code] & ".pdf"
DoEvents
rst.MoveNext
Loop
rst.Close
Set rst = Nothing
End Sub
What I am trying to do is bring in data from another column in the [General] table from the same row that the [Portfolio Code] is in (it is a name associated with the portfolio ID). The reason is so that I can have the pdf files get sorted into directories by name, e.g.
"O:\Annual Report\AnnualReviewReport\ & [Name] & "\" & [Portfolio Code] & ".pdf"
Is there a way to add the [Name] field into the rst (the name would not be DISTINCT, only the Portfolio Code. I am fairly new at this, so please go easy.
Below is the updated script:
Private Sub Command0_Click()
Dim rst As DAO.Recordset
Set rst = CurrentDb.OpenRecordset("SELECT DISTINCT [Portfolio Code], [Trustee 1] FROM [General] ORDER BY [Portfolio Code];", dbOpenSnapshot)
Do While Not rst.EOF
strRptFilter = "[Portfolio Code] = " & Chr(34) & rst![Portfolio Code] & Chr(34)
DoCmd.OutputTo acOutputReport, "Main Report", acFormatPDF, "O:\Annual Review\AnnualReviewReport" & "\" & rst![Trustee 1] & "\" & rst![Portfolio Code] & " - " & rst![Trustee 1] & ".pdf"
DoEvents
rst.MoveNext
Loop
rst.Close
Set rst = Nothing
End Sub

Fix link on-the-fly as an Error handle for error 3044 or more

I have a massive set of linked databases that have the potential to move. Luckily they are all in ONE working directory of nested folders.
I have effectively created a module that has the path of this working folder defined.
As strWorkingFolder
Now the VBA of the main control center remains intact for multiple calls running and executing queries (append, delete, insert) etc. EXCEPT each of the databases that are still linked to the old folder.
I figured that whenever the error 3044 (Not sure of the exact verbiage "The path to this table does not exist), I could just relink to the correct path - because it is known: It would be strWorkingFolder (concatenated to whatever nested folder the database is in)
I thought I could get away with just linked tables, but apparently, I will need to re-link all kinds of files: csv, Excel, as well as ACCDB.
How can I get it to work?
This is currently what I have setup
Sub RemoveLinks()
Dim tdf As TableDef
For Each tdf In CurrentDb.TableDefs
If Left(tdf.Name, 4) <> "MSys" And (tdf.Attributes And dbAttachedTable) = dbAttachedTable Then
CurrentDb.TableDefs.Delete tdf.Name
End If
Next tdf
Set tdf = Nothing
End Sub
Sub LinkDatabase(StrDBPath As String)
Dim dbs As Database
Dim tdf As TableDef
Set dbs = OpenDatabase(StrDBPath)
For Each tdf In dbs.TableDefs
If Left(tdf.Name, 4) <> "MSys" Then
DoCmd.TransferDatabase acLink, "Microsoft Access", Trim(StrDBPath), acTable, tdf.Name, tdf.Name
SysCmd acSysCmdSetStatus, "Processing table [" & tdf.Name & "]..."
End If
Next tdf
SysCmd acSysCmdClearStatus
Set dbs = Nothing
Set tdf = Nothing
End Sub
Sub RefreshLinks(StrDBPath As String)
Dim tdf As TableDef
For Each tdf In CurrentDb.TableDefs
If (tdf.Attributes And dbAttachedTable) = dbAttachedTable Then
tdf.Connect = "; Database = " & StrDBPath
SysCmd acSysCmdSetStatus, "Processing table [" & tdf.Name & "]..."
tdf.RefreshLink
End If
Next tdf
Set tdf = Nothing
SysCmd acSysCmdClearStatus
End Sub
And finally, in the error_handler, I will trap 3044 and call
Public Sub Relink(strEnginePath)
Dim dbs As Database
Set dbs = CurrentDb
RemoveLinks
LinkDatabase (strEnginePath)
RefreshLinks (strEnginePath)
End Sub
Is there a better way to go about this?
I have altered your code so it will handle text and Excel in addition to Access tables. If you have other types attached, you need to modify the code.
NOTE: With this code, you should NOT delete the links because that will remove all of the attributes you need!
Also, if you have any parameters following the path/file names in the connect strings, you need to add code to retain that information. I hope you have some standards in place that would allow some logical actions to be taken.
Sub RefreshLinks(StrDBPath As String)
Dim iLen As Integer
Dim iStart As Integer
Dim iEnd As Integer
Dim iPos As Integer
Dim strOldConn As String
Dim strNewConn As String
Dim strFile As String
Dim tdf As TableDef
On Error GoTo Error_Trap
For Each tdf In CurrentDb.TableDefs
If (tdf.Attributes And dbAttachedTable) = dbAttachedTable Then
Debug.Print "Table Name: " & tdf.Name
strOldConn = tdf.Connect ' Save the connect string
iLen = Len(strOldConn)
iStart = InStr(1, strOldConn, "DATABASE=") ' Find start of path
iEnd = InStr(iStart + 1, strOldConn, ";") ' Is there more after path?
Debug.Print tdf.Name & ": " & tdf.Connect
If LCase(left(strOldConn, 4)) = "text" Then ' Text file attached
strNewConn = left(strOldConn, iStart + 8) & StrDBPath
ElseIf LCase(left(strOldConn, 5)) = "excel" Then ' Excel file attached
strFile = ""
For iPos = iLen To 1 Step -1 ' Get the file name from the path
If Mid(strOldConn, iPos, 1) = "\" Then Exit For
strFile = Mid(strOldConn, iPos, 1) & strFile
Next
If iPos = 0 Then
MsgBox "Did not find path delimiter '\'" & vbCrLf & vbCrLf & "for TDF '" & tdf.Name & "'", vbOKOnly + vbCritical, "Path Delimiter Unknown"
End If
strNewConn = left(strOldConn, iStart + 8) & StrDBPath & "\" & strFile
Else
' Assume it is Access table. If other types, add code to handle.
strFile = ""
For iPos = iLen To 1 Step -1 ' Get the file name from the path
If Mid(strOldConn, iPos, 1) = "\" Then Exit For
strFile = Mid(strOldConn, iPos, 1) & strFile
Next
If iPos = 0 Then
MsgBox "Did not find path delimiter '\' in connect string '" & strOldConn & "'", vbOKOnly + vbCritical, "Wrong delimiter?"
End If
strNewConn = left(strOldConn, iStart + 8) & StrDBPath & "\" & strFile
End If
Debug.Print " (new): " & strNewConn
tdf.Connect = strNewConn
SysCmd acSysCmdSetStatus, "Processing table [" & tdf.Name & "]..."
tdf.RefreshLink
Else
' Ignore this table since it is not linked.
End If
Next tdf
Set tdf = Nothing
SysCmd acSysCmdClearStatus
Exit Sub
Error_Trap:
MsgBox "Error: " & Err.Number & vbTab & Err.Description & vbCrLf & vbCrLf & _
"While processing table: " & tdf.Name & vbCrLf & _
"Old: " & strOldConn & vbCrLf & _
"New: " & strNewConn, vbOKOnly, "Relink Error"
Exit Sub
End Sub

Audit Track a form

I am setting up a Audit Tracking system for the forms in my database. I am following the example from Susan Harkins Here
My code works for my form customers which is based off the customers table. Here is my code:
Const cDQ As String = """"
Sub AuditTrail(frm As Form, recordid As Control)
'Track changes to data.
'recordid identifies the pk field's corresponding
'control in frm, in order to id record.
Dim ctl As Control
Dim varBefore As Variant
Dim varAfter As Variant
Dim strControlName As String
Dim strSQL As String
On Error GoTo ErrHandler
'Get changed values.
For Each ctl In frm.Controls
With ctl
'Avoid labels and other controls with Value property.
If .ControlType = acTextBox Then
If .Value <> .OldValue Then
MsgBox "Step 1"
varBefore = .OldValue
varAfter = .Value
strControlName = .Name
'Build INSERT INTO statement.
strSQL = "INSERT INTO " _
& "Audit (EditDate, User, RecordID, SourceTable, " _
& " SourceField, BeforeValue, AfterValue) " _
& "VALUES (Now()," _
& cDQ & Environ("username") & cDQ & ", " _
& cDQ & recordid.Value & cDQ & ", " _
& cDQ & frm.RecordSource & cDQ & ", " _
& cDQ & .Name & cDQ & ", " _
& cDQ & varBefore & cDQ & ", " _
& cDQ & varAfter & cDQ & ")"
'View evaluated statement in Immediate window.
Debug.Print strSQL
DoCmd.SetWarnings False
DoCmd.RunSQL strSQL
DoCmd.SetWarnings True
End If
End If
End With
Next
Set ctl = Nothing
Exit Sub
ErrHandler:
MsgBox Err.Description & vbNewLine _
& Err.Number, vbOKOnly, "Error"
End Sub
However, when I try to change data in my subform within the form I get an error "Operation is not supported for this type of object". I can see the error is occuring here:
If .Value <> .OldValue Then
My subform is based off of a query which is based off of three tables
I'm trying to change a customer price under Customer Products and keep a log of those changes. Is there something I'm missing or a work around.
Thank you for the help!
Temporarily disable your error handler like this:
'On Error GoTo ErrHandler
When you get the error notice about "operation not supported", choose Debug from the error dialog. That will allow you to find out more information about the current text box control which is triggering the error. Try the following statements in the Immediate window:
? ctl.Name
? ctl.ControlSource
? ctl.Enabled
? ctl.Locked
? ctl.Value
At least ctl.Name will identify which text box is triggering the error.
After examining the db, I'll suggest a function (IsOldValueAvailable) to indicate whether .OldValue is available for the current control. With that function, the AuditTrail procedure works after this change:
'If .ControlType = acTextBox Then
If IsOldValueAvailable(ctl) = True Then
And the function. It may still need more work, but I didn't spot any problems in my testing.
Public Function IsOldValueAvailable(ByRef ctl As Control) As Boolean
Dim blnReturn As Boolean
Dim strPrompt As String
Dim varOldValue As Variant
On Error GoTo ErrorHandler
Select Case ctl.ControlType
Case acTextBox
varOldValue = ctl.OldValue
blnReturn = True
Case Else
' ignore other control types; return False
blnReturn = False
End Select
ExitHere:
On Error GoTo 0
IsOldValueAvailable = blnReturn
Exit Function
ErrorHandler:
Select Case Err.Number
Case 3251 ' Operation is not supported for this type of object.
' pass
Case Else
strPrompt = "Error " & Err.Number & " (" & Err.Description _
& ") in procedure IsOldValueAvailable"
MsgBox strPrompt, vbCritical, "IsOldValueAvailable Function Error"
End Select
blnReturn = False
Resume ExitHere
End Function