Emptying a DAO recordset or Creating an Empty DAO recordset - vba

So this may seem a bit of an odd question. However, I am trying to make a simple If-Else statement regarding whether or not to find records.
The first part goes like this:
If String Meets Criteria
Open Recordset
Else
Recordset Is Empty
End If
I did do a bunch of searching on empty sets, and so far my answer is just to create a quick SQL statement that returns empty. Is that really the simplest way to do this? I would never have set it up this way, but this is the fastest way to fix some code after which goes:
If recordset.recordCount > 0 Then
[Rest of Code]
Is there something a little more obvious that I haven't found yet? Like:
Set Recordset = 0

If you want a recordset that doesn't contain any records and is not updateable, you can use the following code:
Dim rs As DAO.Recordset
Set rs = CurrentDb.OpenRecordset("SELECT 1 From MSysObjects WHERE FALSE")
DAO recordsets are always open and linked to a data source. Something like Dim rs As New DAO.Recordset won't work (in contrast to ADO recordsets, where you can dim an unopened recordset).
If you just want a placeholder object, you can also Set rs = Nothing, but you will encounter run-time errors when trying to access properties or methods in that case.

Related

How to Pass Multivalued field in query to VBA function

In my project I have a table1 that has an ID and a text field. table2 has a multi-valued field multifield wherein I can select multiple values from table (via lookup).
Now, I have query1 where I want to pull the value from multifield - not multifield.value and pass it to a VBA function, like this: IDsToNames: table2.multifield. Problem is that VBA gives me this error:
The multi-valued field 'table2.multifield` is not valied in the expression IDsToNames(table2.multifield)
Now, I've tried with IDsToNames([table2].[multifield]) with the same results.
Here is my query's SQL:
SELECT table2.Title, table2.multifield, IDstoNames(table2.multifield) AS FieldNames
FROM table2;
If I remove the IDsToNames function from the SQL, then table2.multifield by itself will return the IDs like: 5, 4, 1 properly. I'm trying to fetch the second column of table1 instead of the first one that includes the IDs. So i figured I'd try passing that field to a function and perform a string split and them look them up with dlookup in a loop. But I can't get the field data into the function.
Is there a way to do this?
Is there a way to pass a multivalued field directly to a VBA function from within an SQL statement? No, regrettably.
However, there are various alternative methods that you can implement to get the list of values stored in the field. It's easy to ramble on about the pros and cons of Access multivalued fields. I'm not going to do that in detail, but it is worth stating that the primary benefit of a multivalue field is the convenience and apparent simplicity of the Access interface being able to automatically list and allow selection of multiple values of a one-to-many relationship. Trying to mimic that behavior in Access can be very awkward and cumbersome (and often impossible). Much of the implementation details for the multivalued fields are "hidden" (i.e. not well documented or are not exposed to the standard SQL or VBA programming interfaces). This includes the ability to pass the mutlivalued field to a VBA function from within an SQL statement. Regardless of how intuitive the intended behavior seems, Access will not simply pass the same concatenated list of values that it displays to another function. But there are still times when one simply wants the list of values, made accessible in a simple list. The information linked to by Gustav is useful and should be well understood for querying multivalued fields, but it still does not address other perfectly reasonable actions required for multiple values. Hopefully the following pointers are useful.
If the values are needed within a standard SQL statement, I suggest passing the primary key value(s) to a VBA function. Then have the VBA function look up the record and retrieve the multivalued-field values using DAO recordsets.
Because this will call the VBA function for every row, this can be (very) slow. It is possible to optimize the function using various techniques, like opening a static recordset object. Further details are beyond the scope of this answer.
Since you're already in code at this point and can structure VBA and queries however you want, the most efficient query will circumvent the multivalued-field itself and use standard SQL joins to get what you need. For instance, if you want to get all of the related user names, then open and enumerate the following recordset to build your list of names:
sSQL = "SELECT table2.key, table2.multifield.value, UserTable.Username " & _
" FROM UserTable INNER JOIN table2 ON UserTable.ID = table2.multifield.Value" & _
" WHERE (table2.key = [KeyParameter])"
Set qry = CurrentDb.CreateQueryDef(, sSQL)
qry.Parameters("KeyParameter") = keyPassedToFunction
Set rs = qry.OpenRecordset
If the SQL query can/will be opened as a DAO recordset in a code context and you still need to retrieve the multivalued-field as a single field, there is a way to enumerate the multivalued-field in VBA.
If the code ends up repeatedly opening and closing multiple recordsets, especially in multiple nested loops, it is likely that you could improve efficiency by restructuring the SQL using appropriate joins and changing the data processing order.
Rant: As you might notice, it is somewhat inconsistent that the underlying recordset object of an SQL statement does indeed return an object which can be enumerated in code, although the Access SQL engine refuses to pass such an object to a VBA function. The SQL engine already deals with boxing and unboxing data into the VBA variant type, so it seems reasonable that when implementing the multivalue fields, they could have had the SQL engine simply box the multivalued recordset object and passed it to a VBA function to be handled similar to the following code... so the original attempt in the question was not unreasonable.
The following code snippet illustrates that the multivalue field is returned as a DAO.Field object containing a DAO.Recordset2 object:
Dim rs as Recordset2
Set rs = CurrentDB.OpenRecordset("SELECT table2.multifield ... FROM ...")
Dim sList As String
sList = ""
If TypeOf rs![multifield] Is Recordset2 Then
Dim rsMVF As Recordset2
Set rsMVF = rs![multifield]
Dim bFirst As Boolean
bFirst = True
rsMVF.MoveFirst
Do Until rsMVF.EOF
If bFirst Then
sList = rsMVF.Fields(0)
bFirst = False
Else
sList = sList & "," & rs.Fields(0)
End If
rsMVF.MoveNext
Loop
'* DO NOT CLOSE the Recordset based on the Multivalue field.
'* Access will close it automatically.
End If
'* sList will contain comma-separated list of values

