Most efficient way to query 1 of 4 large Access tables based on condition (using VBA module) - sql

For the past couple weeks I've been working on a very unconventional solution to a problem for my job. I'm almost there, but I need to know the most efficient way to do the last step. I will dumb it down so I don't have to write an essay describing the insane nature of the problem I've been working on.
I have four large local tables in MS Access with a total of over 500,000 records.
Each table represents a different type of product.
The productID for table1 always starts with "9"
The productID for table2 always starts with "8"
The productID for table3 always starts with "4"
The productID for table4 always starts with "3"
I have a vba procedure written that does exactly what I need it to do except I have it querying information using only table1 thus far. Basically, a user inputs a productID and the procedure searches the table for that record and sends the information to a sharepoint list. Speed of execution is HIGHLY important in my situation. So, what is the fastest way to have it run? Should I write a statement that says "If the ID starts with 9 then search this table, ElseIF ..... and so on." Or, should I combine all the tables into one and not have it look at the first digit of the ID?
I know this sounds like a simple issue but trust me, this is a wild over simplification of the real issue and it would take 2,000 words to explain how ridiculous it actually is; I'm not kidding. However, I am fairly confident that the answer to the above question will give me all the information I need to finish this project successfully. I have come so far and all I have left is to figure out the most efficient way to apply it to the 3 other tables.
Thanks!

As the tables are local, use the Seek method which is extremely fast:
Recordset.Seek method (DAO)
If the tables were linked, you could still using Seek by opening the backend database. This is an example:
Function SeekTable()
Const cstrTable As String = "tblValue01"
Const cstrAttached As String = ";DATABASE="
Dim wks As Workspace
Dim dbs As Database
Dim tdf As TableDef
Dim rst As Recordset
Dim strConnect As String
Dim strTablename As String
Set wks = DBEngine(0)
Set dbs = wks(0)
Set tdf = dbs.TableDefs(cstrTable)
strConnect = tdf.Connect
strTablename = tdf.SourceTableName
Set tdf = Nothing
If InStr(1, strConnect, cstrAttached, vbBinaryCompare) = 1 Then
strConnect = Mid(strConnect, Len(cstrAttached) + 1)
' Open database shared and read-only.
Set dbs = wks.OpenDatabase(strConnect, False, True)
Set rst = dbs.OpenRecordset(strTablename)
'
' Perform Seek operation. Example.
rst.Index = "ID"
rst.Seek "=", 10010
Debug.Print rst!Value
'
rst.Close
Set rst = Nothing
End If
dbs.Close
Set dbs = Nothing
Set wks = Nothing
End Function

Related

How do I update a single field of a single record with data from another table with VBA in MS Access

In MS Access, I have a table called "Products". It contains quality testing parameters, as well as fields for "Part_No" and "Last_Test_Date". The "Last_Test_Date" field being the last time that the specific product was tested.
I have another table (Trend001) that is live updated with the actual test data from a hydraulic test machine, as well as the "Part_No" being tested and a current "Time_Stamp".
I would like to update the "Last_Test_Date" of the specific "Part_No" being tested with the "Time_Stamp" from the "Trend001" table.
That way the user knows the last time that the Part was tested.
I found this on Stackoverflow, but I don't know enough about the language to apply it to my project.
Updating existing records and adding new records in table (MS Access VBA)
'''
Public Sub UpdateExistingRecords()
On Error GoTo ErrTrap
Dim rs As DAO.Recordset
Set rs = CurrentDb().OpenRecordset("SELECT * FROM tblTempData", dbOpenSnapshot)
Set rsCommon = CurrentDb().OpenRecordset("SELECT * FROM tblCommon", dbOpenDynaset)
Dim idx As Long
For idx = 1 To rs.RecordCount
If ExistsInCommon(rs![Item ID]) Then
If Not Update(rs) Then
MsgBox "Failed to update.", vbExclamation
GoTo Leave
End If
'''
You don't need VBA to do the update, just use a SQL update. This SQL will update all the parts in the Products table that exist in the Trend001 table.
UPDATE Products INNER JOIN Trend001 ON Products.Part_No = Trend001.Part_No
SET Products.Last_Test_Date = Trend001.Time_Stamp;
PLEASE - Remember to back up your database before running this update. A small typo could delete your data.

Setting listboxes in MS access form - row by row

