ADO Failing Second time through a loop - sql-server

I have an Access application (Access Front-end, SQL backend using Linked tables) and I'm having this issue:
The user enters in a part number. Then they enter in a quantity. New business logic says I have to check this part number against a table, to see if we have quoted it to a customer within the past year. If we have, we can quote it now; if not, we have to reject the part.
When the program was originally written, they used a datasheet format to allow the user to copy from Excel a list of parts, paste them into the datasheet, and then copy and paste in a list of quantities. Once the quantity is entered/pasted for a line, that line is processed.
If I manually enter in a part number, then the quantity, the needed code checks to see if the part number has been quoted within the last year, and everything is peachy.
But if I copy and paste in a list of parts, the first time through the process it works fine; but every other time fails.
Here's the basic code:
Dim cn21 As ADODB.Connection
Set cn21 = New ADODB.Connection
Dim strsql21 As String
Dim cm21 As New ADODB.Command
Dim rs21 As New ADODB.Recordset
' gblODBCString = "ODBC;Description=PartsPortalsSql;DRIVER=SQL Server;SERVER=db-TEST-partsptl-primary;Trusted_Connection=Yes;APP=Microsoft Office 2010;DATABASE=PartsPortalSQL;"
cn21.Open gblODBCString
cm21.ActiveConnection = cn21
' All I want to know is how many records there are for this part within the last year...
strsql21 = "Select count(*) from tblQuoteDetail tqd INNER JOIN tblQuotes tq on tqd.quoteid = tq.quoteid WHERE " _
& " tqd.qdetailpartno = '" & Me.QDetailPartNo & "' AND tq.quotesentdate >= '" & OneYearAgo & "' AND tqd.qdetailunitprice > 0"
cm21.CommandText = strsql21
Set rs21 = New ADODB.Recordset
Set rs21 = cn21.Execute(strsql21, varparams, adCmdText)
If rs21(0) = 0 Then ' nothing found! can't be escalated...
blah blah blah...
end if
' done with this part... clean up
Set cm21 = Nothing
rs21.Close
Set rs21 = Nothing
Set cn21 = Nothing
Then it will return here when it gets to the next part...
But if i do it manually, it works fine. But when it is running through the loop of parts, it gets to the:
Set rs21 = cn21.Execute(strsql21, varparams, adcmdtext)
line
and takes about 30 seconds to 'process/time out', and then the rs21(0) returns "Run-time error '3265': Item cannot be found in the collection corresponding to the requested name or ordinal."
I've verified every field in the sql query is correctly populated. I've never run into this issue before.
Other things I've attempted - use DAO instead of ADODB... no luck...
If anyone has any suggestions I'm all ears... short of blowing it up... I'm in the testing phase of a replacement version, that doesn't use that copy/paste functionality. But that's still a month or two away from production.
Thanks
Access 2016, Linked tables to Microsoft SQL Server 2016 I believe...
Windows 10 64 bit
Also running on a VPN, connected to a VDI.

I htink youre using the wrong commands to check for recordsets. I could be wrong as I dont use ADO as much as I should, but here is my template for it if you thin itll help
'needs the MSO AtiveX Data Objects Library
Dim vbSql As String, cnnstr as string
Dim cnn As ADODB.Connection
Dim rs As New ADODB.Recordset
vbSql = "SELECT ;"
Set cnn = New Connection
cnnstr = ""
cnn.Open cnnstr
rs.CursorLocation = adUseClient
rs.Open vbSql, cnn
cnn.Close
Set cnn = Nothing

Related

ADODB Recordset from Access VBA to a SQL Server fails to update a Boolean field from true to false

