Edit records in ADODB recordset - vba

What I am trying to do is to get some data from an online server through an SQL Query and then loop through the recordset modifying the records.
I get an error when trying to modify the recordset:
"Multiple-Step operation generated errors. Check each status value."
My question is: Is there a way of modifying a record from a recordset that I got from a Query?
In this case I am modifying field 2 if field 1 meets a certain criteria. (In this case Field 2 is a string)
Here is the simplified code:
Dim adoConn As ADODB.Connection
Dim locRS As New ADODB.Recordset, proRS As ADODB.Recordset
Dim strConnection As String
Set getSQL = New ADODB.Recordset
'Set Objects
Set adoConn = New ADODB.Connection
'Specify connection string
strConnection = "User ID=xxx; Password=xxx;Data Source=xxx;Provider=OraOLEDB.Oracle"
'Open the connection
adoConn.Open (strConnection)
'Set up recordset properties
getSQL.CursorType = adOpenStatic
getSQL.CursorLocation = adUseClient
getSQL.LockType = adLockBatchOptimistic
'Import the data
getSQL.Open "SELECT FIELD1, FIELD2 FROM TABLE", adoConn, adOpenStatic, adLockOptimistic
Set getSQL.ActiveConnection = Nothing
getSql.Update
'Loop through data
getSQL.MoveFirst
Do While Not stockRS.EOF
'If cetrain condition is met then modify the null column
if getSQL!FIELD1=CRITERIA then
'Error here
getSQL!FIELD2="SOME STRING"
End If
getSQL.MoveNext
Loop
'Close
adoConn.Close
Set adoConn = Nothing

Your SQL is not doing what you think:
SELECT ... NULL OUTCOME ... is going to return the value NULL in a field called OUTCOME but will not link to a field in the table called OUTCOME (which is what I think you are looking for) as your current syntax is setting up an ALIAS not selecting the field. I am assuming the field OUTCOME exists on the table. If not you need to create it up front or do an alter table to add the field before you can write anything to it.
I recommend creating field up front (which I think you have already done). But make sure that the default value is NULL so you don't need to do your NULL trick in the select ALSO make sure that the field is allowed to take a NULL value or you will see errors. Select becomes:
getSQL.Open "SELECT FIELD1, FIELD2, OUTCOME FROM TABLE", adoConn, adOpenStatic, adLockOptimistic
And then manage the NULL value in the function as follows:
if getSQL!FIELD1=CRITERIA then
'Error here
getSQL!OUTCOME="SOME STRING"
ELSE
getSQL!OUTCOME=NULL
End If
This ensure that you always write something to OUTCOME field so processing and OUTCOME don't get out of sync.
Also I still think that you have divorced the recordset data from the server when you:
Set getSQL.ActiveConnection = Nothing
Do this to release resources after your are done.
You may also need a
getSql.Update
After making changes to commit them back to database.

Related

VBA SQL pulled data coming back as empty

I'm pulling into Excel VBA from a SQL ADODB Connection and it seems that some fields are coming back as empty that have values in SQL. I'm very green in VBA (just diving back into a legacy application to try and migrate everything to SQL Database storage instead of CSVs)
Here's an example of the value return (just a " where we should have "Sample Data | QRSTE/ S179399")
The code to pull:
Sub GetDFInfoByDf(recordID As String, connectionString as String)
Dim connectionString As String
connectionString = connectionString
Dim command As String
command = "Select * FROM data_table WHERE id = '" & recordID & "'"
Dim conn As ADODB.Connection
Dim rs As ADODB.Recordset
Set conn = New ADODB.Connection
Set rs = New ADODB.Recordset
conn.Open connectionString
rs.CursorLocation = adUseClient
rs.CursorType = adOpenStatic
rs.LockType = adLockBatchOptimistic
Set rs = conn.Execute(command)
Dim rsMatrix As Variant
rsMatrix = rs.GetRows(1)
If IsNull(rs) Then
'rs is null
MsgBox "Pulled recordset is null"
Else
Call FillObjValuesFromRecordSet(rs)
End If
I see that we have a somewhat special character in there (|)
In terms of any SQL Encoding configurations: It's most likely UTF-8. I would think that I have to convert that to ANSI either in VBA or on the SQL side, but have been running in circles to try and figure that out.
Note that this field is NVARCHAR in SQL
Any ideas on how to handle this? Documentation is very sparse on the subject, from what I've seen.
Thanks!
Things I've Tried:
Adding Session Mode=ANSI; to the connection string
Expected Outcome:
-SQL stores the varchar "Sample Data | QRSTE/ S179399" (no quotes in the field)
-I'm expecting my Select to return that exact varchar/string value instead of the return in the image (")
Solution:
My SQL table columns with varchar(max) or nvarchar(max) were not able to translate back.
My initial table had larger-than-needed sizes, so altering those columns to varchar(8000) and nvarchar(4000) fixed the issue!
Thanks

