VBA collection with 2 or more fields - sql

I'm using a Collection to store data from a recordset in VBA. The recordset has two fields.
I'm using a collection because I want to utilise its ability to prevent duplicates by using the key parameter. I'm running an SQL query to generate the recordset many times and a lot of the results will be identical to the previous, but some will be different. I want to capture a collection of the unique results from each recordset.
I can do this currently using the following:
rs.Open sql_vehicles, cn
If rs.RecordCount > 0 Then
Do While Not rs.EOF
On Error Resume Next
value = rs.Fields("EVN").value
catalogue_Tags.Add Item:=value, Key:=value
rs.MoveNext
On Error GoTo 0
Loop
End If
which all resides in a for loop generating a new recordset each time which may or may not be different.
This will give me a collection with unique values from the "EVN" field in the recordset, but I need to be able to store the second field in the recordset as well, but I want to still avoid duplicates of the EVN field!
Any ideas on how to do this?

Seems to me you can just use 2 collections to get 2 unique lists...
value = rs.Fields("EVN").value
catalogue_Tags_EVN.Add Item:=value, Key:=value
value = rs.Fields("ABC").value
catalogue_Tags_ABC.Add Item:=value, Key:=value
I just notice your requirement is slightly different then I read the first time...
Add "Microsoft Scripting Runtime" reference to your project and use a dictionary of dictionaries.
Dim evnValues as new Scripting.Dictionary ' this will actually be a dictionary of dictionaries.
inside the loop do this...
ABCvalue = rs.Fields("ABC").value
if not evnValues.Exists(value) then
' sub dictionary does not exist yet. initialize the list for this evn value
evnValues(value)=New Scripting.Dictionary
end if
evnValues(value).item(ABCValue)=ABCValue ' accumulate a list of the ABCValues relative to the unique evnValue.
At the end you will have a dictionary with the Names of the top level being your unique EVN values and the sub value will be a collection of 1 or more ABC values.

Related

Dictionary.Item returns collection but Dictionary.Item.Add adds new collection item to every key instead of specified key

I am trying to create a data structure in which a dictionary stores collections assigned to a key as a double. Each collection contains further array variants also. I am looping through rows in worksheet and adding certain values in each row to its associated collection for further manipulation later.
When I am adding data from a row to a collection, whether it belongs in a new collection--ergo a new key value pair in the dictionary--or simply just added to an existing collection, the data in the format of an array variant is being added to every key in the dictionary. Is somebody able to identify my problem?
For Each row In Selection.Rows 'Loop through each row in chunks of 5000
Dim NewInv(0 To 1) As Variant
If MasterDict.Exists(row.Cells(3).Value) Then
NewInv(0) = row.Cells(15).Value
NewInv(1) = row.Cells(15).EntireRow.Address
MasterDict.Item(row.Cells(3).Value).Add (NewInv)
'for some reason the line above is adding the array variant to every collection assigned to every key, not just the specified key.
Else
Dim NewAcct As New Collection
NewInv(0) = row.Cells(15).Value
NewInv(1) = row.Cells(15).EntireRow.Address
NewAcct.Add (NewInv)
MasterDict.Add Key:=row.Cells(3).Value, Item:=NewAcct
End If
Next
In the code above MasterDict is the dictionary in question.
Thank you for your response.
You are making a fundamental error. You only have one NewInv array. Even though you change the values of the individual items this does not make it a new array thus during the loop the reference is to NewInv only and consequently only the last values assigned to NewInv will be visible in each item. To do what I think you intended you need to revise your code as follows
For Each Row In Selection.Rows 'Loop through each row in chunks of 5000
If Not MasterDict.Exists(Row.Cells(3).Value) Then
MasterDict.Add Key:=Row.Cells(3).Value, Item:=New Collection
End If
MasterDict.Item(Row.Cells(3).Value).Add Array(Row.Cells(15).Value, Row.Cells(15).EntireRow.Address)
Next

Specify column index of selected data in ItemBox to retrieve when iterating through selected items

Basically have an ItemBox with three columns in an Access form. I want to get specific columns when I iterate though the selected items, but I am having difficulty doing so.
Function GetSelectedValues(famList As Variant) As Collection
Dim famListColl As New Collection
' Loop through all of the selected surveys
For Each fam In famList.ItemsSelected
Debug.Print (fam) ' prints the index number of the selected item
Debug.Print (famList.ItemData(fam)) ' this prints out the last columns value for the selected item
famListColl.Add famList.ItemData(fam)
Next
Set GetSelectedValues = famListColl
End Function
If I try to do the following I get an Run-time error '424': Object required error:
Debug.Print(fam.Column(1))
Debug.Print(famList.ItemData(fam).Column(1))
This somewhat works, but only displays the values of the first selected item indefinitely (it doesn't iterate):
Debug.Print(famList.Column(1))
So I need to combine the two somehow, but struggling to figure that much.
Thanks for the help!
Stumbled across this post that helped me sort it out. I doubt I would have figured it out otherwise. Looks like this:
Debug.Print (famList.Column(2, fam))
So the entire solution for me is the following:
Function GetSelectedValues(famList As Variant) As Collection
Dim famListColl As New Collection
' Loop through all of the selected surveys
For Each fam In famList.ItemsSelected
famListColl.Add famList.Column(1, fam)
famListColl.Add famList.Column(2, fam)
Next
Set GetSelectedValues = famListColl
End Function

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

Loop through recordset data 10 records at a time and add to array

Context: I am writing a Google Maps API in my Microsoft Access database which takes postcodes in a table and sends them over to Google as part of a HTTP GET request. To make my requests more efficient, I am trying to batch 10 postcodes together at a time to send over to Google. I have chosen 10 as Google has a 2000 character limit in it's GET API requests.
Exam Question: Using DAO recordsets, how do I loop through my Postcodes table, 10 records at a time and add those 10 records to an array, until I reach the end of the table? So in essence, get 10 records from the table, add them to an array, then clear the array, then get the next 10 records and add them to the array, then clear the array...until I reach the end of the table.
My basic code so far is:
Public Function CalcGeoData()
Dim rs As DAO.Recordset
Dim Postcodes(0 To 10) As String
' Begin to loop through the Postcodes in I_Postcodes and update for distance and time from base
Set rs = CurrentDb.OpenRecordset("Postcodes")
If Not (rs.EOF And rs.BOF) Then
rs.MoveFirst
Do Until rs.EOF = True
Debug.Print (rs!Postcode)
Debug.Print "-----------"
rs.Move 10
Loop
End If
rs.Close
Set rs = Nothing
End Function
Inside your loop:
Create another loop from i = 1 to 10. Inside that loop:
Add the values from the current record to myArray(i).
Special case: If the end of the recordset is reached, clear the remaining array entries.
Send the array to Google (or do whatever else you want to do with it).
Translating this to code is left as an exercise.
Note: You might want to consider using a VBA Collection instead of an array, which has a useful Add method.
Use the GetRows method: Moving through the Recordset in Access VBA
Put it inside a loop where you pull 10 records (not RecordCount as shown) in each loop until no more records.

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