I'm maintaining an Access 365 database (32-bit) running on devices using Access 365 Runtime (32-bit) on Windows 10 & 11. The back-end uses Microsoft SQL Server Express (64-bit), version 15.0.4198.2, on AWS RDS. For one feature, the code uses ADODB 2.8 (the VBA reference is Microsoft ActiveX Data Objects 2.8 Library) to open a Recordset, connect to a table, and modify some fields.
The code was working just fine until I included a line to switch a boolean field from true to false. After this change, the code would throw error #-2147217864 with the description Row cannot be located for updating. Some values may have been changed since it was last read.. I isolated the code to a unit test and ensured that no other lines of code changed the recordset, but the code still threw the error.
Here's the unit test with some helper functions shown but not included:
Private Sub TestRelistingDataChangeProcess()
On Error GoTo TestFail
Dim itemSku As String
itemSku = "1234"
Dim verifySql As String
verifySql = StrFormat("SELECT failedImport FROM dbo.myTable WHERE SKU = '{0}'", itemSku)
Dim rsSql As String
rsSql = StrFormat("UPDATE dbo.myTable SET failedImport = 0 WHERE SKU = '{1}'", itemSku)
ExecuteCommandPassThrough rsSql
rsSql = "PARAMETERS SKU Text ( 255 ); SELECT * FROM myTable WHERE SKU=[SKU]"
Dim cmd As ADODB.Command
Set cmd = New ADODB.Command
cmd.ActiveConnection = GetCurrentConnection()
cmd.CommandText = rsSql
Dim param As ADODB.Parameter
Set param = cmd.CreateParameter(Name:="[SKU]", Type:=adLongVarChar, Value:=itemSku, Size:=Len(itemSku))
cmd.Parameters.Append param
Dim rs As ADODB.Recordset
Set rs = New ADODB.Recordset
rs.Open cmd, , adOpenDynamic, adLockOptimistic
With rs
Debug.Print "1. Setting field to TRUE."
.Fields("failedImport") = True
.Update
Assert.IsTrue ExecuteScalarAsPassThrough(verifySql)
Debug.Print "2. Setting field to FALSE."
.Fields("failedImport") = False
.Update
Assert.IsFalse ExecuteScalarAsPassThrough(verifySql)
End With
Assert.Succeed
TestExit:
Exit Sub
TestFail:
Assert.Fail "Test raised an error: #" & Err.Number & " - " & Err.Description
Resume TestExit
End Sub
Searching for information on this error led to many possibilities, not all of them in VBA. I was aware of issues with Access and SQL Server tables with nullable boolean fields, so I verified the SQL Server table had a default value for the field. I tried numerous CursorType and LockType combinations when opening the recordset. None worked.
What am I doing wrong that causes this error to be thrown? What can I do to change the code so that it works?
After serious searching and testing, I found this blog post which included this line from the [9 Nov 2009 8:49] Tonci Grgin post:
rsCustomers.Properties("Update Criteria").Value = adCriteriaKey
I didn't recognize the adCriteriaKey enum, so I searched, found, and read this MS documentation page. This enum family "specifies which fields can be used to detect conflicts during an optimistic update of a row of the data source with a Recordset object." Specifically, the adCriteriaKey value "detects conflicts if the key column of the data source row has been changed, which means that the row has been deleted."
Through some testing and debug statements, I learned the recordset I opened used adCriteriaUpdCols by default. This value "detects conflicts if any of the columns of the data source row that correspond to updated fields of the Recordset have been changed." For whatever reason, ADODB was identifying a conflict when there shouldn't be one. I wondered whether the bug had something to do with VBA using -1 as true where SQL Server uses 1, but that doesn't appear to be the case based on this SO post.
I also don't know why the previous version of code worked when changing the boolean field from false to true but not from true to false. Perhaps there is a way to trace into the ADODB code and determine exactly what's going wrong here, but I don't know how to do it yet. I've already spent HOURS on this bug, so I need to move on... :-)
As such, here's the line of code I added to make everything work:
Dim rs As ADODB.Recordset
Set rs = New ADODB.Recordset
rs.Open cmd, , adOpenDynamic, adLockOptimistic
rs.Properties("Update Criteria").Value = adCriteriaKey ' <----- NEW LINE HERE
Note that this line will only work for you if your table includes a primary key and you use it in your Recordset. Also, here's another forum post showing the adCriteriaKey saving the day.
I hope this writeup makes sense to others and helps save someone in the future some time! If nothing else, it was a good exercise for me. :-)

