I have a number of buttons on a form which provide additional information to the user, each using DCount to see if there is any information to display and if so, opening a popup form to display it. All had been working well but now for one particular button, it is taking anything between 30 seconds and a minute to open the popup form, which is obviously unacceptable. Can't understand why it worked fine originally but has now gone so slow. All the other buttons still open their form in under a second. VBA is:
Private Sub btnNotes_Click()
'open the popup notes for the current record, if there are associated records
If DCount("ID","qlkpIDForNotes") = 0 Then
MsgBox "There are no notes for this patient", vbOKOnly, "No information"
Else
DoCmd.OpenForm "fsubNotes",,,"ID = " & Me.displayID
End If
End Sub
The table being queried has approx 40,000 rows, where the largest table checked for the other buttons has about 12,000. Have tried doing the DCount directly on the table rather than through a query, but doesn't make any difference. Also tried taking out a section of the data from the original table, copying about 1100 rows into a new table and testing on that. It still took 12 seconds to open.
Any ideas, anyone?
Using DCount() just to find out if there are any row in the table or query can be rather inefficient since DCount() will have to run the whole query and go through all records to return the total count just so you can compare that to 0.
Depending on the complexity of that query, and the joins in it, and whether the joins use fields that have indexes or not, the cost of having to run that query can be exponentially proportional to the number of records in the underlying tables.
To solve your issue try this:
make sure there is an index on the underlying table's ID field in the qlkpIDForNotes query, and that all fields used in the JOIN or WHERE clauses also have indexes.
check if you can use the main underlying table or use a simplified query just to test if there are records that may be returned by qlkpIDForNotes, in short, you may not need to run that query in full just to find out if it would have some records.
use a separate function such as HasAny() below instead of DCount() when you only need to find out if a query returns any results:
'-------------------------------------------------------------------------'
' Returns whether the given query returned any result at all. '
' Returns true if at least one record was returned. '
' To call: '
' InUse = HasAny("SELECT TOP 1 ID FROM Product WHERE PartID=" & partID) '
'-------------------------------------------------------------------------'
Public Function HasAny(ByVal selectquery As String) As Boolean
Dim db As DAO.database
Dim rs As DAO.RecordSet
Set db = CurrentDb
Set rs = db.OpenRecordset(selectquery, dbOpenForwardOnly)
HasAny = (rs.RecordCount > 0)
rs.Close
Set rs = Nothing
Set db = Nothing
End Function
With this, you can simply rewrite your code as :
Private Sub btnNotes_Click()
'open the popup notes for the current record, if there are associated records '
If Not HasAny("qlkpIDForNotes") Then
MsgBox "There are no notes for this patient", vbOKOnly, "No information"
Else
DoCmd.OpenForm "fsubNotes",,,"ID = " & Me.displayID
End If
End Sub
Related
I see a lot of people that filter their Userform's ListBox using a TextBox or a ComboBox.
I'd like it to do mine, but unlike them, my Listbox is filled using a MySQL Recordset, while they are using an Excel Spreadsheet
There is my actual code, where SelectProduct is my Userform and ListRef is my Listbox.
Requete = "SELECT Reference,Nom,Marque,PrixVente FROM Produits_Beta"
rs.Open Requete, oConnect
SelectProduct.ListeRef.Clear
SelectProduct.ListeRef.Column = rs.GetRows
The Listbox is soon going to show 700+ results and I need a way for my user to filter them to find what they need.
If I used a Spreadsheet to get the Listbox value, my Filter code owuld look like this.
(Code originally from Ralph)
Dim i As Long
Dim arrList As Variant
Me.ListeRef.Clear
If TheoricalSheet.Range("A" & TheoricalSheet.Rows.Count).End(xlUp).Row > 1 And Trim(Me.TXBoxFilter.Value) <> vbNullString Then
arrList = TheoricalSheet.Range("A1:A" & TheoricalSheet.Range("A" & TheoricalSheet.Rows.Count).End(xlUp).Row).Value2
For i = LBound(arrList) To UBound(arrList)
If InStr(1, arrList(i, 1), Trim(Me.TXBoxFilter.Value), vbTextCompare) Then
Me.ListeRef.AddItem arrList(i, 1)
End If
Next i
End If
If Me.ListeRef.ListCount = 1 Then Me.ListeRef.Selected(0) = True
I could, but I would need a way to paste all a MySQL table to an hidden Spreadsheet, and, again, I have no idea how to do so.
The canonical way to filter SQL data for display is to use a WHERE clause in your query. That will work when you have seven, seven hundred, or seven million rows.
You might try something like this:
SELECT Reference,Nom,Marque,PrixVente
FROM Produits_Beta
WHERE Produit LIKE CONCAT('filter value', '%')
ORDER BY Produit
LIMIT 100
If you give an empty string for filter value you'll get the first hundred rows; your user will quickly see that a filter is necessary.
With no filter value, you get WHERE Produit LIKE '%' to not filter. With Pom as the filter value you'll get WHERE Produit LIKE 'Pom%' That matches Pomme, Pomme de terre, and Pomade, for example.
Edit You can use %pom% in LIKE. Here's the thing, however: if your search term has % coming first, the DBMS cannot use index lookups to find your data, so the searches will be slower. With a thousand rows to search, this doesn't matter. With millions of rows, it matters a lot.
Many developers of this kind of software use queries a lot to filter their data. DBMSs are built for it. The whole point of a DBMS is to allow software to handle vast sets of data efficiently.
Pro tip: Always use ORDER BY in your queries. If you don't the database server is free to present results in any order it finds most efficient at the moment. That's called unstable sorting and it drives users crazy.
The Worksheet.Visible attribute has three options, as follows:
xlSheetVisible 'The usual visible worksheet.
xlSheetHidden 'Worksheet that is hidden but may be turned visible by the user.
xlSheetVeryHidden 'Worksheet that is hidden but may only be turned visible via VBA.
If you were to create a hidden sheet to receive this data, you could try it as such:
Let's say you have first created a Worksheet in your workbook, (vba)named ws. In order to fetch the data from this recordset, you'll have to loop through its records and copy the value of each to a row:
'Header
With ws
.Cells(1,1) = "Reference"
.Cells(1,2) = "Nom"
.Cells(1,3) = "Marque"
.Cells(1,4) = "PrixVente"
End With
'Rows
Dim i as Long: i = 2
with Requete
If not (.EOF and .BOF) then
.movefirst
Do until .EOF
ws.Cells(i,1) = .Fields("Reference")
ws.Cells(i,2) = .Fields("Nom")
ws.Cells(i,3) = .Fields("Marque")
ws.cells(i,4) = .Fields("PrixVente")
.MoveNext
i=i+1
Loop
End If
End With
Then, if you don't want the user to have access to your sheet, just do:
ws.visible = xlSheetVeryHidden
If you plan to refresh the query and fetch the data again, you'll have to clear your worksheet beforehand.
Also, it might be good to order your data in your SQL query, so that the listbox gets populated alphabetically, should it suit your needs.
I have a database which I'm working on. It is composed of split database, both front and multiple links to backend tables.
I am working on a report which is composed of 15 different sub-reports. I have a form which allows me to input start date and end date for the report. There's a button which generates the final report. The problem is when I want to generate the report, I would have to re-run each of the different make-table queries for each of the sub-reports. The issue with this is that there would be 2 warnings for each query, one to delete my table and another for rows added to the table.
I researched online and found this code to run the Execute command which will remove all the warnings. I'm new to VB but I figured I'll give it a try and I get the following run-time error "3078: The MS Access database engine cannot find the input table or query". I checked the query name and it matches so I'm not sure why I'm getting this error. I've only tried one of the 15 queries so I can make sure it works. Once I get this to work, my other question is would combining all these into 15 execute commands work in the module?
Private Sub PS_Report_Date_AfterUpdate()
Dim dbs As DAO.Database
Dim lngRowsAffected As Long
Dim lngRowsDeleted As Long
Dim sql$
sql = "[qry_Maui_Division_KWH_Produced]"
Set dbs = CurrentDb
' Execute runs both saved queries and SQL strings
dbs.Execute sql, dbFailOnError
' Get the number of rows affected by the Action query.
' You can display this to the user, store it in a table, or trigger an action
' if an unexpected number (e.g. 0 rows when you expect > 0).
lngRowsAffected = dbs.RecordsAffected
dbs.Execute "DELETE FROM tbl_Maui_Division_KWH_Produced WHERE Bad", dbFailOnError
lngRowsDeleted = dbs.RecordsAffected
End Sub
SQL Code:
SELECT
tbl_MPP_DailyGenerationReport.DateLog,
[MPP_Daily_Gross_Gen_kWh]+[Total_Gross_kWh] AS Maui_Gross_kWh_Produced,
[Total_Aux]+[Total_Aux_kWh] AS Maui_Gross_Aux_kWh_Produced, [MPP_Daily_Gross_Gen_kWh]-[Total_Aux]+[Total_Net_kWh] AS Maui_Net_kWh_Produced,
Round(([Total_MBTU_Burned]*1000000)/([MPP_Daily_Gross_Gen_kWh]+[Total_Gross_kWh]),0) AS Maui_Gross_BTU_kWh,
Round([Total_MBTU_Burned]*1000000/([MPP_Daily_Gross_Gen_kWh]-[Total_Aux]+[Total_Net_kWh]),0) AS Maui_Net_BTU_kWh,
Round(([MPP_Daily_Gross_Gen_kWh]+[Total_Gross_kWh])/[Total_Barrels_Burned],0) AS Maui_Gross_kWh_BBL,
Round(([MPP_Daily_Gross_Gen_kWh]-[Total_Aux]+[Total_Net_kWh])/[Total_Barrels_Burned],0) AS Maui_Net_kWh_BBL
INTO tbl_Maui_Division_KWH_Produced
FROM ((tbl_MPP_DailyGenerationReport
INNER JOIN tbl_KPP_DailyGenerationReport
ON tbl_MPP_DailyGenerationReport.DateLog = tbl_KPP_DailyGenerationReport.DateLog)
INNER JOIN tbl_MPP_Aux_DailyGenerationReport
ON tbl_MPP_DailyGenerationReport.DateLog = tbl_MPP_Aux_DailyGenerationReport.DateLog)
INNER JOIN qry_Maui_Total_Fuel_Burned
ON tbl_MPP_DailyGenerationReport.DateLog = qry_Maui_Total_Fuel_Burned.DateLog
WHERE (((tbl_MPP_DailyGenerationReport.DateLog)=[Forms]![Power Supply Reports]![PS_Report_Date]));
This will run your queries without warnings:
Private Sub PS_Report_Date_AfterUpdate()
DoCmd.SetWarnings False
DoCmd.OpenQuery "qry_Maui_Division_KWH_Produced"
DoCmd.RunSQL "DELETE FROM tbl_Maui_Division_KWH_Produced WHERE Bad"
DoCmd.SetWarnings True
End Sub
I looked at a number of 3061 posts but they all have the query in VB. I am trying to run an already saved query in Access, that has a filter using a text field on a form. So all I am trying to do is just get a recordset from an existing query.
Not sure quite how to explain what's going on. But I have a Master form which holds the current selected date in a text object. I have a query that filters results based on the text object value:
SELECT DISTINCT EmployeeName
FROM dbo_Audits
WHERE dbo_Audits.AuditDate = [Forms]![MasterForm]![ReportDate]
Running the query is fine and it pulls for the selected date except in a specific circumstance.
If I open a subform, and keep the master form still open but not in focus, it still works i.e. I can run the query and it pulls the list of employees that had an audit that day.
But if I click a button on the subform to perform an action and put a breakpoint on the OnClick event, then try to run the query, it doesn't return any results. Its because it doesn't "recognize" or it's lost the value of "[Forms]![MasterForm]![ReportDate]" and therefore no results are returned.
Odd thing is, at the breakpoint, I query the text box value in the intermediate window and it still returns the date.
That is one way I have tested it. But what I am really trying to do is get the recordset from this query, in the back end coding, but when it encounters this coding:
strSQL = "SELECT * FROM " & strQueryName & " "
Set rstNames = CurrentDb.OpenRecordset(strSQL)
The OpenRecordSet returns the error message:
3061 - Too Few Parameters. Expected 1.
I put a breakpoint on the OpenRecordSet and do a DCount on the strQueryName and get a result of the number of records. So the query is kind of working. But not when I run the query through access (while on the breakpoint) and not when it tries to open the recordset.
Any ideas what's going on and how to fix this?
Since OpenRecordset does not dereference [Forms]![MasterForm]![ReportDate], and thinks it's a parameter, open the the saved query as a QueryDef object and give it the parameter value Access wants. Then you can use OpenRecordset from the QueryDef.
Dim qdf As DAO.QueryDef
Set qdf = CurrentDb.QueryDefs(strQueryName)
qdf.Parameters(0).Value = Eval(qdf.Parameters(0).Name)
Set rstNames = qdf.OpenRecordset()
In my MS Access DB I'm running a query in VB that should return two records. When I run it in SQL I get two records but when ran from VBA I get two. Here is the code in the SQL view which gets two records:
SELECT *
FROM tblWebMeetingData
WHERE [Last Name] LIKE 'Marx';
And when I call this in VBA like so:
SQL = "SELECT * FROM tblWebMeetingData WHERE [Last Name] LIKE 'Marx';"
Set rst = CurrentDb.OpenRecordset(SQL)
MsgBox ("Number of records: " & rst.RecordCount)
I get one record for number of records. Isn't record count suppose to count all the records returned from a SQL statement or table? What is it I'm doing wrong here?
Thanks
DAO doesn't retrieve the entire result set at once for all but the simplest queries (performance optimisation). To force a complete retrieval and a valid recordcount use
rst.MoveLast after opening the recordset and before retrieving rst.RecordCOunt.
for recordcount property you need to set cursor type as
RS.Open SQL, MyConn, adOpenStatic, adLockReadOnly, adCmdText
if it can not execute then you need to use ADOVBS.INC as include file in the top of your page you can download and use it from the internet
Above answers are all good. Microsoft's documentation on RecordCount has this:
Use the RecordCount property to find out how many records in a Recordset or TableDef object have been accessed. The RecordCount property doesn't indicate how many records are contained in a dynaset–, snapshot–, or forward–only–type Recordset object until all records have been accessed. Once the last record has been accessed, the RecordCount property indicates the total number of undeleted records in the Recordset or TableDef object. To force the last record to be accessed, use the MoveLast method on the Recordset object. You can also use an SQL Count function to determine the approximate number of records your query will return.
Note
Using the MoveLast method to populate a newly opened Recordset negatively impacts performance. Unless it is necessary to have an accurate RecordCount as soon as you open a Recordset, it's better to wait until you populate the Recordset with other portions of code before checking the RecordCount property.
I have a job-tracking system, and there is a query that returns results of all jobs that are overdue.
I have a form that displays each of these jobs one-by-one, and has two buttons (Job has been completed, and Job not completed). Not completed simply shows the next record.
I cannot find a way to get access to the current record to update it's contents if the "Has been Completed" button is pressed, the closest I can get is the long number which represents the records position in the form.
The VBA to get the index of the record in the form is as follows.
Sub Jobcompleted(frm As Form)
Dim curr_rec_num As Long
curr_rec_num = frm.CurrentRecord
End Sub
This is my first shot at VBA, and after an hour of searching I cannot find anything to solve my problem.
Am I going about this the entirely wrong way? Working in Microsoft Access 2007
Further Info All tables are normalized
Vehicle Table: Contains vehicle_id(pk), as well as rego and model etc
Job Table: Contains job_id(pk), vehicle_id(fk) and other info about what needs to happen, as well as the next occurance date, days between each occurance of the job (all jobs repeat) and other info
Job History Table: Contains job_history_id(pk), job_id(fk), date completed and comments
When the job completed button is pressed, it should create a new entry in the job history table with the current date, any comments and the job id
This is the script I am trying to get working
Private Sub Command29_Click()
Dim strSQL1 As String
Dim strSQL2 As String
Set Rs = CurrentRs
Set db = CurrentDb
strSQL1 = "INSERT INTO completed_jobs(JOB_ID, DATE_COMPLETED, COMMENTS) VALUES " & Rs!job.ID & ", " & Date
db.Execute strSQL1, dbFailOnError
strSQL2 = "UPDATE job SET JOB_NEXT_OCCURANCE = JOB_NEXT_OCCURANCE+JOB_RECURRANCE_RATE WHERE job.ID = Rs!job.ID"
db.Execute strSQL2, dbFailOnError
End Sub
Note: Line Set Rs = CurrentRs is completely incorrect, I believe this is what I need to figure out? This is called on button-press
I am posting an image which shows the form (non-continuous).
#HansUp, I get what you are saying, but I dont quite think it's applicable (I did not provide enough information first time around for you to understand I think)
#sarh I believe this Recordset that you are talking about is what I need, however I cannot figure out how to use it, any hints?
#Matt I am 90% sure I am using a bound form (Like I said, new to Access, been looking at everything people have suggested and learning as I go). There is of course an ID for the job (Just not shown, no need to be visible), but how would I access this to perform an operation on it? SQL I can do, integrating with Access/VBA I am new at
As I understand your situation, your form is data-bound bound (you can get record index), so - your form already located on this record. If you need to update some field of underlying dataset, you can write something like
Me!SomeField = ...
DoCmd.RunCommand acCmdSaveRecord
If your form has control bound to "SomeField", then the form will be updated automatically.
If this will not help, you can look to a couple of another directions:
1) Update records using SQL code. For example, you have ID of record that should be updated in the form data set, so you can write something like:
Call CurrentDB.Execute( _
"UPDATE SomeTable SET SomeField = SomeValue WHERE SomeTableID = " & Me!SomeTableID, dbSeeChanges)
2) You can look at the Bookmark property - both Recordset and Form has this property, it describes the record position. So you can write something like this (not the best example, but can help you to get an idea):
Dim Rs as Recordset
Set Rs = Me.RecordsetClone 'make a reference copy of the form recordset
Rs.Bookmark = Me.Bookmark 'locate this recordset to the form current record
Consider a simpler approach. I doubt you need to be concerned with the form's CurrentRecord property. And I don't see why you should need a command button for "Has been Completed" and another for "Has not been Completed".
Add a "Yes/No" data type field to the table which is used by your form's record source. Set it's default value property to 0, which represents False or No. Call it "completion_status". Create a new form using that record source. Then your form can have a check box control for completion_status.
Newly added records will have False/No as completion_status --- the check box will appear unchecked. The completion_status for other records in the forms can be toggled between Yes (checked) and No (unchecked) using the check box control.