VBA queries and cleaning up strings on exit - vba

If I have a query that I have created using VBA:
dim SQL as string
dim rs as recordset
dim db as database
SQL = "SELECT ... FROM ..."
Set db = CurrentDb
Set rs = db.OpenRecordset(SQL, dbOpenDynaset)
At the end of my sub I would always do the following:
rs.close
set rs = nothing
My question is, do I need to SQL ="" or something of that like? I think my confusion originally came from the fact that I haven't used set SQL in my code.
and if I do clear these strings, then, is there a 'best' way?

Since you're not opening a connection to either CurrentDb or the SQL string, there's no need to close them. However, you are opening a recordset, so that should be closed. It wouldn't harm anything to set SQL = "", but it's not going to actually do anything constructive.
As far as a "best way", I think you've already got it. At the end of your sub, or before any code that might prematurely exit it, just put:
rs.close
set rs = nothing

Related

EOF value is always true even if there is record returned from VBA SQL

I am querying my access table using VBA and writing the query result in excel.
EOF is always true but BOF is False - even if the record count is 1 or 14 or 100. What possibly would be wrong? I can see the record count more than zero. get string value has data in it. Due to this there is no data written in the destination sheet except for Headers. The headers are coming in fine.
List of things tried but result was still same:
Added Move last and Move first command
Tried all possible combinations of Cursor location, cursor type, lock type
Tried with execute command
Tried with different MS access table
Tried early and late binding techniques
Below is my query and link below is my how my record set looks after SQL open statement.
Const MyConn = "Access DB location"
Dim con As ADODB.Connection
Dim rs As ADODB.Recordset
Set con = New ADODB.Connection
With con
.Provider = "Microsoft.ACE.OLEDB.12.0"
.Open MyConn
End With
QuerySql = "SELECT * FROM Store_Location"
Set rs = New ADODB.Recordset
rs.CursorLocation = adUseClient
rs.Open QuerySql, con, adOpenStatic, adLockReadOnly, adCmdUnknown
rs.MoveLast
rs.MoveFirst
i = 0
For i = 0 To rs.Fields.Count - 1
Sheets("Search_Temp").Cells(1, i + 1) = rs.Fields(i).Name
Next i
Range("A2").CopyFromRecordset rs
rs.Close
Set rs = Nothing
con.Close
Set con = Nothing
While debugging this is what my record set looks like:
Building on this answer to a similar question, calling getString on a Recordset object has a side-effect of moving the recordset to EOF.
You don't call getString anywhere in your code but you've added a watch on rs.getString which is visible as the last entry in the Watches window. If you have a watch on rs.getString and you have a breakpoint in the code where rs is open then that breakpoint will cause the recordset to move to EOF.
Depending on where the breakpoint occurs, this might not cause any issues (e.g. if the recordset was already at EOF) but, in this case, it is moving the recordset to EOF before you have copied the data from the recordset.
To solve this issue, remove the watch on rs.getString. It's probably a bad idea in general to have items in the Watches window cause side effects. You could also avoid the issue by not having any breakpoints where the recordset is open but removing the watch entirely is more robust.
The issue of getString moving the recordset to EOF isn't mentioned in the ADO documentation but it's easy to reproduce this effect.
It's uncommon for someone to include the entire list of watches they had set in their question but I'm not sure this question was answerable without that information

MS Access VBA code