Speed up code for referencing / importing data from SQL Server into Excel

I have a problem here that I will probably need to solve in several iterations, this being the first. maybe you have an idea:
I need to speed up regular updates from 4 views in an SQL database on Azure to an Excel Worksheet. There are probably a lot of moving parts here, among them: The code, my connection to the internet, the service that provides me with a static IP address, the fact that I reference views and not tables, and the (rather basic) service level I booked in Azure.
What my code does is simple: it opens a connection, updates 4 worksheets from 4 views (3 of them with 5 - 10 rowns of data, 1 with about 2.000), and closes the connection again.
This takes up to 30 seconds, which seems an awfully long time.
I would like to make sure it is not my code that slows this down. My first attemt was to use Powerquery for the connections, call the connection, then set autofilters with VBA to have only the rows visible that I needed. That took ages.
My second try is: not use Powerquery, get rid of the autofilters, and use VBA instead to pass a SELECT to the SQL Server, so the work of selecting the data is done there, and send over only what I need. But it seems the difference that this makes is not really significant.
The code I use is:
Dim WS1 As Worksheet
Set WS1 = Worksheets("Eingabe")
Dim SelectedCustomer As Range
Set SelectedCustomer = WS1.Range("C39")
Dim cn As ADODB.Connection
Dim SQLStr1 As String
Dim rs1 As ADODB.Recordset
Set rs1 = New ADODB.Recordset
Dim SQLStr2 As String
Dim rs2 As ADODB.Recordset
Set rs2 = New ADODB.Recordset
Dim SQLStr3 As String
Dim rs3 As ADODB.Recordset
Set rs3 = New ADODB.Recordset
Dim SQLStr4 As String
Dim rs4 As ADODB.Recordset
Set rs4 = New ADODB.Recordset
'Open a connection to SQL Server - using the connection string provided by Azure
Set cn = New ADODB.Connection
cn.Open "Driver={ODBC Driver 17 for SQL Server};Server=tcp:server-
displacethis.database.windows.net,1433;Database=my-database;Uid=my-userID;Pwd=my-
password;Encrypt=yes;TrustServerCertificate=no;Connection Timeout=30;"
'First table
SQLStr1 = "SELECT CustomerID,CustomerUniqueName,CustomerFirstName,CustomerLastName,TransactionDate,EmissionenNeukauf,EmissionenSecondhand,600 As DurchschnittDE, 300 As Paris2030 FROM vAccumulatedEmissionsByCustomer WHERE CustomerUniqueName = '" & SelectedCustomer & " ' "
rs1.Open SQLStr1, cn, adOpenStatic
'Dump to spreadsheet
With Worksheets("vAccumulatedEmissions").Range("A2:I100")
.ClearContents
.CopyFromRecordset rs1
'Do some column formatting
Worksheets("vAccumulatedEmissions").Range("F:F").NumberFormat = "#,##0.00"
'Tidy up
rs1.Close
Set rs1 = Nothing
End With
'Then the next table follows. The connection os closed after the last table:
cn.Close
Set cn = Nothing
My questions is, very simply, is this highly ineffective code, and could that be the reason for the long time it runs, or should I look somewhere else to speed up the whole process?
Thank you in advance,
Ulrich

How to autopopulate the field of a table in MS Access with a Identity created in seperate table