Access subtract form value from another query

In MS Access using VBA trying to subtract a calculated value (time between dates) from a total amount of time in another table.
The query holding the total amount of time is QryScsBatchHandler the field I want to subtract the value from is FreezerLifeUsed and the match condtion is BatchID
The query holding the value I want to subtract is QrySCSMaterialFreezerLog or the form is Frm_MaterialFreezerLog.. the value is AccumilatedTime and the Match condition is Batch ID
Private Sub BkInFrzr_Click()
Dim db As DAO.Database
Dim rst As DAO.Recordset
Dim strSQL As String
Set db = CurrentDb()
strSQL = "SELECT FreezerLifeUsed FROM QryScsBatchHandler WHERE [BatchID] = BatchID"
Set rst = db.OpenRecordset(strSQL, dbOpenDynaset)
With rst
.MoveFirst
.Edit
FreezerLifeUsed = -AccumilatedTime
.Update
End With
End Sub
I can’t seem to get this simple subtraction to work… any suggestions on what’s not right?
Any help will be greatly appreciated..
At least 3 issues with code.
concatenate reference to form field/control for dynamic criteria in SQL:
strSQL = "SELECT FreezerLifeUsed FROM QryScsBatchHandler WHERE [BatchID] = " & Me.BatchID
The VBA needs to use dot or bang when referencing controls or fields and qualifying with prefix is advised:
!FreezerLifeUsed
Me.AccumilatedTime
Nothing is subtracted from anything, so consider:
!FreezerLifeUsed = !FreezerLifeUsed - Me.AccumilatedTime
However, if query is expected to return only one record, don't bother with loop. Also, instead of opening a recordset, an UPDATE action would work.

In access VBA, Is there a way to look-up a record in a table using a combo-box criteria and adding that record to a different table?

I am trying to use unbound comboBoxes and textBoxes where a user a updates the controls and clicks on a button and a new record is created in another tblEntry using some data of the same record from tblItems.
Problem: My code only works on the first record. It creates the new record in the tblEntry using data of the first record in tblItems. Can someone have a look please?
Private Sub addItem_Click()
Dim rs1 As DAO.Recordset
Dim rs2 As DAO.Recordset
Set rs1 = CurrentDb.OpenRecordset("SELECT * FROM tblItems")
Set rs2 = CurrentDb.OpenRecordset("SELECT * FROM tblEntry")
If Not IsNull(Me.combo1) Then
rs2.AddNew
rs2.Fields("Description").Value = rs1.Fields("Description").Value
rs2.Fields("ItemNo").Value = rs1.Fields("ItemNo").Value
rs2.Fields("ItemName").Value = Me.txtItemName.Value
rs2.Fields("entryDate").Value = Me.txtentryDate.Value
rs2.Update
Form.frmItemEntryDatasheet.Requery
End If
rs1.Close
Set rs1 = Nothing
rs2.Close
Set rs2 = Nothing
End Sub
As #June7 says, there probably is no reason to do this. However, what you need to do is to open rs1 up filtered to just show data relating to that selected in combo1. Assuming that the first column in combo1 is the Primary Key from tblItem and called "ItemID":
Set rs1 = CurrentDb.OpenRecordset("SELECT ItemDescription, ItemNo FROM tblItems WHERE ItemID ='" & ItemID.Value & "'")
I have also renamed your field "Description" to "ItemDescription" as it is probably a reserved word within Access and may case problems. I have also just selected the 2 fields that you are going to use - there is no point getting all of the fields. You should be opening both recordsets within the If/End If statement.
Also, when you are opening rs2, you are effectively selecting the whole table. Far better is to use:
Set rs2 = CurrentDb.OpenRecordset("SELECT * FROM tblEntry WHERE 1 = 2")
This opens up a recordset based on tblEntry, but with no records selected, and so therefore has less overhead.
Regards,

