I am creating a DB in Access and using VBA to implement a feature to run several INSERT queries into multiple tables, based on values found in other 'template' tables.
At a certain point in the process, I am retrieving the results of a SELECT query and using the results of the query as parameters in an INSERT.
I have built and tested the query using the Access query builder so I know that the query functions as expected.
I am using the DAO library to interface with the DB and run my queries.
I have the function below which converts a recordset returned from the latter function to a collection of collections.
In the function below have run into a problem where the recordset I return apparently contains zero records. This causes it to throw a 'No Current Record' exception on the line 'records.MoveLast'.
What I should be seeing, which I know from the query, is a Recordset containing 2 records, with 5 fields each.
Private Function RecordsetToCollection(records As RecordSet) As Collection
Dim recordCollection As New Collection
Dim i As Integer
'Go to first record?
'Exception thrown here
records.MoveLast
records.MoveFirst
'Check if current record position before first record
If Not records.BOF Then
'While not after last record
While Not records.EOF
'Collection to hold field values
Dim fieldCollection As New Collection
'Loop through fields
For i = 0 To records.Fields.Count - 1
'Add to collection
fieldCollection.Add records.Fields(i).Value
Next i
'Add field collection to record collection
recordCollection.Add fieldCollection
Set fieldCollection = Nothing
'Go to next record
records.MoveNext
Wend
End If
'Return collection
Set RecordsetToCollection = recordCollection
End Function
The recordset being fed into this function is retrieved using the following function:
Private Function GetTemplateDeliverables(TemplateProjectActivityID As Integer) As Collection
'Get Template Deliverables recordset from tbl_TemplateDeliverables using given ProjectActivityID
'Open query
Dim qdf As DAO.QueryDef
Set qdf = CurrentDb.QueryDefs("qry_GetTemplateDeliverables")
'Add parameters
qdf.Parameters("Project Activity ID") = ProjectActivityID
'Get return recordset
Dim rst As RecordSet
Set rst = qdf.OpenRecordset()
Dim recordCollection As New Collection
Set recordCollection = RecordsetToCollection(rst)
'Get ProjectActivityID from recordset
Set GetTemplateDeliverables = recordCollection
'Clean up
qdf.Close
Set qdf = Nothing
Set rst = Nothing
End Function
Does anyone have any suggestions as to why this may be the case?
I can't see why this isn't working given that I already have functions to retrieve recordsets that are working fine, the only difference being that in those functions each record has only 1 field, whereas this has 5 fields, but I can't think why this would be a problem.
Any help would be much appreciated!
(P.S. any tips on how to improve my code would also be of help.)
Related
I am having a difficult time how to properly copy specific field data from previous records on my user form. I don't have a code sample to show but my request is very simplistic.
Currently, out of 12 fields, I have 6 that I often repeat data. I can click on and press Ctrl+' ("Insert the value from the same field in the previous record") and it performs the task I want. However, it adds a lot of time to the task. I simply want to write VBA code to perform that command to those specific fields.
I haven't been able to get SendKeys to work. DLast appears to provide random data at times. I feel like this should be a very simple request but for some reason I am not finding a functional solution for it.
Don't fiddle with arrays or queries - use the power of DAO:
Private Sub CopyButton_Click()
CopyRecord
End Sub
If a record is selected, copy this.
If a new record is selected, copy the last (previous) record.
Private Sub CopyRecord()
Dim Source As DAO.Recordset
Dim Insert As DAO.Recordset
Dim Field As DAO.Field
' Live recordset.
Set Insert = Me.RecordsetClone
' Source recordset.
Set Source = Insert.Clone
If Me.NewRecord Then
' Copy the last record.
Source.MoveLast
Else
' Copy the current record.
Source.Bookmark = Me.Bookmark
End If
Insert.AddNew
For Each Field In Source.Fields
With Field
If .Attributes And dbAutoIncrField Then
' Skip Autonumber or GUID field.
Else
Select Case .Name
' List names of fields to copy.
Case "FirstField", "AnotherField", "YetAField" ' etc.
' Copy field content.
Insert.Fields(.Name).Value = Source.Fields(.Name).Value
End Select
End If
End With
Next
Insert.Update
Insert.Close
Source.Close
End Sub
This also, by the way, is an excellent example of the difference between the RecordsetClone and the Clone of a recordset - the first being "the records of the form", while the second is an independant copy.
This also means, that the form will update automatically and immediately.
Provided that it's a simple form to edit a simple table, and that the bound data field names match the control names, you may get away with
If Me.Recordset.AbsolutePosition > 0 Then
With Me.Recordset.Clone()
.AbsolutePosition = Me.Recordset.AbsolutePosition - 1
Dim control_name As Variant
For Each control_name In Array("field1", "field2", "field3", "field4", "field5", "field6")
Me.Controls(control_name).Value = .Fields(control_name).Value
Next
End With
End If
which you assign to a separate button on the same form.
You have a good idea post here already.
You could also say place a function in the before insert event. This event ONLY fires when you start typing into a NEW reocrd, and it becomes dirty.
So, maybe this:
Private Sub Form_BeforeInsert(Cancel As Integer)
Dim rstPrevious As DAO.Recordset
Dim strSQL As String
strSQL = "SELECT TOP 1 * FROM tblPeople ORDER BY ID DESC"
Set rstPrevious = CurrentDb.OpenRecordset(strSQL)
' auto file out some previous values
If rstPrevious.RecordCount > 0 Then
Me.Firstname = rstPrevious!Firstname
Me.LastName = rstPrevious!LastName
End If
End Sub
And some good ideas in say having a "list" or "array" of controls/fields to setup, so you don't have to write a lot of code. (as suggested in the other post/answer here)
I am having a difficult time how to properly copy specific field data from previous records on my user form. I don't have a code sample to show but my request is very simplistic.
Currently, out of 12 fields, I have 6 that I often repeat data. I can click on and press Ctrl+' ("Insert the value from the same field in the previous record") and it performs the task I want. However, it adds a lot of time to the task. I simply want to write VBA code to perform that command to those specific fields.
I haven't been able to get SendKeys to work. DLast appears to provide random data at times. I feel like this should be a very simple request but for some reason I am not finding a functional solution for it.
Don't fiddle with arrays or queries - use the power of DAO:
Private Sub CopyButton_Click()
CopyRecord
End Sub
If a record is selected, copy this.
If a new record is selected, copy the last (previous) record.
Private Sub CopyRecord()
Dim Source As DAO.Recordset
Dim Insert As DAO.Recordset
Dim Field As DAO.Field
' Live recordset.
Set Insert = Me.RecordsetClone
' Source recordset.
Set Source = Insert.Clone
If Me.NewRecord Then
' Copy the last record.
Source.MoveLast
Else
' Copy the current record.
Source.Bookmark = Me.Bookmark
End If
Insert.AddNew
For Each Field In Source.Fields
With Field
If .Attributes And dbAutoIncrField Then
' Skip Autonumber or GUID field.
Else
Select Case .Name
' List names of fields to copy.
Case "FirstField", "AnotherField", "YetAField" ' etc.
' Copy field content.
Insert.Fields(.Name).Value = Source.Fields(.Name).Value
End Select
End If
End With
Next
Insert.Update
Insert.Close
Source.Close
End Sub
This also, by the way, is an excellent example of the difference between the RecordsetClone and the Clone of a recordset - the first being "the records of the form", while the second is an independant copy.
This also means, that the form will update automatically and immediately.
Provided that it's a simple form to edit a simple table, and that the bound data field names match the control names, you may get away with
If Me.Recordset.AbsolutePosition > 0 Then
With Me.Recordset.Clone()
.AbsolutePosition = Me.Recordset.AbsolutePosition - 1
Dim control_name As Variant
For Each control_name In Array("field1", "field2", "field3", "field4", "field5", "field6")
Me.Controls(control_name).Value = .Fields(control_name).Value
Next
End With
End If
which you assign to a separate button on the same form.
You have a good idea post here already.
You could also say place a function in the before insert event. This event ONLY fires when you start typing into a NEW reocrd, and it becomes dirty.
So, maybe this:
Private Sub Form_BeforeInsert(Cancel As Integer)
Dim rstPrevious As DAO.Recordset
Dim strSQL As String
strSQL = "SELECT TOP 1 * FROM tblPeople ORDER BY ID DESC"
Set rstPrevious = CurrentDb.OpenRecordset(strSQL)
' auto file out some previous values
If rstPrevious.RecordCount > 0 Then
Me.Firstname = rstPrevious!Firstname
Me.LastName = rstPrevious!LastName
End If
End Sub
And some good ideas in say having a "list" or "array" of controls/fields to setup, so you don't have to write a lot of code. (as suggested in the other post/answer here)
I have a SQL query pulling back two columns and populating an ActiveX Combobox. The query in SQL returns a single row of data, however when I select the combobox in Excel the list is empty.
This is the part of the code that populates the combobox:
With rst
Set .ActiveConnection = Nothing 'Disconnect the recordset.
k = .Fields.Count
'Populate the array with the whole recordset.
vaData = .GetRows
End With
CB_Layer.List = Application.Transpose(vaData)
To test this I have added a watch to the rst and can see that I have one row of values. As soon as I pass over the vaData = .GetRows the watch value changes to:
: Value : <Either BOF or EOF is True, or the current record has been deleted. Requested operation requires a current record.>
Can anyone advise why this is happening?
Thanks in advance
All tables in a certain database have the exact columns, so I'm wondering if there is a way I can query all of them at once for a specific few columns that I know every table will have. The reason I want to do this is that the number of tables in the database will constantly be growing, and I don't want to have to every day go and change my query to accommodate the names of the new tables.
Help is appreciated as always
In that case, try ADO:
Function ListTablesContainingField(SelectFieldName) As String
'Tables returned will include linked tables
'I have added a little error coding. I don't normally do that
'for examples, so don't read anything into it :)
Dim cn As New ADODB.Connection
Dim rs As ADODB.Recordset
Dim strTempList As String
On Error GoTo Error_Trap
Set cn = CurrentProject.Connection
'Get names of all tables that have a column called <SelectFieldName>
Set rs = cn.OpenSchema(adSchemaColumns, _
Array(Empty, Empty, Empty, SelectFieldName))
'List the tables that have been selected
While Not rs.EOF
'Exclude MS system tables
If Left(rs!Table_Name, 4) <> "MSys" Then
strTempList = strTempList & "," & rs!Table_Name
End If
rs.MoveNext
Wend
ListTablesContainingField = Mid(strTempList, 2)
Exit_Here:
rs.Close
Set cn = Nothing
Exit Function
Error_Trap:
MsgBox Err.Description
Resume Exit_Here
End Function
From: http://wiki.lessthandot.com/index.php/ADO_Schemas
You might like to consider a table of tables, if you have not already got one, that lists the linked Excel tables and holds details of archive dates etc, because you will run into limits at some stage.
This is part of another question which has been resolved, pasting the link here for the convenient of those coming across in the future.
Credit to Erik_von_Asmuth for his help previously.
Using VBA to import a large number of attachment into Microsoft Access
The concept of the code I think that might work:
Sub MacroInsertImageToDatabase()
Dim I As Integer 'number of row in file_paths.txt
Dim J As Integer 'number of entries in the database
For J = 1 To 100
For I = 1 To 100
'Lets say there are 100 lines in file_paths.txt. Something like:
'C:\image_folder/image1.jpg
'C:\image_folder/image2.jpg
'all the way to
'C:\image_folder/image100.jpg
If (string of file_name in column 2 in the database) = (current row in file_paths.txt we are looking at)
Then 'That means there is a match!
[Attach the image from as given from file_paths.txt(we ar looking at) into the 3rd row of the database(we are looking at)]
[also escape this loop through file_paths.txt so we can move onto the next entry in the database to repeat this If statement]
Else 'current row in file_paths.txt we are looking at is NOT what we
[move in the the next "I" iteration to look at the next row in file_paths.txt]
Next I 'successfull attached the image to the correponse entry in the database as per string in the 2nd column (file_name)
Next J 'now move on to the next row (entry) in the database, the "J" loop
End Sub
Or should I exploit the features of MS Access, I am reading the documentation about the "database table relationships". Have 1 table with just the attachments. Have another table with the corresponding file names (and other data). Then use the relationship features of MS Access to link them together.
You can use the following code to add attachments to your table based on the location set in the file_name column
Public Sub MacroInsertImageToDatabase()
Dim db As DAO.Database
Dim rsEmployees As DAO.Recordset, rsPictures As DAO.Recordset, ws As DAO.Workspace
'Initialize database, workspace and recordset
Set db = CurrentDb()
set ws = DBEngine.Workspaces(0)
Set rsEmployees = db.OpenRecordset("Table1", dbOpenDynaset)
Do While Not rsEmployees.EOF
'Open the attachment recordset for the current record
Set rsPictures = rsEmployees.Fields("attachment_column").Value
'If there are no attachments yet
If rsPictures.BOF Then
'Edit the record
rsEmployees.Edit
'Begin a separate transaction for inserting the picture
ws.BeginTrans
'Load the picture
rsPictures.AddNew
rsPictures.Fields("FileData").LoadFromFile rsEmployees.Fields("file_name")
'Update the pictures recordset and commit the transaction to avoid troubles with nested transactions
rsPictures.Update
ws.CommitTrans
rsPictures.Close
'Then update the record (if anything has changed inside it, which it actually hasn't)
rsEmployees.Update
End If
rsEmployees.MoveNext
Loop
rsEmployees.Close
End Sub