ADODB Recordset fields access order gives empty values - sql-server

I got an SQL 2005 table with many (84 to be specific) fields (actually it is a query returned by a procedure)
It looks like when I access recordset fields placed later then some field placed earlier becomes empty while server had actually returned a value for it
Had anyone such problem?
My solution is to put such disappeared field at the end of a table so when it is accessed later by a code (here VBA) its value is still accessible BUT I see it as a big problem in ADODB.Recordset 2.8 as I should not care about field order
I know that question is not very specific but maybe someone had a similar issue?

One way to to make sure the field values are there is to pass on the recordset to a array like (You will have to build your own connection function):
Function getStoredProcedure() As Variant
Dim rs As ADODB.Recordset
Dim cmd As ADODB.Command
Dim conn As ADODB.Connection
Dim values As Variant
Set conn = getConn("Server", "Database")
Set cmd = New ADODB.Command
cmd.ActiveConnection = conn
cmd.CommandType = adCmdStoredProc
cmd.CommandText = "StoredProcedureName"
cmd.Parameters.Item("#TODAY") = today
Set rs = cmd.Execute
If Not rs.EOF Then
values = rs.GetRows
Else
Exit Function
End If
Set cmd = Nothing
getStoredProcedure= transposeArray(values)
End Function
From there you can always retrieve the values from the array. Otherwise, without seeing your code or understand what you are trying to do, I cannot tell if this is really an issue with ADODB because I cannot recreate this issue when pulling field items in any order I want such as: rs.Fields.Item(i).Value for i = any number in any order.

I met this problem twice. There are two exception fields in my query string. Running the query string in sql server, they can return the values but these two exception fields are empty when run the query string in VBA used recordset. I put these exception fields behind other normal fields in the query string, and then they can return value in the recordset instead of blank. So it's really a big problem.

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. :-)

Error: Row cannot be located for updating. Some values may have been changed since it was last read. On changing provider from SQLOLEDB to MSOLEDBSQL

On change of provider from SQLOLEDB to MSOLEDBSQL in the ADODB connection string, we get the error:
-2147217864 Row cannot be located for updating. Some values may have been changed since it was last read.
The connection string is:
Provider=MSOLEDBSQL;SERVER=servername;APP=Applicationname;DATABASE=databasename;WSID=id;Trusted_Connection=yes;MARS Connection=True;DataTypeCompatibility=80
And the code looks like:
Dim rs As ADODB.Recordset
Set rs = New ADODB.Recordset
rs.CursorLocation = adUseClient
rs.Open("SELECT * FROM tableName WHERE 1 = 2", Adoconnection, adOpenStatic, adLockBatchOptimistic, CommandTypeEnum.adCmdText)
rs.AddNew
'Add the fields
...
...
rs.UpdateBatch ''this line throws error
Now, when in the connection string of provider is changed to SQLOLEDB, with the same code it works great without any issue.
Try adding a timestamp, or so called "rowversion" column to the table. (use type timestamp - which has ZERO to do with time).
Also, if you have any bit columns in that table, then make sure they are not null, and make sure a default value of (0) is set for that bit column.
And if the application has linked tables, then re-link your tables after you made the above change server side.
I found out the issue, it was in the SQL triggers.
The respective table had some update statements on the trigger. Adding SET NOCOUNT ON just before the update statement in the trigger helped me to avoid this error.
I found my way to this thread because I had a similar error. I am using Access 365 VBA 32-bit on Windows 10. Here's a snippet of my code (some details omitted):
Dim cmd As ADODB.Command
Set cmd = New ADODB.Command
cmd.ActiveConnection = GetCurrentConnection()
cmd.CommandText = sql
Dim rs As ADODB.Recordset
Set rs = New ADODB.Recordset
rs.CursorLocation = adUseClient
rs.Open cmd, , adOpenDynamic, adLockOptimistic
With rs
.Fields("FailedImport") = True
.Fields("ImportErrors") = ReadErrorsFromResponse(xmlDoc)
.Update
End With
I also encountered the same error as in the OP. In my case, I didn't get the error until I included the update to the boolean field called FailedImport. I found my way to this forum post from 2008, containing this comment (formatting mine):
Had same error, however had it with recordsets rather than data control.
From another forum, I learnt that if you are using a static cursor for the recordset, using adLockBatchOptimistic instead of adLockOptimistic solved the problem.
So, I changed this line:
rs.Open cmd, , adOpenDynamic, adLockOptimistic
to this:
rs.Open cmd, , adOpenDynamic, adLockBatchOptimistic
And my code worked again! Hope this helps someone else in the future!

