Transpose a table in Access using VB code - ms-access

I have a table that is populated with data from a delimited text file. The data comes from another system and I cannot modify how it is generated into the text file I am importing. Once the data is imported into access, it is not in a normalized fashion. The first two columns of data are date ranges, the third is a location code, the remaining 54 columns hold specific data for each location. I need to find the top five values for each record so I can put them into a report.
I had posed this question in another thread, but was unable to find a solution. In that thread, someone recommended that I used a union query. It appeared that it was going to work perfectly, but you can only use 50 unions in access and I have to many fields.
Now I am trying to use VB code in access to transpose the table. I am working with the following code that I retrieved from this page. It is throwing an error on execution. I cannot figure out what the issue is. I know it is a syntax error or creating the object, but I have tried everything I can think of and cannot get it to work. Also, The column headers would contain string info so I was going to change the variable to a variant instead of an integer? Any help with this code, or suggestions regarding how to get what I want from the table would be appreciated.
Picture of actual table.
I am getting a error -> 'Run-time error '3265': Item not found in this collection.
Private Sub Command78_Click()
Const cstrInputTable = "Base Period OT"
Const cstrOutputTable As String = "Normalized Base Period OT"
Dim dbs As DAO.Database
Dim rstInput As DAO.Recordset
Dim rstOutput As DAO.Recordset
Dim intYear As Integer
Set dbs = CurrentDb
Set rstInput = dbs.OpenRecordset(cstrInputTable)
Set rstOutput = dbs.OpenRecordset(cstrOutputTable)
If Not rstInput.EOF Then
' For each column in the Input table, create a record in the output table
For intYear = 1990 To 2011
rstInput.MoveFirst
rstOutput.AddNew
rstOutput![Year] = intYear
' Go through every record in the Input table
Do
rstOutput(rstInput![Data Type]) = rstInput(CStr(intYear))
rstInput.MoveNext
Loop Until rstInput.EOF
rstOutput.Update
Next intYear
End If
rstInput.Close
rstOutput.Close
dbs.Close
MsgBox "Data Successfully Transformed"
DoCmd.OpenTable cstrOutputTable
End Sub

Still not sure I have fully understood your inputs and outputs. I'll give it a try though and you let me know if I'm even close to what you're looking for.
You can create a "Temp" table with only 3 fields just for sorting purposes. You can then loop through your source table and add Location, Column header (3 letter code) and the value of each field to the "Temp" table.
You can then sort by value DESC and select the top 5.
Public Sub GetTopFive()
On Error GoTo ErrProc
Dim rs As DAO.Recordset
Set rs = CurrentDb.OpenRecordset("SELECT Location, AMR, AXT, BRM, BMM, CSR, CTC " & _
"FROM DataSource ORDER BY Location;", dbOpenSnapshot)
If rs.EOF Then GoTo Leave
With rs
.MoveLast
.MoveFirst
End With
Dim idx As Long
For idx = 1 To rs.RecordCount
AddToTempTable rs
'Now the Temp table holds one Location, sorted by value
'Selecting the top 5 records will give you what you're looking for
'If that's the case, provide additional info on how to handle this
'as each location might have different field names.
rs.MoveNext
Next idx
Leave:
On Error Resume Next
rs.Close
Set rs = Nothing
On Error GoTo 0
Exit Sub
ErrProc:
MsgBox Err.Description, vbCritical
Resume Leave
End Sub
'Add To Temp for sorting
Private Sub AddToTempTable(rs As DAO.Recordset)
Dim fld As DAO.Field
For Each fld In rs.Fields
If fld.Name <> "Location" Then
With CurrentDb.QueryDefs("qryAddToTemp")
.Parameters("[prmLocation]").Value = rs!Location
.Parameters("[prmFileldName]").Value = fld.Name
.Parameters("[prmFieldValue]").Value = fld.Value
.Execute dbFailOnError
End With
End If
Next fld
End Sub
Import query
PARAMETERS [prmLocation] Text ( 255 ), [prmFileldName] Text ( 255 ), [prmFieldValue] IEEESingle;
INSERT INTO tbTemp ( Location, [Field Name], [Field Value] )
SELECT [prmLocation] AS Location, [prmFileldName] AS [Field Name], [prmFieldValue] AS [Field Value];
Temp Table
Update:
Public Sub GetTopFive()
On Error GoTo ErrProc
Dim rs As DAO.Recordset
Set rs = CurrentDb.OpenRecordset("SELECT Location, AMR, AXT, BRM, BMM, CSR, CTC " & _
"FROM DataSource ORDER BY Location;", dbOpenSnapshot)
If rs.EOF Then GoTo Leave
With rs
.MoveLast
.MoveFirst
End With
Dim rsTemp As DAO.Recordset, fld As DAO.Field, idx As Long
Set rsTemp = CurrentDb.OpenRecordset("tbTemp")
With rsTemp
For idx = 1 To rs.RecordCount
For Each fld In rs.Fields
If fld.Name <> "Location" Then
.AddNew
.Fields("YourCodeColumnName").Value = fld.Name
.Fields(rs!Location).Value = fld.Value
.Update
End If
Next fld
rs.MoveNext
Next idx
End With
Leave:
On Error Resume Next
rsTemp.Close
Set rsTemp = Nothing
rs.Close
Set rs = Nothing
On Error GoTo 0
Exit Sub
ErrProc:
MsgBox Err.Description, vbCritical
Resume Leave
End Sub