Can we format, in VBA, a query field properties after it is create?

In Access, when creating a new query using vba, some of the data I'm getting are currency, but it comes by default as General Number. I would like to change it's format like we can do in the Property Sheet, but I can't seem to find a way.
I have tried
db.QueryDefs("TestQuery").Fields("SumOfSomething").Type = DAO.dbCurrency
But then I get: Run-time error '3219': Invalid operation.
I have searched around a bit and found a similar question (but couldn't find it back) to which the answer was that you can't change that after it is created. I fail to see the point of having the possibility to change the type if we can't actually do it.
So, in the end, can we change (in VBA) the properties of a field after the query is created?
Your question appears to be simple, but as you've probably guessed by now, it isn't.
The format property is a custom property of a field. They can be present, and if they are, they should be changed using the Field.Properties collection, but if they aren't, they need to be created using Field.CreateProperty method and then appended to the Fields.Properties collection
Const currencyFormat As String = "€ #,##0.00;€ #,##0.00-"'Dutch currency format, you need to set this for your locale
Dim qd As DAO.QueryDef
Set qd = CurrentDb.QueryDefs("TestQuery")
On Error Resume Next
qd.Fields("SumOfSomething").Properties!Format = currencyFormat
If Err.Number = 3270 Then 'Property not found, field had no format set
qd.Fields("SumOfSomething").Properties.Append qd.Fields("SumOfSomething").CreateProperty("Format", dbText, currencyFormat)
End If
On Error GoTo 0
You can get the currency format for your locale by setting a field to use it, and then using ?CurrentDb.QueryDefs("TestQuery").Fields("SumOfSomething").Properties!Format
We have to be clear here on the goal. Do you want to “just” cast or change the column type returned in a query, or do you want to modify the actual column type in the database table? These are two VERY different goals.
To use code to modify a column type, you have to create (add) the new column type, transfer the data, and then delete the old column, and then re-name the column back to the old type.
In other words, you can’t just change the data type, since Access will THEN have to update each and every row to the new data type.
The UI system in fact does the above behind the scenes.
If you use DAO code, then you need the following code outlined here:
http://accessblog.net/2007/03/how-to-change-field-type-using-dao.html
However, above is quite a bit of code. In place of DAO code to add the column, you CAN use a DDL command.
(SQL data definition language) to modify the type. Thus in code you can do this:
Dim db As dao.Database
Dim strSQL As String
Set db = CurrentDb
strSQL = "ALTER TABLE dbo_tblHotels1 ALTER COLUMN MyAmount currency"
db.Execute strSQL, dbFailOnError
So in above, we change the column "MyAmount" to currency type.

How to debug.print a query result from the query design in VBA /Access

I want to execute a predefined query from access via VBA and print it to the debug output. The name of the query design is in a variable named report.
I was expecting that it would work with:
debug.print db.Execute report
But everytime vba autocorrects it to:
debug.print db.Execute; report
If I understand it correctly, the ; stands for a new line and makes therefore no sense in my case. But in both cases, with and without the new line I get an error. I assume that the simple problem is, that this is not the right syntax.
I could find a lot of information about how to debug print a query that is created as a string in VBA, but I can't find any hints how to output a query that is predefined in Access via a query design.
Try either:
'// You need the parentheses because the result has to be evaluated before it can be passed to the .Print() method
Debug.Print db.Execute(result)
or
Dim myResult As String
myResult = db.Execute(result)
Debug.Print myResult
In VBA you can pass arguments to a procedure/method without using parentheses, as long as the result isn't being being assigned to anything.
'// Not assigning anything
MyExampleFunction arg1, arg2, arg3
'// assigning the result, which needs to be evaluated before it can be passed to a variable.
MyResult = MyExampleFunction(arg1, arg2, arg3)
In the same way, when you call Debug.Print it assumes that db.Execute and result are actually separate arguments (it has no way of knowing that you want result to be passed to db.Execute first because there's nothing in your syntax to state that). So you have to use parentheses to let it know.
It seems as the problem was that it is only possible to call updates with db.Execute and not queries.
There is no good solution to print a whole table from a query with debug.print but using a RecordSet as seen in the following code is a possible way.
Dim rs As DAO.RecordSet
Set rs = db.OpenRecordset(Bericht)
Do Until rs.EOF = True
Debug.Print rs("Matrikelnummer"), rs("Bewertung"), rs("Termin (Datum)"), rs("Maximale Bewertung"), rs("Bestehensgrenze"), rs("Versuch"), rs("Äquivalenzleistung")
rs.MoveNext
Loop
rs.Close
Set rs = Nothing

recordset.movefirst "Rowset position cannot be restarted"

I have a function that runs a stored procedure that returns only a single row and column (so one result).
I'm trying to get that one result into a variable so I can return it. I'm trying to use recordset.MoveFirst but I get the "Rowset position cannot be restarted." error. I tried just removing it, since I only have one result, but I then get an overflow. My statement looks like this:
If recordset.EOF = False Then
recordset.MoveFirst
temp = rs!ID
End IF
temp is an integer. I've checked the stored procedure to make sure it only returns the single result, and it does. Am I doing something wrong? Is there a better way to pass the result into a variable? It's possible the recordset is forward only (which means it's read only?) but I can't seem to find an answer as to how to fix that.
There is usually no reason to MoveFirst if you have not previously navigated the record set.
The overflow is unrelated to the database code and is caused by rs!ID not fitting in a VBA integer (16 bit) so make temp a Long instead (32 bit) and remove MoveFirst.
Make sure you are not using a forward-only recordset. Recordsets are this way by default. Instead use a dynamic (adOpenDynamic) or static (adOpenStatic) cursor type.
You may also need to set CursorLocation = adUseClient.
Finally, check for BOF before calling MoveFirst.
Example:
...
rs.CursorType = adOpenStatic
rs.CursorLocation = adUseClient
rs.Open "SELECT * FROM MyTable"
...
If (Not rs.BOF) Then
rs.MoveFirst
End If
You can use MoveFirst for forward only recordset ONLY when you are at EOF.
Tricky, undocumented, but WORKS!!!!

Using GetRows on Recordset object returns no rows

I am trying to load a recordset into an array. I am using the following code:
Set rst = CurrentDb.OpenRecordset("SELECT id FROM TABLE1")
bankacid = rst.GetRows()
rst.Close
i = UBound(bankacid, 2)
MsgBox i + 1
This returns no rows. If I use "bankacid = rst.getrows(5)" it works.
I am very new to VBA and would very much
appreciate someone pointing out what I am missing.
It would be best to take a step back and say why you want an array, after all, a DAO recorset is much more functional than an array. If an array is really necessary, use ADODB. If you just want to refer to fields and rows, use Move, MoveFirst, MoveLast, MoveNext, MovePrevious and either the name of the field (column) or the ordinal position (.Field(3).