I am building a form in MS Access using linked ODBC tables which is to become a large input basis for some of our teams. I have most of it worked out however am trying to auto generate a Primary ID for the main table and populate the text box so the agent doesn't have to. Effectively building an auto number in SQL.
I have the generation and rolling edits in the ODBC tables working and am just stuck on getting the generated code into the relevant field. I am using a macro on button click and have the following code:
Private Sub cmdSubmitDetails_Click()
Dim strSQL As String
Dim rst As Recordset
Dim db As Database
Dim para As String
para = InputBox("Please Enter Your User ID:")
strSQL = "SELECT Min([P_ID]) AS ID FROM QA_IDS WHERE EV_ID = " & para & ";"
Set db = CurrentDb
Set rst = db.OpenRecordset(strSQL)
Me.ID.Text = rst!ID
rst.Close
Set rst = Nothing
Set db = Nothing
End Sub
Me.ID is the field I am trying to populate. It is erroring out in the line
Set rst = db.OpenRecordset(strSQL)
with the error Run-time error '3464' Data type mismatch in criteria expression.
It's my first real dabble in MS Access and I've searched for the solution online. Any help would be greatly appreciated.
I managed to achieve what I was trying using a different method and thought I would post for someone to find useful in the future.
To get around the issue of inconsistent datatype I used a query to run the SQL (including inputting the User ID) , and append it to a new table. Then pull the information from that table with a DLookup, then purge that table. I ended up with the following code.
Private Sub cmdSubmitDetails_Click()
Dim x As String
Dim y As String
Dim z As String
DoCmd.SetWarnings False
DoCmd.OpenQuery ("AppendID")
x = DLookup("[ID]", "[TempID]")
Me.ID = x
y = DLookup("[EV_ID]", "[TempID]")
Me.EVALUATOR_ID = y
DoCmd.OpenQuery ("qryDELETE_TEMP_ID")
DoCmd.OpenQuery ("qryAddNextCallID")
DoCmd.OpenQuery ("qryDELETE_MINIMUM_ID")
DoCmd.SetWarnings True
End Sub

Microsoft Access Checkbox with SQL Backend Does Not Update