Based on what you have provided there are 6 possibilities of where you are getting error 3265, and 4 of them have the same solution, once you understand how DAO Recordset objects work and reference fields in the "Table" they represent.
The error Item not found in this collection, given the code you have presented indicates that you are referencing a field name (column name) in the recordset that does not exist. Or, that you are referencing a table name that does not exist in the database.
Since your code is dynamically determining field names, and you haven't provided the structure of the tables Base Period OT or Normalized Base Period OT, you will have to figure part of this out on your own.
Here are the 4 places where the error could be occurring for the Recordset objects and what you are looking for:
rstOutput![Year] = intYear, you are telling Access that you expect a column named "Year" to exist in your table Normalized Base Period OT and that you want to insert the current value of intYear into that column. If "Year" is not a column name in that table, this would be the problem.
3, & 4. rstOutput(rstInput![Data Type]) = rstInput(CStr(intYear)) In this single line of code, you have 3 possible locations for the error.
a. rstInput![Data Type] Does the table Base Period OT contain a column named "Data Type"? If not, this would be an error. Here you are statically providing the name of the column that you expect to exist in the input table.
b. rstOutput(rstInput![Data Type]) Assuming that rstInput![Data Type] is a valid column, the value in that column is now the name of the column you are expecting to exist in Normalized Base Period OT. If that is not true, this would be an error. Here, you are dynamically providing the name of the column that you expect to exist in the output table.
c. rstInput(CStr(intYear)) Does the table Base Period OT contain a column for the current value of intYear (i.e. does that table contain columns named 1990, 1991, 1992, etc through 2011 as defined in your loop?) If not, this would be an error. Here, again, you are dynamically providing the name of the column that you expect to exist in the input table.
5 & 6. You could also receive this error on your OpenRecordset commands if the tables, named in your two constants don't exist.
This addresses the issue with your code sample, but does not address whether your approach to transform the data for your other stated purposes is correct or not as we do not have enough additional information.

Related

Loop through records and increment value in vba access