I have a requirement in MS Access where a table is displayed as several rows in the form. I have created one form detail record(several fields) that will repeat for each row in the Table1. Lets say I have five columns in the Table1. Based on Column3 value, I would like to have a list of value for Column4 and Column5 during form_load. I have also created a separate Table2 to establish relationship between Column3, Column4 and Column5. I have set up Event procedure to populate the values using sub function. The challenge I have is, not being able to set up different listbox 'value list' for different rows. Any tips on populationg form fields IMRecomExIns and AmendReasonExIns by processing each row in Table1 would be a great help.
Private Sub IMRecomExIns_Click()
Dim CoverType As String
Dim ListRecomm As String
Dim ListAmend As String
Dim db As DAO.Database
Dim tablevar As Recordset
Set db = CurrentDb
Set tablevar = db.OpenRecordset("Table2")
CoverType = "*" & Me.CoverTypeExIns.Value & "*"
ListRecomm = ""
ListAmend = ""
If tablevar.EOF = False And tablevar.BOF = False Then
tablevar.MoveFirst
Do Until tablevar.EOF
If tablevar!CoverType Like CoverType Then
ListRecomm = tablevar!Recommendation
ListAmend = tablevar!AmendReason
tablevar.MoveLast
End If
tablevar.MoveNext
Loop
End If
Me.IMRecomExIns.RowSourceType = "Value list"
Me.IMRecomExIns.RowSource = ListRecomm
Me.AmendReasonExIns.RowSourceType = "Value list"
Me.AmendReasonExIns.RowSource = ListAmend
End Sub
1) I have stored all the value list in a single cell. For example tablevar!Recommendation will have all the values for Me.IMRecomExIns.RowSource, which means the output is will look like "Rec1";"Rec2";"Rec3";etc... Same applies for tablevar!AmendReason "AR1";"AR2';"AR3";ETC... Understand this is not the normalized form of storing data. I want to POC to work before building a full solution with normalized tables.
2) Answered earlier.. the rowsource will be set with all the possible values at the first match, so no point in going all the way to the end of the table
3) CoverTypeExIns is a string, Table 2 have many different possibilities such as "Mortgage Income" and "Family Income", however the Recommendation and Amendreason are same for all "Income" category that comes from Table1. Thats why the wildcard search.
My problem is not with setting the RowSource for a single row, but setting up RowSource for multiple occurrence in of the same IMRecommmendation and AmendReason in MS Access screen.
Here is the design view of the form. This form is linked to MS Access table. For multiple rows the Detail record will repeat itself as many times.
An example of two rows displayed in the screen.
I'm not sure exactly what you are asking/trying to do here.
I can see at several problems with the code that you have:
You are using tablevar.MoveLast in the loop, whic would automatically take you to the end of the recordset.
Also, you are not concatenating (joining together) ListRecomm/ListAmend, you are just setting them equal to a value, so each loop that matches will overwrite any previous value.
Finally, I am not sure what you are doing with trying to find CoverTypeExIns - you are using LIKE, which would indicate that it is text, but not wrapping the value in single quotes. If it is a numeric value, then you should be using "=".
However, rather than opening a recordset, looping it and checking for a match to build up a list of values, it is better to just set the RowSource of listboxes equal to a SQL string (effectively a query).
Something like (assuming CoverType is numeric):
Private Sub IMRecomExIns_Click()
Dim strSQL As String
strSQL = "SELECT Recommendation FROM Table2 WHERE CoverType=" & Me!CoverTypeExIns
Me!AmendReasonExIns.RowSource = strSQL
End Sub
I prefer to declare a string to hold the SQL statement rather than setting the .RowSource directly, as it makes troubleshooting easier.
Regards,
Based on the new information given, below is some VBA code that opens up a recordset based on the information entered in "Cover", and then sets the .RowSource property of the two combo boxes to be the value lists. In my example, don't bother setting the .RowSourceType, as this should be done at design time:
Private Sub IMRecomExIns_Click()
Dim db As DAO.Database
Dim rsData As DAO.Recordset
Dim strSQL As String
Set db = DBEngine(0)(0)
strSQL = "SELECT Recommendation, AmendReason FROM Table2 WHERE CoverType LIKE '*" & Me!cboCover & "*';"
Set rsData = db.OpenRecordset(strSQL)
If Not (rsData.BOF And rsData.EOF) Then
Me!IMRecomExIns.RowSource = rsData!Recommendation
Me!AmendReasonExIns.RowSource = rsData!AmendReason
End If
rsData.Close
Set rsData = Nothing
Set db = Nothing
End Sub
As I have previously stated, you should really normalize the design of your database now, rather than getting in so far that it requires a major re-write.
Regards,