I've been researching it and have found many similar cases... but none exactly the same. I've tried a lot of different resolutions in the previous cases mentioned and none of them fixed this one.
I just recently did a SQL Server Migration and am now troubleshooting the issues that have sprung up with the new SQL back end. This is the last one that I can not figure out:
I have a split form with a check box control, bound to a SQL Server View through a DSN-Less connection. Before the migration the Bound Access Query could not be updated... so I programatically updated the forms checkboxes through VBA using two different methods:
Was a check all/ check none checkbox which when clicked checked or unchecked all of the checkboxes in the datasheet.
Gave the ability for the user to check or uncheck each individual checkbox by using the checkboxes Mouse_Down Event.
Here is the code for each of the two methods:
' Check All/ Check None
Dim rsSelect As DAO.Recordset
Dim rsUpdate As DAO.Recordset
Dim SQL As String
Dim CurrDb As Database
Dim currFilter As String
On Error GoTo chkSelect_Click_Error
' Capture current filter
If Me.FilterOn Then currFilter = Me.Filter
Set rsSelect = Me.RecordsetClone
Set CurrDb = CurrentDb
rsSelect.MoveFirst
Do While Not rsSelect.EOF
SQL = "SELECT * FROM tblTimesheet WHERE [TimesheetID] = " & rsSelect("TimesheetID")
Set rsUpdate = CurrDb.OpenRecordset(SQL, dbOpenDynaset, dbSeeChanges)
If Not rsUpdate.EOF Then
If Me.chkSelect Then
With rsUpdate
.Edit
rsUpdate("TimesheetSelect") = True
.Update
End With
Else
With rsUpdate
.Edit
rsUpdate("TimesheetSelect") = False
.Update
End With
End If
End If
rsSelect.MoveNext
Loop
rsUpdate.Close
rsSelect.Close
Me.Requery
If currFilter > "" Then
Me.Filter = currFilter
Me.FilterOn = True
End If
If Me.chkSelect Then
Me.lblSelect.Caption = "Select None"
Else
Me.lblSelect.Caption = "Select All"
End If
On Error GoTo 0
Exit Sub
chkSelect_Click_Error:
MsgBox "Error " & Err.Number & " (" & Err.Description & ") in procedure chkSelect_Click of VBA Document Form_frmTimesheetSummary"
And Secondly:
' Check/ Uncheck Individual Checkbox
Dim rsUpdate As DAO.Recordset
Dim SQL As String
Dim CurrDb As Database
Dim currFilter As String
' Capture current filter
If Me.FilterOn Then currFilter = Me.Filter
Set CurrDb = CurrentDb
SQL = "SELECT * FROM tblTimesheet WHERE [TimesheetID] = " & Me.TimesheetID
Set rsUpdate = CurrDb.OpenRecordset(SQL, dbOpenDynaset, dbSeeChanges)
If Not rsUpdate.EOF Then
If Me.TimesheetSelect Then
With rsUpdate
.Edit
rsUpdate("TimesheetSelect") = False
.Update
End With
Else
With rsUpdate
.Edit
rsUpdate("TimesheetSelect") = True
.Update
End With
End If
End If
rsUpdate.Close
Me.Form.Requery
'Me.Repaint
Me.Refresh
If currFilter > "" Then
Me.Filter = currFilter
Me.FilterOn = True
End If
Both of these procedures worked with an Access back end... but the "Check Individual" procedure refuses to work now. When I check a checkbox it does update the SQL Backend... but the control itself refuses to update the new status... I of course have tried Requery, but also Repaint and Refresh and it refuses to update unless I completely close the form and reopen it again.
The real kicker in all this is that the Check All method still works! I've spent hours on this and am hoping to get some fresh eyes on it because it should be working if the backend is updating!!
ADDITIONAL NOTES ADDED LATER: In response to some of the great reasoning below I feel I should include this additional notes:
I am using SQL Server 2012 and the SQL Server Native Client 11.0 Driver for my connection string.
I am using Microsoft Access 2010 32 bit
The SQL Server field is a bit I've removed all nulls and set allow nulls to 'no' with a default of 0
Some things come to mind:
1) You can replace the whole rsUpdate construction in the second procedure by this:
SQL = "UPDATE tblTimesheet SET TimesheetSelect = " & _
IIf(Me.TimesheetSelect, "0", "-1") & _
" WHERE TimesheetID = " & Me.TimesheetID
CurrDb.Execute SQL, dbSeeChanges
It depends on your TimesheetSelect datatype whether you should use "-1" or "1".
2) Me.Form.Requery should be Me.Requery.
3) If it still doesn't work, adding a TIMESTAMP column to tblTimesheet might help Access recognize that the record was changed. This is generally a good thing to have, but shouldn't be necessary.
Recommended reading: https://stackoverflow.com/a/2861581/3820271
tblTimesheet does have a primary key, doesn't it?
If Andre's suggestions don't solve it, instead of Me.Requery, try resetting the form's recordsource. Me.RecordSource = .
You don't say what version of SQL Server you are using or which ODBC driver. Make sure you are using the correct ODBC Driver for your version of SQL Server and not the default 'SQL Server' driver.
Thanks to all who posted... the answer that ended working for me in this case was ditching the split form... I had heard it suggested that split forms can be problematic at times so redesigned the form in a standard Parent/ Subform setup which completely resolved the issue.

Duplicate rows inserted in linked table when updating Form's RecordSource with VBA