I'm trying to loop through records in a table with part no's and with matching part no's, increment a value in the pos field. I asked a question earlier(Object variable or With block variable not set Access vba) with the same project but for a different step of the process (running a query). I haven't found a question that addresses this problem in Access yet, but I have taken some pointers from this question: Code to loop through all records in MS Access . Now, I'm trying to write the code to loop through the records and increment the value in the pos field.
The query that will run:
SELECT CTOL.ID, CTOL.BOM_PART_NAME, CTOL.CII, CTOL.[PART FIND NO], CTOL.CSN, CTOL.AFS, CTOL.EQP_POS_CD, CTOL.LCN, CTOL.POS_CT, CTOL.SERIAL_NO, CTOL.PART_NO_LLP, [CTOL_Asbuilt].[PART-SN], [CTOL_Asbuilt].[PART-ATA-NO], [CTOL_Asbuilt].[PW-PART-NO]
FROM CTOL LEFT JOIN [CTOL_Asbuilt] ON CTOL.[PART FIND NO] = [CTOL_Asbuilt].[PART-ATA-NO];
Code so far (credit to Kostas K for helping me on the other question):
Option Compare Database
Option Explicit
'Const adOpenStatic = 3
'Const adLockOptimistic = 3
Function queryDatabase()
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim qdf As QueryDef
'Dim rsQuery As DAO.Recordset
Dim rows As Variant
Dim part_find_no() As String
Dim eqp_pos() As Integer
'Dim strSQL As String
Dim i As Integer
Dim j As Integer
'Set objConnection = CurrentDb.OpenRecordset("CTOL")
Set db = CurrentDb
Set qdf = db.QueryDefs("SicrProcess")
Set rs = qdf.OpenRecordset(dbOpenDynaset)
If rs.EOF Then GoTo Leave
rs.MoveLast
rs.MoveFirst
For i = 1 To rs.RecordCount
Debug.Print rs.Fields("PART FIND NO") & " " & rs.Fields("EQP_POS_CD")
rs.MoveNext
Next i
Leave:
On Error Resume Next
rs.Close
Set rs = Nothing
qdf.Close
Set qdf = Nothing
Set db = Nothing
On Error GoTo 0
Exit Function
ErrProc:
MsgBox Err.Description, vbCritical
Resume Leave
End Function
The two fields I want to loop through are PART FIND NO in the CTOL table and EQP_POS_CD in the CTOL table. The value in EQP_POS_CD should be incremented by 1 if the last PART FIND NO is identical to the current no. The result set should contain the other fields selected in the query as well. Is there anything else I need to add to get it output? Right now, I'm not too concerned as to how it's output, whether in a window or in datasheet format (though I would appreciate pointers on this if anyone knows). Any pointers on how to accomplish this? Also, if there's anything I'm missing in my question that's important, or if there's something I could do better when posting a question, I'm open to discussing it. I'm also open to discussion of how to approach the problem. Thanks!
Possibly:
SELECT CTOL.*, [CTOL_Asbuilt].[PART-SN], [CTOL_Asbuilt].[PART-ATA-NO], [CTOL_Asbuilt].[PW-PART-NO],
DCount("*", "CTOL", "[Part Find No]=" & [Part Find No] & " AND ID<" & [ID])+1 AS Seq
FROM CTOL LEFT JOIN [CTOL_Asbuilt] ON CTOL.[PART FIND NO] = [CTOL_Asbuilt].[PART-ATA-NO];

Why isn't MS Access "item found in collection?"