Finding leaf nodes with a specific ancestor [duplicate]

I can't get a running sum to work in an Access query. I have a pipesystem, where I'm trying to summarize the flow Q, through the pipenetwork. I've been trying to do a running sum based on a group_by ID_downstream and a DSum on Q_total. However I keep getting errors or wrong input.
The desired output, is that I can see the flow accumulated through the network as shown in the table and picture.
You have several options. One, however, won't do, and that is a recursive query using SQL only; Access can't be fooled and will claim about a circular reference. Your only chance is to create a query resolving a limited number of levels only, say, 8 or 10.
But you can cover the recursive call in a domain aggregate function like DLookup. This is, however, very slow as DLookup calling the query will run for each and every record. For more than some dozens of records this will most likely be unacceptable.
The fastest way, for an unlimited number of levels, I've found, is to create a lookup function which walks the tree for each record. This can output either the level of the record or a compound key build by the key of the record and all keys above.
As the lookup function will use the same recordset for every call, you can make it static, and (for JET/ACE) you can improve further by using Seek to locate the records.
Here's an example which will give you an idea:
Function RecursiveLookup(ByVal lngID As Long) As String
Static dbs As Database
Static tbl As TableDef
Static rst As Recordset
Dim lngLevel As Long
Dim strAccount As String
If dbs Is Nothing Then
' For testing only.
' Replace with OpenDatabase of backend database file.
Set dbs = CurrentDb()
Set tbl = dbs.TableDefs("tblAccount")
Set rst = dbs.OpenRecordset(tbl.Name, dbOpenTable)
End If
With rst
.Index = "PrimaryKey"
While lngID > 0
.Seek "=", lngID
If Not .NoMatch Then
lngLevel = lngLevel + 1
lngID = !MasterAccountFK.Value
If lngID > 0 Then
strAccount = str(!AccountID) & strAccount
End If
Else
lngID = 0
End If
Wend
' Leave recordset open.
' .Close
End With
' Don't terminate static objects.
' Set rst = Nothing
' Set tbl = Nothing
' Set dbs = Nothing
' Alternative expression for returning the level.
' (Adjust vartype of return value of function.) ' RecursiveLookup = lngLevel ' As Long
RecursiveLookup = strAccount
End Function
This assumes a table with a primary key ID and a foreign (master) key pointing to the parent record - and a top level record (not used) with a visible key (AccountID) of 0.
Now your tree will be nicely shown almost instantaneously using a query like this, where Account will be the visible compound key:
SELECT
*, RecursiveLookup([ID]) AS Account
FROM
tblAccount
WHERE
AccountID > 0
ORDER BY
RecursiveLookup([ID]);

Get data from Access given only the primary key of the Table

So I have a question regarding data referencing in Access using VBA and SQL. I have a table in my dataset with like 50 columns, and I need to make a query that a user can run that will manipulate the data in like 30 of these columns with a not-so straightforward algorithm. So the query will prompt the user for the primary key and then run a vba function for getting the value that the user wants, I was just wondering if it was possible that It could be done like this
SELECT Hardware_Type,
Func32(Hardware_Type)
FROM Table1
WHERE (([Hardware_Type]) = [Hardware_Type]);
where Hardware_type is the primary key and Func32 is a Visual Basic Function.
So now Func32 only takes Hardware_type as an Input but needs to use 30 pieces of Data that are in the Row of that specific Hardware_Type. I just need to know that does there exist a way to do this and if there does, I would request a hint, because I really don't want to type in 30 different fields in the query and the function. Oh, and all of this is in Microsoft Access!
Thanks in Advance!
You can avoid passing a whole bunch of parameters to your VBA function but it will cost you one database hit for each time the function is called (i.e., once per row in the query that calls the function). Your function could do something like:
Option Compare Database
Option Explicit
Public Function Func32(HardwareType As String) As Variant
Dim cdb As DAO.Database, qdf As DAO.QueryDef, rst As DAO.Recordset
Dim sql As String
Set cdb = CurrentDb
sql = ""
sql = sql & "PARAMETERS prmHardwareType TEXT(255);"
sql = sql & "SELECT * FROM Table1 WHERE Hardware_Type=[prmHardwareType]"
Set qdf = cdb.CreateQueryDef("", sql)
' assign the PK argument as the query parameter and open it as a Recordset
qdf!prmHardwareType = HardwareType
Set rst = qdf.OpenRecordset(dbOpenSnapshot)
' do your calculations and assign the return value to the function name
Func32 = rst!Field1 + rst!Field2 + rst!Field3
rst.Close
Set rst = Nothing
Set qdf = Nothing
Set cdb = Nothing
End Function