I am doing some maintenance work on a linked-table application in Microsoft Access 2010 and experiencing this little gem of a problem. The application is linked to a SQL Server 2008 database. The application has a main form that allows a user to choose a combination of park code and resource and pops up an edit form for the details of that particular combination. If that combo doesn't exist in the database, the application inserts a new record in, but the issue is that 2 records get inserted.
Here's the seat of the problem code, it gets called when I need to insert a new record in a details popup form:
Private Sub New_Rec(unit_code As String, resource As String, sql As String)
DoCmd.RunSQL ("INSERT INTO PARK_RESOURCES (unit_code, resource, sensitivity) VALUES " _
& "('" & unit_code & "','" & resource & "','public')")
'Force an explicit save
'http://www.pcreview.co.uk/forums/update-cancelupdate-without-addnew-edit-t1150554.html
If Me.Dirty Then
Me.Dirty = False
End If
Me.RecordSource = sql
End Sub
Creating a "new" record results in 2 records getting inserted into the Recordset. It doesn't seem to matter if I move the explicit save code before or after setting the RecordSource. In either order (and stopping after either) produces 2 new records inserted in the database (verified by querying in SSMS).
When I set the RecordSource property and step through the code, the event chain looks like: Me.RecordSource = sql --> Form_BeforeUpdate() --> Form_AfterUpdate() --> Form_After_Insert() --> Form_Current(). The duplicate is not present at the close of BeforeUpdate, but by the time I get to AfterUpdate, the duplicate has already been inserted. What happens between BeforeUpdate and AfterUpdate that causes this to happen?
According to MSDN, the order is: BeforeInsert → BeforeUpdate → AfterUpdate → AfterInsert. They also state that setting the value of a control through Visual Basic doesn't trigger these events. But when I update the RecordSource in code, the last 3 events certainly fire; BeforeInsert is the only one that a step-through doesn't stop on.
As per Daniel Cook's request, here is the calling code.
Private Sub Form_Load()
On Error GoTo Err_Form_Load
Me.KeyPreview = True
If Not IsNull(Me.OpenArgs) Then
ProcessOpenArgs (Me.OpenArgs)
Me.lblHeader.Caption = Me.unit_code & ": Resource - " & Me.resource
Else
Me.lblHeader.Caption = "Information Needs"
End If
... (error trapping)
End Sub
And the ProcessOpenArgs sub (OpenArgs get set as "park;resource"):
Private Sub ProcessOpenArgs(open_args As String)
On Error GoTo Err_ProcessOpenArgs
Dim Args() As String
Args = Split(open_args, ";")
Me.unit_code = Args(0)
Me.resource = Args(1)
'Check to see if there are records in the database for current unit/resource combo'
Dim rs As DAO.Recordset
Dim sql As String
sql = "SELECT * FROM PARK_RESOURCES " & _
"WHERE resource='" & Me.resource & "' AND unit_code='" & Me.unit_code & "'"
Set rs = CurrentDb.OpenRecordset(sql, dbOpenDynaset, dbSeeChanges)
'if there aren''t, create a new record'
If (rs.RecordCount = 0) Then
New_Rec Me.unit_code, Me.resource, sql
Else 'go to the current listing'
Me.RecordSource = sql
End If
Exit_ProcessOpenArgs:
Exit Sub
Err_ProcessOpenArgs:
MsgBox Err.Number & Err.description
Resume Exit_ProcessOpenArgs
End Sub
I will continue to comb through the event documentation and as a last resort I may go totally nuts and just stick every possible event in my VBA code and step through them, but does anyone know what could be happening to cause the duplicates?
In your ProcessOpenArgs, the If (rs.RecordCount = 0) Then line could be a problem unless you first use rs.MoveLast - see here
When I'm setting Me.unit_code and Me.unit here:
Private Sub ProcessOpenArgs(open_args As String)
On Error GoTo Err_ProcessOpenArgs
Dim Args() As String
Args = Split(open_args, ";")
Me.unit_code = Args(0)
Me.resource = Args(1)
the code is creating 1 record and then New_Rec inserts a second record in the DB. When the Form automatically Requeries after Me.RecordSource = sql, it sticks the first record (created by the Me.xxx = yyyy statements in ProcessOpenArgs into the DB too and then pulls both back out to the Form Recordset. That's where the double insert is coming from.
In order to correct it, I changed Me.unit_code and Me.resource to local subroutine variables l_unit_code and l_resource and used those instead in ProcessOpenArgs. That took care of this problem as well as a second problem that I had with records form one resource type bleeding into other resource types.
Thanks all for the assist!

Resources