I have an Access 2000 database that sits on a peer to peer network. (All computers are connected via ethernet 100 to a simple 4 port switch)
The database is designed with a back-end and a front-end.
There are 2 users that connect to the back-end via the network. One user connects to the back-end directly as the back-end sits on her computer.
All the computers are fairly low spec, with either 500 meg or 1 gig of ram
I have one form, frmInvoice, which is based on the table "tblInvoice" There are about 60,000 records in the table. There is also the table "tblInvoiceProducts" which is linked to "tblInvoice". There are about 150,000 records in the table "tblInvoiceProducts".
When I open form "frmInvoice" on the computer which hosts the back-end, the form opens very quickly. However if I open the form on one of the other computers it is very slow to open. However, once opened, record navigation is fast.
I think the problem is that when the form is opened, Access opens the entire table and all the records are accessible. Am I correct in this assumption?
I need to rectify this problem and welcome all suggestions.
I am going to install a D-Link DNS-323 2-Bay Network Storage Enclosure and store the back-end database on this as this will improve security / data protection.
Thanks in advance
"I think the problem is that when the form is opened, Access opens the entire table and all the records are accessible. Am I correct in this assumption?"
My bet is you are absolutely correct.
If you open the property sheet for the form, choose the Data tab, and look at the value for Record Source, do you see the name of one of your tables?
Or a query without a WHERE clause, perhaps like "SELECT * FROM tblInvoice;"?
If your answer is yes to either of those questions, Access will have to pull every single record from the table across the wire.
So, don't do that! :-) The key to decent performance is to limit the form's Record Source to a reasonable subset of rows.
Choose some criterion which makes sense in your situation, perhaps invoice date, and build a query which incorporates that criterion into its WHERE clause. Base that criterion on an indexed field --- add an index if your table doesn't already have one on that field. You can experiment by creating a new query in the query designer ... maybe the SQL View would look like this:
SELECT Invoice_ID, Customer_ID, invoice_date
FROM tblInvoice
WHERE invoice_date = Date();
That would retrieve only rows where invoice_date matches today's date.
(If you've already loaded the form, pulling down all the rows into local cache, you won't get a true indication of the speed of that query. It would be better to test the query from a new Access session without first loading the form.)
So then give the user a method to select a different invoice_date. For example, a text box control named txtSelectDate. And in the After Update event of that control, you can write an updated SELECT statement which you apply as the form's Record Source.
Private Sub txtSelectDate_AfterUpdate()
Dim strSql As String
If Len(Me.txtSelectDate & "") > 0 Then
strSql = "SELECT Invoice_ID, Customer_ID, invoice_date" & vbCrLf & _
"FROM tblInvoice" & vbCrLf & _
"WHERE invoice_date = " & Format(Me.txtSelectDate, "\#yyyy-m-d\#")
Debug.Print strSql
Me.RecordSource = strSql
End If
End Sub
The Debug.Print line will print the SELECT statement in the Immediate Window so if anything goes wrong you can view the completed statement, and copy & paste it into SQL View of a new query for testing. Changing the Record Source property automatically causes Access to requery the data source. And you can validate the user's entry in the text box's Before Update event to make sure it's a valid date.
Another possibility is to have the form first load with a single non-editable dummy record(1). Then display records only after the user chooses an invoice_date. To do that, you could use something like this as the form's Record Source:
SELECT TOP 1 Null AS Invoice_ID, Null AS Customer_ID, Null AS invoice_date
FROM tblInvoice;
In addition to the excellent suggestions made by HansUp, I would add the following:
Detailed forms should never take more than a couple of seconds to load. If they do, you're either doing something wrong or you need to optimize your binding/code.
Datasheet and reports can take longer depending on their complexity and how often people need them. Basically, if it's something they need a lot, it need to be fast. On the other hand, if it's a monthly report, waiting 30s is acceptable.
You don't say if your front end is split from the back-end or if you're using the same .mdb for your data and UI. If you do, then stop and split your database, even for the person on the same computer as the data.
Your back-end mdb file should only contain your data tables, and nothing else. The front-end mdb must be installed on the user's individual machines.
Make sure that your tables have the right indexes for your filter and sorting criteria. If you're binding your form to your tblInvoice and set the property to order by invoice reference and your table doesn't have an index for this, Jet will need to load all invoice references from the table to check their order.
If all your indexes are OK, Access will not load all the data to display a single record in a detailed form. It will only load lots of data if you're using a datasheet or a continuous form.
So the issue is probably with something else: maybe you're binding to a complex query from within your form, like a combo box or a datasheet, that it's that particular thing that's dragging your down.
Try to bind/unbind controls one by one to see which one has the biggest impact, then concentrate on optimizing that particular area.
You can also bind the controls as needed from code. For instance you can keep your form or the expensive controls unbound and wait for the user to select the record they need before you bind it to the form/control.
If you have lots of information on the form that loads related data from other queries and tables, you could re-design the form to group data in a Tab Control, then have a sub-form in each tab-control to display the data and load this only the user selects the tab.
Make sure that if you display lots of data in continuous forms/datasheets, that they are bound as snapshot to make them read-only. If they are read/write and someone else is trying to load a detailed form, Access has to do extensive lock check to make sure that the data is not being edited in multiple places. The more you make your data read-only, the better.
A final tip for for better performance: create a dummy table with a DUMMY field in your backend, link to it from the front-end. Bind this table to a form that has a single control for the DUMMY field. Make this form invisible and open it automatically when you start your application.
This form will keep the dummy table open and will ensure that your front-end doesn't have to open/close the connection to the backend every time it needs to query it.
Locking the database is time-expensive, especially over the network. If this is done very often, it can really decrease performance.
Maybe it won't change anything for your particular case, but I've found it to be good practice as the impact can be tremendous.
I have found the reasons for forms being very slow to load.
They include:
1. Having active filters on the form
2. Having combo boxes which allow users to navigate / select records
If I remove these I don't experience and speed issues.
To allow users to navigate / select records, I have a cmd button on the form. When clicked it opens a pop up form allowing the user to select the record of choice. I store the record id as a variable, close the pop up form and filter the recordset of the form e.g
select * from tblInvoice where InvoiceId = tmpInvoiceId
works for me!
yes Access (Jet) sucks bad across a network.. to make things more complex, there aren't real 'indexing tools' in Access like there are in SQL Server..
If I were you, I'd go get ONE solid PC, install SQL Server 2008 R2 on it, and upgrade to Access Data Projects.
Related
Updated***
To quickly summarize what I said before which was lengthy and probably too roundabout confusing...
My issue was I have a main form interface with 2 subforms that does the following:
the First one is always shown for current daily event task(s) based on a label control with current date or any given dates via the prev/next toggle buttons as criteria.
The second one is part of the main subform that will be called based on the selection from the main navigation controls. This subform shows all the event tasks that are created in the database.
Both use saved query select statements with a snapshot and no record lock as recordsource in a continuous form. The table source is linked via split database in the same shared network drive and the table itself contains only 8 fields:
ID (12 chars randomly generated with specific sequence via VBA with no duplicate allowed)
Event Name (Text)
Event Desc (Long Text/Memo)
Occurrence (Text)
Start Date (Date)
Selected Day (Text)
Allow Weekend (Text)
Active (Boolean)
To edit a selected record, it will be called and populated onto a popup unbound model form via VBA. Here everything works fine as for records with fewer chars found in the Event Desc field. However, for records with a large number of chars (so far those that I noticed with issues were the ones with >4000 chars). I can call and display them fine to the detail view form (unbound) but when making edits, by the time it gets to the updating field for Event Desc, it would generate the record locking error by the same user (me) for those with large text size.
After several trials and errors, I noticed it has to do with the displayed continuous subforms that I have presenting. I noticed that the form for them were originally set as dynaset to which I tried to switch over to snapshot and it was a hit and miss (mostly miss). I don't know how much of this setting was an issue to the record updating as I had no problem saving records with smaller character counts.
Finally, I decided to before saving the record, I went and remove the recordsource for both of the continuous forms to empty ("") then save then reassign the query (saved or direct doesn't matter) and it worked without errors after. I personally don't like doing it this way as it makes the continuous forms update look ugly and not smooth (see the #Name?) in the assigned controls due to unrecognized control source since the recordsource is now empty. However, this was the best way that I could think up at the moment.
I even tried using a bounded form for editing the record instead of the unbound form and the same error outcome, but that time it gave a different record locking error# but pretty much for the same reason.
I'm sure this issue is not related to how my VBA code was written nor the queries used in the continuous forms. It's most likely the form settings themselves or just some limitation of Access?
I'm turning to you for some help on an ACCESS FE/BE app.
I'm using VBA for quite some time now (Though only on ACCESS for like a year) but that's the first project where I actually need to setup a back-end on a distant server, and I'm struggling with what seems to be a connection issue (I'm no VBA Dev). I'm certainly doing things wrong, so feel free to redirect me on a good tutorial that could help me clear things up if that's the case.
So, my app is basically a storage database, we work on a file, add it to the BE through the FE with all relevant info, and users can look up things like client ID or name to get a list of all the files related and see where they are stored.
Everything works super well in local and is pretty much usable all-round with one user on a distant server, but things get complicated with multiple users. I have figured that the database seems to need to be entirely loaded in RAM to perform a search on its content, and that anytime someone modify a record, some flag tells Access to re-download the database on a new search to get everything fresh, which takes way too long (Around 30 seconds each time). Everything working as expected I guess, but I struggle to find a good way to have a multi-user app then. I can't imagine that's how it's supposed to work, so I guess I'm doing things the wrong way (Or it's the server, which is mainly a storage server for the entire company, that is not made for traffic purpose, but I dunno).
I have pinned the problem to this, which is a part of the code called when someone uses the search form :
Set searchRST = dbs.OpenRecordset(SQLQuery, dbOpenSnapshot)
If searchRST.RecordCount <> 0 Then
If searchRST.EOF = False Then
searchRST.MoveNext
End If
searchRST.MoveFirst
End If
Select Case searchRST.RecordCount
Case 0
.Controls("search_Results").Visible = False
.Controls("Button_Load").Visible = False
MsgBox ("No record found.")
Case 1
fileRST.FindFirst ("[ID] = " & searchRST![ID])
Forms(const_File).Bookmark = fileRST.Bookmark
.Controls("search_Results").Visible = False
.Controls("Button_Load").Visible = False
Call launchFileMode(modeVal)
Case Is > 1
searchRST.MoveLast
"Others checks are made before displaying results in a subform control
Basically, when you enter your search criteria and click on a button, it filters the database based on the SQLQuery string provided, goes through the entire filtered recordset so it can load it afterward, and either show a message " Nothing found " if the RecordCount of the filtered recorset is 0, show the only file available if it's 1 (By setting the bookmark of the recordset currently loaded by the File form to the bookmark of the filtered recordset), or show the filtered recordset in a subform if it's > 1 so the user can select the file to access.
The SQLQuery is a string programmatically created from the different searching criteria ( It was designed to handle a multiple criteria search but that's not used for now ), which gives something like this :
SQLQuery = "SELECT * FROM [DB] WHERE [CLIENT ID] LIKE '0123456';"
The loading time is not at the same point depending on the RecordCount :
If it's 0, the line Set searchRST = dbs.OpenRecordset(SQLQuery, dbOpenSnapshot) will get the charge ;
If it's 1, the line searchRST.MoveNext will get the charge ;
If it's > 1, the line searchRST.MoveLast ( Which is used to load the subform with all records of the filtered recordset so you can scroll down in it without delay ) will get the charge.
That's were I'm lost. Why isn't it the same line that get the loading time regardless of how big the recordset is ? If it's actually the action of retrieving the data that is problematic, it should be the dbs.OpenRecordset line that cause the problem in every situation, no ?
I'm probably doing things the worst way ever, so any advice on this or on any other way of handling these search would be greatly appreciated!
Feel free to ask for any detail that could be helpful.
(And sorry if it's not crystal clear, I'm working against the clock to post this from work where I can access the accdb file)
if there is only one record, then why a find first? If the query is similar to what you posted, then in most cases such a search for a ID will likely only return one record and should be fast. So most important is that the criteria limits the number of records pulled. Assuming some type of "key" is used, then as noted in the vast majority of cases you only likely will grab/return/pull one record from the server. You also need to 100% ensure that you have a persistent connection (a bound form remains open to a linked table that points to the back end.
It is also assumed that your application is split, and that each workstation has a copy of the FE.
It also assumed that the shared folder on the server for the back end is on your local network, and not some remote "VPN" type of connection over the internet which is usually about 100 times slower then your typical office network. You can read about the speed difference between a LAN (local area network) vs that of a WAN (wide area network) here:
http://www.kallal.ca//Wan/Wans.html
If you are working from a LAN, then I don't see why performance is slow, or any kind of issue (if it application runs fast with one user, but slow with two, then 9 out of 10 times, the persistent connection trick will fix this issue).
So basic assumptions are:
You application is split.
Each workstation gets a copy of the front end.
You have a persistent connection.
You are working on a standard office LAN as opposed to some kind of WAN (or VPN).
I'll qualify this with the upfront admission that I am not a professional programmer or database designer. I had a specific problem to solve - the need to manage a great deal of information on employee benefit requests - and limited resources, and built a database to replace the Excel spreadsheet I inherited. I'm learning as I go, and what I'd really like to find out is, can Access do what I'm envisioning, or do I need to adjust my goal?
The main tables my database includes are EMPLOYEES, REQUESTS, TASKS, and ACTIVITY (plus some auxiliary tables used for drop-down fields, etc.). Each employee can have multiple requests, and for each request, I used TASKS to keep track of all that needs to be done, by whom and by when, to see the request through from start to finish (I'm the only user of the dbase file itself, but I publish the task list as a report to those involved). ACTIVITY, as you would expect, is the "journaling" feature where I keep track of what's been done, conversations, etc.
What I want to do now is speed up the process of task entry, since for MOST requests, the taskflow is pretty similar. I already created tables for "TASK OWNER" and "TASK CATEGORIES" - both related to my primary TASKS table - so I could standardize the data entry for those fields, in addition to filtering on it ("show me everything the employee owns"). TASKS has a "NOTES" field so I can individualize the entry without changing the drop-downs. It's progress, but I'm still spending too much time adding the same however-many tasks to each new request I set up.
What I've been playing with in my head is identifying tasks with some sort of code that associates them with a type of request - i.e., an employee request for tuition reimbursement is identified as "request type 001" and all the tasks that normally go with a tuition reimbursement have a lookup field connecting them back to "001" - so that when I check a box or select "001 - Tuition Reimbursement" from a drop-down on my form, the 6 or 8 "standard" tasks automatically drop in to the TASKS subform, BUT they're NEW records - not just "template tasks" that I'm querying from my TASK CATEGORIES. I want to be able to tweak, edit, update, and even delete them, without affecting any other request.
I've found a lot of information adding new records via APPEND queries, but it seems to be limited to generating ONE record for each "transaction" if that makes sense - each time the query finds its value and performs the action, one record is generated. How do I get Access to recognize one value - request type - and generate each of the tasks associated with that request type as a new record in TASKS?
User selects category of request, such as 'Tuition Reimbursement' from a multi-column combobox. Hidden column of combobox has the category code and this is the bound column so this is value of combobox. Then code to save multiple records would be like:
CurrentDb.Execute "INSERT INTO Tasks(RequestID, TaskDesc) SELECT " & _
Me.tbxReqID & " AS RID, TaskDesc FROM TasksCategories WHERE TaskCat = " & Me.cbxCategory
The real trick is figuring out what event to put this code into. Should it be automatic after some data element is entered or should it be a button click? Then there is need to prevent duplicate entries - what if user accidentally clicks that button again? (This might be handled by a compound index setting in table.) Also, the new master record created on requests form has to be committed to table first. Record is committed when closing table/query/form, moving to another record, or running command to save.
As shown, your requirement is certainly possible, just keep in mind "the more user-friendly, the more code".
Now, when you develop code with specific issue, post it for analysis.
Hello helpful internet strangers. I have created a fairly simple database for a client that has one main entry form and a search form that uses unbound text boxes for searching around 15 fields.
Details are below, but here is my problem: When I add new records and use the search form all the fields work as expected and return the correct results in the datasheet. When I sent the database to the client and they add new records, they are not returned in the search. I had them save the database with their records and send it back to me, and I confirmed that when I search for the records they added (I can see the records in the table) they are not returned in the search. If I add new records to the copy they sent me back, my records do appear in the search.
I created the database in Access 2013 on a Windows 8 machine. The client is using Access 2010 on Windows Vista.
The field types are text, dropdown and date. The dropdowns are all based on lookup queries so they store the id number in the main info table and pull the name value from the query. All of the fields in the search query are written to allow nulls, including the date range searches. And again, all fields test out correctly on my machine when I enter the records.
I went on site and compared settings and nothing jumped out at me except the different versions. I also watched the client enter new records and she didn't do anything 'wrong' or unusual. When I try to do a save as 2007-2010 it says I am using features that won't allow for that, but for the life of me I can't think of anything like that since this is really a very straightforward design.
I'm going to do a package as executable, but am highly doubtful that will help. Any insights?
Thanks in advance.
A few things. Most important - always, always develop in the earliest version of Access that the system will be used with. So, you need to rebuild, using Access 2010, period. Try creating a new blank database (in 2010) and importing the objects one (or a few) at a time. Make sure the system is split. One file for the FE (forms, queries, reports, code, etc.) and one for the BE (tables only). Make sure that the Filter On Load property of all forms is set to No. If the recordsource for the form contains a where clause, make sure that the newly entered records meet the criteria for the where clause.
I'm trying to make access conditionally only show rows that meet a certain condition, allow me to give you some background info before I proceed :
I've created an Access form and linked it to a test DB on my machine. The particular table I am interested in contains the following (important) rows :
ID , Office, Name, SecurityNumber
The thing is, ID is not unique. There are two Office locations, and each Office has it's own set of unique ID numbers. This means that ID 10 here and there may or may not be the same person. (this data comes out of a legacy security system we're not looking to change yet, so I cannot change it)
But ID -is- unique to each Office.
SO! I created an Access form with TABS! Two tabs, one for each office. What I am trying to achieve now is :
Have the ID/Name/SecurityNumber fields for each tab populate with only rows that match it's particular 'Office' value.
Thank you for reading and thank you for helping! :D
If you want the data for the office locations presented in separate tab page controls, you could use subforms on the pages which differ only in the WHERE clause of the queries used as their record sources. So for the Office1 subform, the query could be:
SELECT ID, Office, [Name], SecurityNumber
FROM YourTable
WHERE Office = 'Office1'
ORDER BY [Name];
Then for Office2, the query would be the same except for the WHERE clause:
WHERE Office = 'Office2'
As I understand your question, that approach would do what you're asking for.
However, that's not really the easy "Access way" to do it. Instead consider a combo box control to allow your users to choose which office they want to view. In the code for the combo's after update event, either modify the SELECT statement used as the form's record source or create a filter expression an apply it.
Also, since you're pulling the form's data from SQL Server, consider whether you want your form to load every record for the selected office location. It may not be much concern if you have only a few to moderate number of rows for each location, but if you'll be dealing with multiple thousands of rows it could be. In general, you should try to avoid pulling copious amounts of data across the wire; pull sparingly instead ... only what you need for the immediate task at hand.