VBA SQL select query but only show some rows

I have a table that contains data that I am trying to import into a spreadsheet control on a userform in vba/excel.
The results will be viewed by an end user, so I have set the value of the header cells on initialization as opposed the the column headings from the sql table.
My query looks something like this
Dim conn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim sConnString As String
sConnString = "Provider=SQLOLEDB;Data Source=mysource;Initial Catalog=mydatabase;User Id=myusername;Password=mypassword;QuotedID=No"
Set conn = New ADODB.Connection
Set rs = New ADODB.Recordset
conn.Open sConnString
Set rs = conn.Execute("SELECT * FROM WOFT_tbl_clients WHERE userid = '" & UserId.Value & "';")
If Not rs.EOF Then
/////////////////
/something here!/
/////////////////
Else
MsgBox "Error: No records returned.", vbCritical
End If
If CBool(conn.State And adStateOpen) Then conn.Close
Set conn = Nothing
Set rs = Nothing
What I am trying to do is get the output of selected columns from the database and be able to feed them into whatever colomn I like on the spreadsheet control.
So in effect I would somehow like a loop that allows me to output the resultset of columns id, name and userid into the spreadsheet control starting from row 2. My database also contains many other columns which are not needed in this spreadsheet, but will be needed for another spreadsheet control on the same userform, some of which will appear on both.
What I would like to be able to do is have each column in its own recordset, so I could have something like ids stored in a id recordset which I could then use in column A in spreadsheet control 1, and colomn 6 in spreadsheet control 2?
I hope this makes sense! I am using Excel 2010
To have each data column from one table in an extra recordset would lead you to isolated data lists, which wouldn't follow any logic of a relational database model. If the data indeed is isolated in the way, that id is a list, name is another list and so on, living in the same table, then - forgive me - your database model is quite bad.
If you nevertheless want to get only certain columns from your SQL database, specify them in the SQL statement:
SELECT id, name, userid FROM tablename WHERE anycondition ORDER BY name
To loop through every record the recordset object returns, use a do-until loop like this. Don't forget the MoveNext, or it may result in an endless loop.
Do Until rs.EOF
... do anything you want with the record ...
rs.MoveNext
Loop
I hope this is what you needed. Feel free to further explain, if I didn't understand your needs correctly.

How to get id of newly inserted record using Excel VBA?