Zeros rows returned from ADO Recordset

I'm trying to get data from a SQL Server table into an ADO Recordset using the below code. Everything works fine with no errors, but I always get a record count of -1. I've confirmed that the database name and table are correct. If I use SSMS I can see that there is data in the table.
What am I missing here?
Dim cn As New ADODB.Connection
Dim rs As New ADODB.Recordset
Dim rsCount As Integer
cn.ConnectionString = "Provider=SQLNCLI11;Server=Server1;DataBase=Database1;Trusted_Connection=yes;"
cn.Open
Call rs.Open("Select * from table1", cn, adOpenDynamic, adLockOptimistic)
rsCount = rs.RecordCount
As i mentioned in a comment to the question, replace adOpenDynamic with adOpenStatic and you should get proper recordcount.
The use of the ADO Recordset's .RecordCount property requires either
the use of:
1. Static or Keyset server-side cursors or
2. A client-side cursor (which returns a Static cursor)
The RecordCount property will return -1 for a forward-only cursor; the
actual count for a static or keyset cursor; and either -1 or the
actual count for a dynamic cursor, depending on the data source.
More details here and here!

XML Created in TSQL Used as ADODB.Recordset

I am attempting to use XML that was generated from a stored procesure in our MS SQL database as a Recordset in a VS2005 Application. The issue I am having is that when reading in the xml as a string, the string result comes in as "System.Byte[]". Seeing this I changed the datatype from String to Byte() and tried to use the Byte array. The Byte array does not seem to have anything to do with the data I want to be receiving. I am wondering if there is a way to handle SQL generated XML files that I am not aware of. Here is some sample code.
This is what the result of the stored procedure looks like when in SQL SMS
With this code I get the System.byte[] as my string:
Dim ADOrs As ADODB.Recordset
Dim SQLString1 As New System.Text.StringBuilder(180)
Dim catzzz as String
SQLString1.Append("exec reports_selectReportMetaData #companyCode = '001'")
ADOrs = fnReturnRecordset(SQLString1.ToString) 'function executes the query
Do While Not ADOrs.EOF
catzzz = ADOrs("XML_F52E2B61-18A1-11d1-B105-00805F49916B").Value.ToString
Debug.WriteLine(catzzz)
Loop
This is the way I get the really odd Byte Array
Dim ADOrs As ADODB.Recordset
Dim SQLString1 As New System.Text.StringBuilder(180)
Dim catzzz As Byte()
SQLString1.Append("exec reports_selectReportMetaData #companyCode = '001'")
ADOrs = fnReturnRecordset(SQLString1.ToString)'function executes the query
Do While Not ADOrs.EOF
catzzz = ADOrs("XML_F52E2B61-18A1-11d1-B105-00805F49916B").Value
Loop
The Byte array looks like
And when converted to ASCII using
catX = System.Text.Encoding.Default.GetString(catzzz)
the first three characters (that should be <rt ) Come up as
So I think my main issue is that I am missing the proper way to bring in the XML created in SQL
Any Ideas would be appricated!
Ended up changing from a stored procedure to a function so that we would be returend a value instead of just having the command executed. This solved the System.Byte[] issue.

ADODB open recordset fails / "Operation is not allowed when object is closed"