I am new to MS Access and would like to type EmployeeID in the text box 1 (text_in) and after the button is pressed the result query (one unique value e.g. employee first name taken from the table) is printed out in the text box2 (text_out).
So far have the following code:
Private Sub btn_get_data_Click()
Dim db As DAO.Database
Dim rs As DAO.Recordset
'declaration of database on recordset objects
Dim strSQL As String
Dim a As String
a = text_in.Value
Set db = CurrentDb
Set rs = db.OpenRecordset("Employee")
strSQL = "SELECT Employee.FirstName FROM Employee WHERE Employee.EmployeeId=" & a
Set db = Nothing
Set rs = Nothing
End Sub
I have tried searching for a solution in many places but I cannot understand the structure that is used in MS access VBA to implement query from regular SQL language.
Guys, thank you very much! It took me one more hour to sucessfully implement both solutions into my database file. As I wrote I am completely new to Ms-access I am learning using some tutorials but my level is still low. Thank you very much once again.
Private Sub btn_get_data_Click()
on error goto errorCatch
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim strSQL As String
Set db = CurrentDb
strSQL = "SELECT Employee.FirstName FROM Employee WHERE Employee.EmployeeId=" & Me.text_in.Value & ";"
Set rs = db.OpenRecordset(strSql, DbOpenDynaset)
If rs.RecordCount > 0 Then
debug.print; rs!FirstName
Me.text_out.Value = rs!FirstName
End if
Cleanup:
Set db = Nothing
Set rs = Nothing
Exit Sub
errorCatch:
debug.print; err.Number
debug.print; Err.Description
GoTo Cleanup
End Sub
I cant tell what youre after is this. You need to be better at utilizing recordsets and how to use a string value as your sql statement.
You also didnt need to create a variable to store the textbox value in- you can use it directly in your sql statement.
Also it is very important to have good error handling as hanging recordset objects can be quite a pain in the ass.
EDIT - Ill expand on other ways to use string sql statements within VBA
DoCmd.RunSQl strSql
Or
DoCmd.Execute strSql
Both of these are great for UPDATE's or INSERT's.
Then, like Gustav pointed out, you have various D functions that are basically compact queries with some limitations. However, they often save you the trouble of having to do all the typing that is involved with opening,utlizing and closing records sets. Even though the D functions seem limited, I have often nested a few to get join results out of. Just a matter of imagination.
Look at this nifty site for function details -
https://www.techonthenet.com/access/functions/index.php
You don't need a query to do this. Just set the ControlSource of text2:
=DLookup("FirstName","Employee","EmployeeId=" & [text_in] & "")

Trying to run a DELETE query using DAO in VBA

Im using Access 2013 and Excel 2013. In terms of References, I am using Microsoft Office 15.0 Access database engine Object Library.
So I am trying to run a DELETE query in VBA. Here is what I have so far
Sub UpdatePartList()
Dim ws As DAO.Workspace
Dim dbs As DAO.Database
Dim rsTable As DAO.Recordset
Dim rsQuery As DAO.Recordset
Dim rsSql As DAO.Recordset
Set ws = DBEngine.Workspaces(0)
Set dbs = ws.OpenDatabase("P:\Distribution Purchasing\Kit Bids\Kit Parts Query.accdb")
'deletes previous part numbers
Sql = "DELETE * FROM [Kit Parts]"
Set rsSql = dbs.OpenRecordset(Sql, dbOpenDynaset)
When I run a SELECT query for the same table, it works just fine. But when I try to DELETE, I get this error.
" Run-time error '3219': Invalid operation. "
DAO's OpenRecordSet is not appropriate for Delete queries, which do not return any recordset object. Use the Execute method instead:
Change
Set rsSql = dbs.OpenRecordset(Sql, dbOpenDynaset)
to
dbs.Execute(Sql)
also there's no need for * in the Delete SQL statement. Although Access will accept it, other systems probably won't.
You can modify your SQL as #Rahul suggested, plus change your last line of code to:
dbs.Execute sql

Delete all of a table's records and replace them with another table's records using VBA