Seems a common enough problem this, but most solutions refer to concatenating multiple SQL commands, something which I believe can't be done with ADO/VBA (I'll be glad to be shown wrong in this regard however).
I currently insert my new record then run a select query using (I hope) enough fields to guarantee that only the newly inserted record can be returned. My databases are rarely accessed by more than one person at a time (negligible risk of another insert happening between queries) and due to the structure of the tables, identifying the new record is normally pretty easy.
I'm now trying to update a table that does not have much scope for uniqueness, other than in the artificial primary key. This means there is a risk that the new record may not be unique, and I'm loathe to add a field just to force uniqueness.
What's the best way to insert a record into an Access table then query the new primary key from Excel in this situation?
Thanks for the replies. I have tried to get ##IDENTITY working, but this always returns 0 using the code below.
Private Sub getIdentityTest()
Dim myRecordset As New ADODB.Recordset
Dim SQL As String, SQL2 As String
SQL = "INSERT INTO tblTasks (discipline,task,owner,unit,minutes) VALUES (""testDisc3-3"",""testTask"",""testOwner"",""testUnit"",1);"
SQL2 = "SELECT ##identity AS NewID FROM tblTasks;"
If databaseConnection Is Nothing Then
createDBConnection
End If
With databaseConnection
.Open dbConnectionString
.Execute (SQL)
.Close
End With
myRecordset.Open SQL2, dbConnectionString, adOpenStatic, adLockReadOnly
Debug.Print myRecordset.Fields("NewID")
myRecordset.Close
Set myRecordset = Nothing
End Sub
Anything stand out being responsible?
However, given the caveats helpfully supplied by Renaud (below) there seems nearly as much risk with using ##IDENTITY as with any other method, so I've resorted to using SELECT MAX for now. For future reference though I would be interested to see what is wrong with my attempt above.
About your question:
I'm now trying to update a table that
does not have much scope for
uniqueness, other than in the
artificial primary key. This means
there is a risk that the new record
may not be unique, and I'm loathe to
add a field just to force uniqueness.
If you are using an AutoIncrement for your primary key, then you have uniqueness and you could use SELECT ##Identity; to get the value of the last autogenerated ID (see caveats below).
If you are not using autoincrement, and you are inserting the records from Access but you want to retrieve the last one from Excel:
make sure your primary key is sortable, so you can get the last one using a query like either of these:
SELECT MAX(MyPrimaryField) FROM MyTable;
SELECT TOP 1 MyPrimaryField FROM MyTable ORDER BY MyPrimaryField DESC;
or, if sorting your primary field wouldn't give you the last one, you would need to add a DateTime field (say InsertedDate) and save the current date and time every time you create a new record in that table so you could get the last one like this:
SELECT TOP 1 MyPrimaryField FROM MyTable ORDER BY InsertedDate DESC;
In either of these cases, I think you would find adding an AutoIncrement primary key as being a lot easier to deal with:
It's not going to cost you much
It's going to guarantee you uniqueness of your records without having to think about it
It's going to make it easier for you to pick the most recent record, either using ##Identity or through sorting by the primary key or getting the Max().
From Excel
To get the data into Excel, you have a couple of choices:
create a data link using a query, so you can use the result directly in a Cell or a range.
query from VBA:
Sub GetLastPrimaryKey(PrimaryField as string, Table as string) as variant
Dim con As String
Dim rs As ADODB.Recordset
Dim sql As String
con = "Provider=Microsoft.ACE.OLEDB.12.0;" & _
"Data Source= ; C:\myDatabase.accdb"
sql = "SELECT MAX([" & PrimaryField & "]) FROM [" & MyTable & "];"
Set rs = New ADODB.Recordset
rs.Open sql, con, adOpenStatic, adLockReadOnly
GetLastPrimaryKey = rs.Fields(0).Value
rs.Close
Set rs = Nothing
End Sub
Note about ##Identity
You have to be careful of the caveats when using ##Identity in standard Access databases(*):
It only works with AutoIncrement Identity fields.
It's only available if you use ADO and run SELECT ##IDENTITY;
It returns the latest used counter, but that's for all tables. You can't use it to return the counter for a specific table in MS Access (as far as I know, if you specify a table using FROM mytable, it just gets ignored).
In short, the value returned may not be at all the one you expect.
You must query it straight after an INSERT to minimize the risk of getting a wrong answer.
That means that if you are inserting your data at one time and need to get the last ID at another time (or another place), it won't work.
Last but not least, the variable is set only when records are inserted through programming code.
This means that is the record was added through the user interface, ##IDENTITY will not be set.
(*): just to be clear, ##IDENTITY behaves differently, and in a more predictive way, if you use ANSI-92 SQL mode for your database.
The issue though is that ANSI 92 has a slightly different syntax than
the ANSI 89 flavour supported by Access and is meant to increase compatibility with SQL Server when Access is used as a front end.
If the artificial key is an autonumber, you can use ##identity.
Note that with both these examples, the transaction is isolated from other events, so the identity returned is the one just inserted. You can test this by pausing the code at Debug.Print db.RecordsAffected or Debug.Print lngRecs and inserting a record manually into Table1, continue the code and note that the identity returned is not that of the record inserted manually, but of the previous record inserted by code.
DAO Example
'Reference: Microsoft DAO 3.6 Object Library '
Dim db As DAO.Database
Dim rs As DAO.Recordset
Set db = CurrentDb
db.Execute ("INSERT INTO table1 (field1, Crdate ) " _
& "VALUES ( 46, #" & Format(Date, "yyyy/mm/dd") & "#)")
Debug.Print db.RecordsAffected
Set rs = db.OpenRecordset("SELECT ##identity AS NewID FROM table1")
Debug.Print rs.Fields("NewID")
ADO Example
Dim cn As New ADODB.Connection
Dim rs As New ADODB.Recordset
Set cn = CurrentProject.Connection
cn.Execute ("INSERT INTO table1 (field1, Crdate ) " _
& "VALUES ( 46, #" & Format(Date, "yyyy/mm/dd") & "#)"), lngRecs
Debug.Print lngRecs
rs.Open "SELECT ##identity AS NewID FROM table1", cn
Debug.Print rs.Fields("NewID")
Re: "I have tried to get ##IDENTITY working, but this always returns 0 using the code below."
Your code sends SQL and SQL2 through different connection objects. I don't think ##identity will return anything other than zero unless you ask from the same connection where you executed your INSERT statement.
Try changing this:
myRecordset.Open SQL2, dbConnectionString, adOpenStatic, adLockReadOnly
to:
myRecordset.Open SQL2, databaseConnection, adOpenStatic, adLockReadOnly
Here's my solution that does not use ##index or MAX.
Const connectionString = "Provider=SQLOLEDB; Data Source=SomeSource; Initial Catalog=SomeDB; User Id=YouIDHere; Password=YourPassword"
Const RecordsSQL = "SELECT * FROM ThatOneTable"
Private Sub InsertRecordAndGetID()
Set connection = New ADODB.connection
connection.connectionString = connectionString
connection.Open
Set recordset = New ADODB.recordset
recordset.Open SQL, connection, adOpenKeyset, adLockOptimistic
With recordset
.AddNew
!Field1 = Value1
!Field2 = Value2
End With
recordset.MoveLast
ID = recordset.Fields("id")
End Sub
Enjoy!
Try following macro code.First add a command button to the sheet from the control box and paste following codes in the code window
Private Sub CommandButton1_Click()
MsgBox GetLastPrimaryKey
End Sub
Private Function GetLastPrimaryKey() As String
Dim con As String
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim sql As String
con = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\myaccess.mdb;Persist Security Info=False"
sql = "SELECT MAX(id) FROM tblMyTable"
Set cn = New ADODB.Connection
Set rs = New ADODB.Recordset
cn.Open con
rs.Open sql, cn, 3, 3, 1
If rs.RecordCount <> 0 Then
GetLastPrimaryKey = rs.Fields(0).Value
End If
rs.Close
cn.Close
Set rs = Nothing
Set cn = Nothing
End Function
8 years late to the party... The problem you are having is that you are using dbConnectionString to create a new connection. ##identity is specific to the connection you are using.
First, don't close the original connection
'.Close
replace
myRecordset.Open SQL2, dbConnectionString, adOpenStatic, adLockReadOnly
with the connection you previously used for the insert
myRecordset.Open SQL2, databaseConnection, adOpenStatic, adLockReadOnly
and you'd have been all set. In fact, you don't even need to specify the table:
SQL2 = "SELECT ##identity AS NewID"