I have a non-linked table "tblGrantRptData" in which I am trying to modify field records for subsequent filtering and export to EXCEL. I have ensured that all field names are correctly spelled, yet I still get an Error 3265, Item not found in this collection.
I have confirmed that MemmonthlyIncome is the correct spelling and is identified in the design as "currency."
here is the design veiw that appears to show the field name:
It stops at this line:
If IsNull (!MemmonthlyIncome) Then
with the error 3265
If I can get some help to resolve this, I would then like to store the range "0-30"....
One suggestion below was to “decompile" and "recompile.” I have read that this may cause problems when the database is used on multiple workstations. I have revised the code to just get to first base….Any suggestions?
Private Sub cmdGenerateGrantRpt_Click()
'now run the qqAll query - this generates the tblGrantRptData - then close the query
DoCmd.SetWarnings False
DoCmd.OpenQuery "qqAll", acViewNormal, acEdit
DoCmd.Close acQuery, "qqAll"
DoCmd.SetWarnings True
'First set up the table: tblGrantRptData with the correct data in fields
Dim db As DAO.Database
Dim rs As DAO.Recordset
Set db = CurrentDb
Set rs = db.OpenRecordset("tblGrantRptData", dbOpenTable)
'Check to see if the recordset actually contains rows and fill in particular values
With rs
If .EOF And .BOF Then
MsgBox "There are no records in this time interval"
Exit Sub
Else
.MoveFirst
Do Until .EOF = True
'Replace the monthly income with income categories
If IsNull(!MemmonthlyIncome) Then
.Edit
!MemmonthlyIncome = "0-30"
.Update
End If
Loop
End If
End With
rs.Close 'Close the recordset
Set rs = Nothing 'Clean up
End Sub
You get error #3265, "Item not found in this collection", at IsNull(!MemmonthlyIncome) in this context ...
Set rs = db.OpenRecordset("tblGrantRptData", dbOpenTable)
With rs
If IsNull(!MemmonthlyIncome) Then
That would happen if tblGrantRptData does not include a field named MemmonthlyIncome. What you are seeing as the column heading in the table Datasheet View may be the field's Caption property. You could check the field's Name and Caption properties in the table's Design View. Or you could list the actual field names for your table in the Immediate window.
Here's an abbreviated list of the field names in my Contacts table:
set db = currentdb
for each fld in db.TableDefs("Contacts").Fields : ? fld.name : next
ID
Company
Last Name
First Name
You just added this screen capture of your table in Design View ...
Notice the field is named MemmothlyIncome, not MemmonthlyIncome (moth vs. month). So it wasn't a Name vs. Caption difference after all; you simply tried to use a misspelled field name. That spelling problem is also visible in the DataSheet View screen capture, but we didn't notice it there.
In VBA, there is no Is Null expression as there is in SQL. Change to IsNull(rs!MemmonthlyIncome).
Possibly, Null is being referenced in recordset collection and hence returning the error.

Advanced Filter Criteria in Access

The issue is simple but I just cant figure it out.
I have two tables in access, one with records and another with "key words". I need to filter the records containing certain "key words". In other words, use one table field as a filter criteria for the other, but without linking them because the "key words" table just contains random words instead of a whole record.
In excel I can run an advanced filter on my records and just specify as criteria the list of key words (and using wildcards), but in acces I havent found a way to filter according to another table fields.
Any ideas about it?
You may need to create a function that spits out custom SQL with all the keywords in it. Here is an example to get you started.
Public Function fGetTrashRecords()
'add your own error handling
Dim SQL As String
Dim rst As DAO.Recordset
Dim rstTrash As DAO.Recordset
Dim db As DAO.Database
Set db = CurrentDb
Set rst = db.OpenRecordset("SELECT sKeyWord FROM tblBadKeyWords", dbOpenSnapshot)
If Not rst Is Nothing Then
rst.MoveFirst
Do While Not rst.EOF
SQL = SQL & " strFieldContaingKeyWord LIKE *'" & rst!sKeyWord & "'* OR"
rst.MoveNext
Loop
If SQL > "" Then SQL = Left(SQL, Len(SQL) - 2) 'get rid of the last OR
rst.Close
Set rst = Nothing
End If
If SQL > "" Then
Set rstTrash = db.OpenRecordset("SELECT * FROM tblHasKeyWords WHERE " & SQL, dbOpenDynaset, dbSeeChanges)
If Not rstTrash Is Nothing Then
rstTrash.MoveFirst
Do While Not rstTrash.EOF
Debug.Print rstTrash!ID
rstTrash.MoveNext
Loop
rstTrash.Close
Set rstTrash = Nothing
End If
End If
Set db = Nothing
End Function

How to test if item exists in recordset?