I'm not quite sure why this isn't working. Debug is giving me an error: Item not found for rs2.Fields(fld) = rs1.Fields(fld)
Here's my code:
CurrentDb.Execute "DELETE * FROM " & appendTo_text.Value & ";", dbFailOnError
Set db = CurrentDb()
Dim fld As DAO.Field
Set rs1 = db.OpenRecordset(tableName_text.Value)
Set rs2 = db.OpenRecordset(appendTo_text.Value)
For Each fld In rs1.Fields
rs2.AddNew
rs2.Fields(fld) = rs1.Fields(fld)
rs2.Update
Next
Set fld = Nothing
rs1.Close
rs2.Close
Where's the part where you loop through all the rows? Plus, you're adding a new row for each field, rather than for each row. Finally, the problem is that fld inside the loop is an object, which when treated as a string will implicitly convert, returning the default property of Value. So you're trying to select the field via the field value from the other recordset--you should be using the field name from the other recordset.
Do While Not SourceRecordset.Eof
TargetRecordset.AddNew
For Each Field In SourceRecordset.Fields
TargetRecordset.Fields(Field.Name).Value = Field.Value
Next
TargetRecordset.Update
Loop
Also, could you please not name the variables rs1 and rs2? At least name them SourceRecordset and TargetRecordset. You don't want the developer coming after you to lie awake at night fantasizing about hunting you down and doing bad things to you.
If you are doing a lot of rows, you may also want to look into UpdateBatch and how to disconnect the recordset, so that you have decent performance.
Finally, if you do still have performance problems, recognize that the syntax Recordset.Fields(Field.Name).Value is slow. (If you were using ADODB, the fastest method would be to pass in an array of values directly in the AddNew method). However, you can still get a speed boost by obtaining a reference to each field like so:
Dim SourceOrderID As DAO.Field
Set SourceOrderID = SourceRecordset.Fields("OrderID")
Dim TargetOrderID As DAO.Field
Set TargetOrderID = TargetRecordset.Fields("OrderID")
Then instead of using For Each inside the Do While loop, just assign:
TargetOrderID.Value = SourceOrderID.Value
TargetOrderDate.Value = SourceOrderDate.Value
I guarantee this will be faster.
Also, if you are looking for speed improvements, if you've used With like in examples found in online in Microsoft documentation for DAO, try it without With to see if it's faster.

Execute a dynamic SQL Query against MS Access 2003

This is a super basic question but I'm trying to execute a Query that I'm building via some form values against the MS Access database the form resides in. I don't think I need to go through ADO formally, but maybe I do.
Anyway, some help would be appreciated. Sorry for being a n00b. ;)
You can use the following DAO code to query an Access DB:
Dim rs As DAO.Recordset
Dim db As Database
Set db = CurrentDb
Set rs = db.OpenRecordset("SELECT * FROM Attendance WHERE ClassID = " & ClassID)
do while not rs.EOF
'do stuff
rs.movenext
loop
rs.Close
Set rs = Nothing
In my case, ClassID is a textbox on the form.
This is what I ended up coming up with that actually works.
Dim rs As DAO.Recordset
Dim db As Database
Set db = CurrentDB
Set rs = db.OpenRecordset(SQL Statement)
While Not rs.EOF
'do stuff
Wend
rs.Close
Here just in case you wanted an ADO version:
Dim cn as new ADODB.Connection, rs as new ADODB.RecordSet
Dim sql as String
set cn = CurrentProject.Connection
sql = "my dynamic sql string"
rs.Open sql, cn ', Other options for the type of recordset to open, adoOpenStatic, etc.
While Not rs.EOF
'do things with recordset
rs.MoveNext ' Can't tell you how many times I have forgotten the MoveNext. silly.
Wend
rs.Close
cn.Close
Set rs = Nothing
Set cn = Nothing
DAO and ADO are very close in usage. You get more control with DAO and slightly better performance with ADO. In most access database applications I have come across it really doesn't make a difference. When I have seen a big difference is with linked tables. ADO often performs better.
The answers you've been given and that you seem to be accepting loop through a DAO recordset. That is generally a very inefficient method of accomplishing a text. For instance, this:
Set db = CurrentDB()
Set rs = db.OpenRecordset("[sql]")
If rs.RecordCount > 0
rs.MoveFirst
Do While Not rs.EOF
rs.Edit
rs!Field = "New Data"
rs.Update
rs.MoveNext
Loop
End If
rs.Close
Set rs = Nothing
Set db = Nothing
will be much less efficient than:
UPDATE MyTable SET Field = "New Data"
which can be run with:
CurrentDb.Execute "UPDATE MyTable SET Field = 'New Data'"
It is very seldom the case that one needs to loop through a recordset, and in most cases a SQL update is going to be orders of magnitude faster (as well as causing much shorter read/write locks to be held on the data pages).