I have the following UDF in excel which uses ADO to connect to my MSSQL server. There it should execute the scalar udf "D100601RVDATABearingAllow".
For some reason the parameters that I try to append are not send to the sql server. At the server only:
SELECT dbo.D100601RVDATABearingAllow
arrives.
MY EXCEL UDF:
Function RVDATA(Fastener) As Long
Dim cnt As ADODB.Connection
Dim rst As ADODB.Recordset
Dim Cmd1 As ADODB.Command
Dim stSQL As String
Const stADO As String = "Provider=SQLOLEDB.1;Data ................"
'----------------------------------------------------------
Set cnt = New ADODB.Connection
With cnt
.ConnectionTimeout = 3
.CursorLocation = adUseClient
.Open stADO
.CommandTimeout = 3
End With
'----------------------------------------------------------
Set Cmd1 = New ADODB.Command
Cmd1.ActiveConnection = cnt
Cmd1.CommandText = "dbo.D100601RVDATABearingAllow"
Cmd1.CommandType = adCmdStoredProc
'----------------------------------------------------------
Set Param1 = Cmd1.CreateParameter("Fastener", adInteger, adParamInput, 5)
Param1.Value = Fastener
Cmd1.Parameters.Append Param1
Set Param1 = Nothing
'----------------------------------------------------------
Set rst = Cmd1.Execute()
RVDATA = rst.Fields(0).Value
'----------------------------------------------------------
rst.Close
cnt.Close
Set rst = Nothing
Set cnt = Nothing
'----------------------------------------------------------
End Function
When I use adCmdStoredProc the whole thing fails and in the vba debugger the properties of the recordset has a lot of "Operation is not allowed when object is closed" (may sound a bit different, the message is translated)
When I don't use adCmdStoredProc I get the message that the variable Fastener was not provided.
I think that maybe something is wrong in the way I open the recordset.
In other treads I read about using the "SET NOCOUNT ON" option, but that did not work either.
Does anyone have a idea?
Regards Lumpi
Ran into this error as well (in my case I am using a Stored Procedure to retrieve some information). I had made some changes which caused the execution to malfunction.
The error disappeared when I put SET NOCOUNT ON as the first statement of the Stored Procedure.
You do not need to SELECT the server side function, just provide its name ("[tra-CAE400-1].dbo.D100601RVDATABearingAllow") in the .CommandText property.
Also you should set the .CommandType property to "stored-procedure" (property reference on w3schools.com).
Then adodb will know that you are talking about calling a function, and not trying to send a plain sql-command.
Chances are that it will then allow you to define the parameters on the command object.
But the parameters you define on the command object should correspond exactly (in name and type) to the ones that are defined as the arguments of the function in the sql server.
An example from microsoft.com on using the command-object with a stored procedure
ADO Reference on microsoft.com
Another possible cause of this is debug statements. I just spent far too long trying to work out why this wouldn't work for me, the Proc on the database worked fine, the data it was supposed to insert was inserted, the VBA code worked fine, but there was nothing in the recordset.
Final solution was to go through the procs that had been built and remove the PRINT statements.
To test if this is the problem, run your proc on SQL Server manually, then look at the messages tab of the results, if there's anything there other than "Command(s) completed successfully." you need to eliminate those messages. "SET NOCOUNT ON" will get rid of the row count messages, but there may be others.
I'm assuming that after 5 years the OP has solved this particular problem, so this is just for anyone like me that finds this while searching for the same problem.
I also ran into this with a stored procedure. Did you SET NOCOUNT = OFF; at the bottom of your code? That is what worked for me after lots of googling. Also, if you have any other code that runs, you have to wrap it in Nocount = on/off, INCLUDING insert and update statements. You would think that an insert statement wouldn't matter but wrapping the code that way is what kept me from committing suicide today.
In our shop we often use lines like this in our stored procedures to assist with debugging:
RAISERROR('Debug message here',0,1) WITH NOWAIT;
This also breaks opening a recordset in Excel vba. I believe the complete answer for this question is, in the stored procedure:
use SET ROWCOUNT OFF
remove all PRINT statements
remove all RAISEERROR statements used for debugging (ie severity of 0)

Resources