I have a crosstab query that is being loaded into a recordset. I'm then writing the query fields to an Excel spreadsheet. The problem is that a field may not exist based on the query results.
For example, I have the following line:
oSheet5.Range("F1").Value = rsB2("AK")
...which would write the value of the recordset item named "AK" to the spreadsheet. But if "AK" doesn't exist, I get an error Item not found in this collection.
How I can I test to see if there's an item named "AK"?
I tried...
If rsB2("AK") Then
oSheet5.Range("F" & Count).Value = rsB2("AK")
End If
...but that didn't work.
I also tried...
If rsB2("AK") Is Nothing Then
oSheet5.Range("F" & Count).Value = ""
Else
oSheet5.Range("F" & Count).Value = rsB2("AK")
End If
...and still the same error.
There are 50+ items/fields to check .. all states in USA plus a few extras.
Thanks!
You can use Recordset.FindFirst Method (DAO) take a look here or here
Small example:
Sub FindOrgName()
Dim dbs As DAO.Database
Dim rst As DAO.Recordset
'Get the database and Recordset
Set dbs = CurrentDb
Set rst = dbs.OpenRecordset("tblCustomers")
'Search for the first matching record
rst.FindFirst "[OrgName] LIKE '*parts*'"
'Check the result
If rst.NoMatch Then
MsgBox "Record not found."
GotTo Cleanup
Else
Do While Not rst.NoMatch
MsgBox "Customer name: " & rst!CustName
rst.FindNext "[OrgName] LIKE '*parts*'"
Loop
'Search for the next matching record
rst.FindNext "[OrgName] LIKE '*parts*'"
End If
Cleanup:
rst.Close
Set rst = Nothing
Set dbs = Nothing
End Sub
You could add an error handler to catch the item not found error ... ignore it and/or do something else instead.
Or if the first recordset field always maps to the first sheet column regardless of the field's name, you can reference it by its ordinal position: rsB2(0)
Or you could examine the recordset's Fields collection to confirm the field name is present before attempting to retrieve its value.
After you open the recordset, load a dictionary with its field names. This code sample uses late binding. I included comment hints in case you want early binding. Early binding requires you to set a reference for Microsoft Scripting Runtime.
Dim objDict As Object 'Scripting.Dictionary
'Set objDict = New Scripting.Dictionary
Set objDict = CreateObject("Scripting.Dictionary")
Dim fld As DAO.Field
For Each fld In rsB2.Fields
objDict.Add fld.Name, vbNullString
Next
Then later you can use the dictionary's Exists method to your advantage.
If objdict.Exists("AK") = True Then
oSheet5.Range("F1").Value = rsB2("AK")
End If

Are there issues with tables using an autonumber as a primary key in a back-end ms access db?