Find Previous and next value in access using SQL

I am using a Microsoft Access 2010 database to import values from one table and append them to a summary table.
One of the issues I am having is finding the previous and next value from the select statement.
This would look as follows.
JOINT JOINT AHEAD JOINT BEHIND
100103 200203
200203 300303 100103
300303 200203
I would like to create this using a SQL code
Be cautious when considering correlated subqueries. They can be very slow. And if you build a query which includes two correlated subqueries, you will magnify the problem.
If your source table contains a smallish number of rows (say a few dozen), the slowness may not be an issue. However, if the table includes a thousand rows you will most certainly notice it. And if your JOINT field is not indexed, the performance could be painfully slow.
If you will be running your query from within an Access session, you can use domain functions (DMin and DMax) instead of correlated subqueries. Domain functions are often criticized as slow. However, in this situation they can be dramatically faster than correlated subqueries.
Correction: You don't need to run your query from within an Access session for it to be able to use the DMin() and DMax() functions. I attached a VBScript example which opens an ADO Recordset based on my qryDomainFunctions. It works without error and correctly reports RecordCount: 1000
I created a table, joints, with a long integer field joint as primary key and added 1000 rows. Then I created these 2 queries:
qryCorrelatedSubqueries:
SELECT
a.joint,
(SELECT TOP 1 joint
FROM joints b
WHERE b.joint>a.joint
ORDER BY joint) AS Ahead,
(SELECT TOP 1 joint
FROM joints b
WHERE b.joint<a.joint
ORDER BY joint DESC) AS Behind
FROM joints AS a;
qryDomainFunctions:
SELECT
j.joint,
DMin("joint","joints","joint > " & [joint]) AS joint_ahead,
DMax("joint","joints","joint < " & [joint]) AS joint_behind
FROM joints AS j;
Here is a transcript from the Immediate window where I compared the speed of those 2 queries, using the QueryDuration function below. That function returns duration in milliseconds.
? QueryDuration("qryDomainFunctions")
0
? QueryDuration("qryCorrelatedSubqueries")
889
Note that both those queries benefit from the index on the joints field. When I dropped the index, compacted the db, and re-ran the tests I got these results:
? QueryDuration("qryDomainFunctions")
16
? QueryDuration("qryCorrelatedSubqueries")
4570
This is the module with the code I used. QueryDuration is by no means the last word on performance measurement. However it's good enough to give us a rough idea of the relative speeds of those 2 queries.
Option Compare Database
Option Explicit
Private Declare Function apiGetTickCount Lib "kernel32" _
Alias "GetTickCount" () As Long
Public Function QueryDuration(ByVal pQueryName As String) As Long
Dim db As DAO.Database
Dim lngStart As Long
Dim lngDone As Long
Dim rs As DAO.Recordset
Set db = CurrentDb()
lngStart = apiGetTickCount() ' milliseconds '
Set rs = db.OpenRecordset(pQueryName, dbOpenSnapshot)
If Not rs.EOF Then
rs.MoveLast
End If
lngDone = apiGetTickCount()
rs.Close
Set rs = Nothing
Set db = Nothing
QueryDuration = lngDone - lngStart
End Function
DomainFunctionsQuery.vbs:
Option Explicit
Dim cn, rs
Set cn = CreateObject("ADODB.Connection")
cn.Open "Provider=Microsoft.Jet.OLEDB.4.0;" & _
"Data Source='database1.mdb'"
Set rs = CreateObject("ADODB.Recordset")
rs.CursorLocation = 3 ' adUseClient '
rs.Open "qryDomainFunctions", cn, 3 ' adOpenStatic = 3 '
WScript.Echo "RecordCount: " & rs.RecordCount
rs.Close
Set rs = Nothing
cn.Close
Set cn = Nothing
How about:
SELECT a.JOINT,
(SELECT TOP 1 Joint
FROM Joint b
WHERE b.JOINT>a.JOINT
ORDER BY Joint) AS Ahead,
(SELECT TOP 1 Joint
FROM Joint b
WHERE b.JOINT<a.JOINT
ORDER BY Joint DESC) AS Behind
FROM Joint AS a;