I inherited an MS Access database at my office that is heavily used by several people over the network. This causes many issues with data collisions and locks. I want to split the db so that each user has thier own front-end app and maintain the core data on the server.
Several of the tables use an autonumber:sequence:long as thier primary key - in researching how to perform the split I've come across several posts that hint this can cause issues when distributing a database but I haven't been able to find anything solid. The issue seems to be that a user can begin a new record and receive the next autonumber but a second user can create a new record within a short interval and receive the same autonumber resulting in an error?
Does Jet handle this correctly or are there autonumber issues with a FE/BE database? If it's an unlikely-but-possile occurance I'm sure it will still be much better than what my users are currently experiencing but I'd like to know if there are ways I can minimize such issues.
Thanks for your help!
I've had the misfortune of working with many Access databases in my youth. While there are many issues with Access, I do not know if I've ever run into a problem with AutoNumber columns in a split database, multi-user environment. It should work fine. This is such a common setup that there would be posts all over the Internet about it if were an issue.
As long as you are not going for data replication (ie multiple subscriber databases, where users can insert new records in same tables but in different locations), you will not have problems with autonumbers as primary keys.
If you think that one of these days you might need to go for replication (different locations, one central database), do not hesitate to switch to unique identifiers (replication IDs).
There seems to be some confusion on your part about the process of splitting. When you do so, you end up with multiple front ends, but the back end is still a single file. Thus, there's no difference at all for the data tables in terms of Autonumbers from what you had before you split the application.
I had the same problem, nevertheless i did a workarround to get the autonumbering work from an Onload() Event
What I did is :
I create a recordset based on Your_Table everytime the user needs an autonumber
Open the recordset (rst)
Search if:
-Your_Table is Empty, then assigns the value "1" to Your_field
-Your_Table is has data without missing numbers,then assigns the value = "Count of lines + 1" to Your_field (1,2,....,n+1)
-Your_Table has missing data (1,3,4,5,7) [Note "#2 and #7 are missing]", then uses a function to search in Your_Table the missing fields and assign to Your_Field the first missing value (#2 in this example)
Private Sub Autonumbering(Your_Table As String)
Dim rst As DAO.Recordset
Dim db As Database
On Error GoTo ErrorHandler
Application.Echo False
Set db = CurrentDb
Set rst = db.OpenRecordset(Your_Table, dbOpenDynaset)
With rst
.AddNew
'Your_Table is Empty, **then** assigns the value "1" to Your_field
If DMin("[Your_Field]", Your_Table) = 1 Then
'Your_Table is has data without missing numbers,**then** assigns the value = "Count of lines + 1" to Your_field (1,2,....,n+1)
If DMax("[Your_Field]", Your_Table) = .RecordCount Then
'Assings n+1 value to [Your_Field] records
Value = .RecordCount + 1
![Your_Field] = Valor
Else
'Your_Table has missing data (1,3,4,5,7) [Note "#2 and #7 are missing]", **then** uses a function to search in Your_Table & _
the missing fields and assign to Your_Field the first missing value (#2 in this example)
Value = MyFunction$(Your_Table, "Your_Field")
![Your_Field] = Value
End If
Else
'Agrega el número 1
Value = 1
![Your_Field] = Value
End If
.Update
.Bookmark = .LastModified
Me.Requery
DoCmd.GoToRecord acDataForm, Me.Name, acGoTo, Value
.Move 0, .LastModified
End With
ErrorCorregido:
Application.Echo True
Exit Sub
ErrorHandler:
MsgBox "An error ocurred, please verify numbering", vbCritical + vbOKOnly
Resume ErrorCorregido
End Sub
Here is the function that i found to get the missing values on an specific table, i cant find it anymore, but thanks for the one who made it.
Function MyFunction$(cstrTable As String, cstrField As String)
' Read table/query sequentially to record all missing IDs.
' Fill a ListBox to display to found IDs.
' A reference to Microsoft DAO must be present.
Dim dbs As DAO.Database
Dim rst As DAO.Recordset
Dim lst As ListBox
Dim Col As Collection
Dim strSQL As String
Dim strList As String
Dim lngLast As Long
Dim lngNext As Long
Dim lngMiss As Long
' Build SQL string which sorts the ID field.
strSQL = "Select " & cstrField & "" _
& " From " & cstrTable & " Order By 1;"
Set Col = Nothing
' Control to fill with missing numbers.
'Set lst = Me!lstMissing
' Collection to hold the missing IDs.
Set Col = New Collection
'// Vacía la colección
'Erase Col
' Read the table.
Set dbs = CurrentDb
Set rst = dbs.OpenRecordset(strSQL)
If rst.RecordCount = 0 Then
' The recordset is empty.
' Nothing to do.
Else
' Read and save the ID of the first record.
lngLast = rst(cstrField).value
rst.MoveNext
' Loop from the second record through the recordset
' while reading each ID.
While rst.EOF = False
lngNext = rst(cstrField).value
' For each ID, fill the collection with the
' missing IDs between the last ID and this ID.
For lngMiss = lngLast + 1 To lngNext - 1
Col.Add (lngMiss)
Next
' Save the last read ID and move on.
lngLast = lngNext
rst.MoveNext
Wend
' Finally, add the next possible ID to use.
Col.Add (lngLast + 1)
End If
rst.Close
For lngMiss = 1 To Col.Count
' Build the value list for the ListBox.
If Len(strList) > 0 Then
' Append separator.
strList = strList & ";"
End If
' Append next item from the collection.
strList = strList & Col(lngMiss)
' For debugging only. May be removed.
Debug.Print Col(lngMiss)
Next
' Pass the value list to the ListBox.
' Doing so will requery it too.
' lst.RowSource = strList
' For debugging only. May be removed.
' Debug.Print strList
MyFunction$ = Col(1)
' Clean up.
Set rst = Nothing
Set dbs = Nothing
Set Col = Nothing
Set lst = Nothing
End Function