Join OfficeTipsAndMethods

Have some suggestions to share with the world?

Join OfficeTipsAndMethods and share your insight in a Comment. You must be a member to comment and all comments are moderated before being published. Membership is free and we would never share your personal information with anyone.

Become an OTM Member

Log In

YouTube Video Channel
Article Categories

Archive for the ‘Access 2003 and Earlier’ Category

Create a Nested Form Access User Interface

This article demonstrates how to create a co-ordinated set of Access forms that could be used, among other things, as a Dashboard or Launching Pad in an Access User Interface. Form content depends on the needs of the application. The forms in the demonstration application are just simple examples to illustrate what can be done. They are there just to show what the code behind the scenes does.

The concept is similar to Frames in HTML Documents.

The Forms

There are six forms in the demonstration application:


frmShell can be thought of as an outer container or framework. It has one subform control and command buttons and a toggle button that swap actual forms in and out of the subform control. This the opening form for the application. When the application starts, this nested form displays a simple welcome message which is contained in frmShell_ContentWelcome.



The other forms take turns populating either frmShell’s subform control second level subforms. The form name indicates each form’s relative position in the overall hierarchy.

Screen2r frmShell_ContentA is at the second level of the form hierarchy. If you consider frmShell to be at the top level of the form hierarchy (the patriarch) then frmShellContentA represents a child of the patriarch. frmContentA itself has two children (siblings if you don’t mind the metaphor.)

frmShell_ContentA populated with frmShellContentA


ContentA and Subformsr The content forms can be loaded as individual standalone forms.

frmContent and its two child forms

Screen3r Screen4r


frmShell_ContentTabs with Mouse pointing at the Child Tab


frmShell populated with frmShell_ContentTabs (populated with frmShell_ContentA)


frmShell_ContentA_1 and frmShell_ContentA_2 populate two subform controls on frmChild when that form is the main child form of frmShell

frmShell_ContentTabs is an alternate child form to frmShell. This form uses a tabbed style of interface to swap its subform content in and out.

frmShell_ContentWelcome populates frmShell with startup information.

Behind the Scenes – the Code

The main code that makes it all work is quite simple as long as you get the notation right. Content screens are swapped by assigning a form to the SourceObject property of the desired subform control.

For example, if a form has a subform control named fsbMainWindow, then a content form (for example, frmContent1) can be assigned to the control with this statement:

me!fsbMainWindow.SourceObject = “frmContent1”

Once the content form has been assigned to the subform control you can then refer to any controls or public properties in one of two ways:

me!fsbMainWindow.Form.[name of property or control]

or by creating a form object and assigning the subform object to the form object. Of course you have to declare a from object.

Dim frm as Form

then you can use the object by assigning the subform control to it.

Set frm = Me!fsbmMainWindow.Form

You can then use the object to refer to the content form and its controls and properties.

frm.txtDate = Now()

assigned the current date and time to a textbox named txtDate on the content form.

If there are several ‘windows’ on the form, then the object approach makes it easier to keep track of which window content you are referring to.

A subform can talk back to the form that contains it using its own parent property.

me.parent.CurrentChild =

is a trivial example. Of course you would have to declare a public variable or property, CurrentChild in the parent form.

It is possible to nest up to seven levels (Access XP and later, three in Access 2000) of subforms on a form. The notation can be confusing because you have to have the bang operator (!) in all the right places. For example,


refers to the subform one level down. If you want to refer to a subform nested on the form one level down, the notation is

Me!fsbMainWindow.Form![name of subform control].Form

For a general reference on referring to sub forms and sub reports see this page on The Access Web.

If you need to set the focus to a control on a sub form, first set focus to the sub form control and then set focus to the control you want to have focus.

General Development Guidelines

If you will be swapping several forms in and out of the same subform control, it helps to first create a simple empty form that is set up with the appearance that you would like all of the forms that you will use in that particular subform. Use that form as a template for each of the others.

A naming convention also helps keep the various forms straight in you mind. For example begin the name of each related form with frm followed by a generic name for the group of forms. For each sub form, append an underscore followed by a meaningful name for the particular sub form. Using this approach, all of the related forms are listed together in the database window or navigation pane depending on your version of Access.


Using a shell form to ‘house’ the main forms of a user interface makes it easier to create and maintain a consistent ‘look’ and feel for the application.

Demonstration File

The demonsration file can be downloaded from:

Action Queries-Delete Queries


Delete queries delete all records from the specified table that meet criteria specified in the query.



Scenario: The table of country names that you have inherited contains some invalid names. You have already updated the records that used to point to these names so they now point to the correct form of the relevant country name. Now you want to remove the invalid names from the table.


Before and After

With just a few entries, you could just open the table and delete the bad records. However, if you have many records to delete, a delete query can take care of all of them at once.

IMPORTANT: Before running a query that will modify your data, it is very important to first back up the data. You can do this either by copying the table’s structure and data or by backing up the entire database.

Setting up the query is quite straight forward. Use the table you want to clean up as the query’s record source and include the field that will determine whether or not a record should be deleted.



This query will delete records that contain “United States” in the Country field. All other records will be untouched.

If you include additional criteria on the rows below the first criteria, then the  query will delete records that match any one of the criteria.







Just to be safe, however you should not run a delete query before you view it. Notice the distinction between viewing and running the query. ViewDatasheet


Viewing a query will display the selected records in the query’s datasheet view.


Running the query, on the other hand, actually deletes the selected records.


Note also that double-clicking a query icon in the database window or Navigation Pane, runs the query. If you are not absolutely sure you should be running a query, open it in design view and view it in datasheet view.

Sample files for Action Queries (Access 2000 and 2007/2010 format) can be downloaded from:


Action Queries-Update Queries


Update queries are the means to modify record data in one or more fields in all the records that match the criteria you specify. For this article, I have modified the sample data so that it reflects an all too possible scenario.

Scenario: You have inherited a customer database where customer data was stored in a single table. You have normalized the table. When you created and populated the Countries table, you didn’t recognize that the country data inserted into the original table was inconsistent. Some country names were mis-spelled (Mxecio, United Staes, for example) and several forms of the name were used for others (US, USA, and United States, for example). The problem came to light with the Regional Manager for Mexico reported that one of more of his customers appeared to be missing when he search for customers in Mexico.

Solution: When you looked at the Countries table you recognized that the data had spelling variations for some countries. You have produced a new table with the correct spelling for each country’s name and there are no variations in the form of the name.

imageNow you want to clean up the data so that your normalized customers table will refer only to the new Countries table. You can’t simply delete the bad spellings because some of the records in your new customers table probably refer to these imagemisspelled country name records.
















The solution requires first identifying which customer records refer to bad country name and then updating those records so that they refer to the correct spelling. The query results at the right is a comparative list showing the current list of country names together with a list of only valid names. In design view the query looks like this:


Notice that the tables are joined by the country names, not by keys. An empty cell in the valid country column indicates that the name in the cell in the left column is invalid. This query gives us the information we need to know which records to update and what CountryID should be used for the update.

In the Customers table, these changes need to be made to the CountryID field

  • Mxeico 13 update to 12
  • United Staes 21 update to 24
  • United States 22 update to 24
  • US 23 update to 24

Because each of these updates has different criteria, four update queries will be required. (As you will see, you use a single query and simply modify the criteria after each update.) After we execute these updates, the customer table will correctly refer to Mexico and USA, respectively in the Countries table. Then the invalid names can be deleted from the Countries table.

A simple unmatched records query will help identify just the customer records that need attention. It is similar to the last query but this is what the results will look like:


The Is Null Criteria limits the output to just the unmatched records:


Now we are ready to create the actual update queries. This query will need the valid country id with which we want to update the affected Customer records:


This query includes the Company name field simply for “comfort.” The table requiring update is joined in this case to its related Countriesimage table so that we can restrict the record by the actual. Before running an action query it is usually a good idea to view the record selection you have specified. However, the datasheet view of an update query only shows you the field to be updated with its current value. To see more about the records that will be updated, you can temporarily convert the query to a select query and the view its datasheet.


Again you are only seeing the current data but you can verify that you have in fact set the criteria correctly. Now all that remains for the update is to convert the query back to its update form and then run it.

After you run the query for the first incorrect country name, you can change the query to the next misspelling, verify and run it. You simply repeat this process until you have updated the Country Id for all of the misspelled country names, one at a time.

It’s a good idea at this point to create a query to verify that the invalid country names are no longer referenced from the Customers table.

Remember: never run any data manipulation queries before backup up the affected tables and or the entire database.

Final cleanup involves deleting the records with the bad spelling. The next article in this series will discuss Delete Queries

Action Queries–Make Table (Part II)

Creating the new customer table is just a little different from the three make table queries in part one.


The query includes the original customers table and the three new tables. Note that the customers table is joined to each of the other tables by the field that contains the data pasted into the new tables by the respective make table queries. Some of the customer records may not have data in one or more of the Address, State/Prov and Country fields. The join types have each been modified so that all customer records will be included.

If we did not change the join type from the default equal join, then the only records the query would produce would be those that had data in the Address, State/Prov and Country fields in the original table. With the join type changed, the query will produce nulls whenever there is no corresponding value to the customer’s address, state or province, or country.

We can use the query grid to modify the field names and data that will be inserted into the new table.


In the query grid, each field cell begins with the name we would like to use for the field in the new table. Since not all customers are not companies and the table is the Customers table, CustomerName is probably a better field name for this data. So the expression


is in the field cell of the first column. So the data that is in the company field in tblCustomers will be pasted into the CustomerName field in the new table.

For the Address, State/Prov, and Country fields, we want the new table’s data to be the Primary Key field of the respective tables that now contain list of unique values for those fields. We can’t use and current names in the source tables for these fields in the query and for each of them a calculation is required to deal with possible null values. So we will use the names Address, State, and Country. After the table is created we will manually edit it so that these field names indicate that they are key values.

The three calculations are




Each of these calculations serves the same purposes

  • Name the corresponding field in the new table appropriately
  • If there is a null value in the query results populate the corresponding field in the new record with null.
  • Convert non-null values to the long datatype  because the three source fields are autonumbers

Set the Make Table up using the name tblCustomers_. Once the new table is created, we will change the name of tblCustomers to ztblCustomers and the name of the new table to tblCustomers so that we can retain the original table as a backup and continue to use the name tblCustomers for the ‘real’ data.

Executing (running) the query produces a new table in the database:


All that remains to be done is to modify the table’s design so that it has a primary key and appropriate field name and then create the relationships.

The modified and renamed new table looks like this:


Creating the relationships finishes the process:


Using a series of Make Table queries in this tutorial, we have taken the data from an unnormalized table are produced a normalized table of customers with three related tables.

Tools for the VBA Developer

Thomas Möller of Team-Moeller has very kindly given permission to publish this commented  list of VBA development tools.


Tool-Name: TM VBA-Inspector

Author: Thomas Möller


Purpose: Examine VBA-Code to find hidden faults and bad coding styles


Add. Info: For additional information refer to the help file and

this post on the Access Team Blog:


 Tool-Name: MZ-Tools 3.0 for VBA

Author: Carlos J. Quintero


Purpose: Great number of functions to make coding easier


Add. Info: You can attach a function to a shortcut

Options dialog / Shortcuts / Add "Search" to CTRL+F


Add. Info: You can easily rename a variable or a procedure:

  • Double click in variable name

  • Start Find-Dialog (CTRL + F) / Start search

  • Insert new name

  • Select each item and hit "Replace"

Tool-Name: VBE Tools v2.0

Author: Office Automation Ltd


Purpose: Bigger ‘Location’ label in References dialog


Add. Info: Start the options dialog. Check "Resize ‘Location Label’

on Tools / References dialog"


Tool-Name: Smart Indenter v3.5

Author: Office Automation Ltd


Purpose: Indent the whole code in a procedure, in a module or

in the whole project with one mouse click


Add. Info: Have a look at the options dialog. It shows directly

how the chosen options will indent your VBA-Code


 Tool-Name: TM VBA-ToDoFinder

Author: Thomas Möller


Purpose: Find ToDo’s in your VBA-Code


Add. Info: Tool and website is available only in German language

(But there is less UI – so check it out)


Add. Info: Put your ToDo’s as a comment to your VBA-Code

For example: ‘ToDo: Optimize this loop

Start ToDo-Finder and hit the Refresh-Button



 Tool-Name: Procbrowser

Author: Sascha Trowitzsch


Purpose: Browse through the procedures of a code-module


Add. Info: You find the button for download at the end of the website


Add. Info: Start the tool: Add-Ins / Procbrowser

Change the language: Context menu / "Sprache" / "Englisch"

Action Queries–Make Table


As the name implies, a Make Table Query creates a new table and populates the table with data selected by the query. In this article we will use an example that extracts data from an un-normalized table and creates a normalized structure with the data distributed into appropriate tables.

In this scenario, we have inherited a database that someone else designed. One of the tables, tblCustomers, includes customer name and address information. The table imagestructure looks straightforward enough but a look at the design reveals that the Address, State/Prov, and Country fields are all text fields.

What’s the problem? From a normalization standpoint, this table has repeated data. From a practical standpoint it means that for every record the name of the address, state or province, and country, must be typed. There is an inherent risk of data entry errors in which one of more of these values may be mis-spelled. There are also possible inconsistencies where a person may enter the abbreviation for the state or province in one record and the full name or an incorrect abbreviation in another record. Such errors increase the complexity of querying the data can often result in inaccurate query results.

If this data is properly normalized, then it is possible to use combo-boxes to restrict what data can be entered in a field to a list of valid values.

This example will use a series of queries to create normalized data from single unnormalized tblCustomers. The procedure for creating a make table query is similar to that for creating an append query. Start by creating a new query in design view. In Access 2007/2010 look for the Make Table shortcut on the Query Tools/Design tab of the ribbon:



QuerySelectQueryTypeIn Access 2003 and earlier, look for the Query Type icon on the Query Design toolbar. If this is the first action query you create the image will represent a select query. If you have previously created one of the other query types, the image will represent the type of the last query you created.

With the query open in design view, add the table from which you wish to extract data for the new table to be created.

When you click the Make Table Icon, the Make Table dialogue will pop up. The Table Name: text box will be empty. Type the name that you want to use for the new table in the textbox and click OK.


The address field is obviously the field that I want to send to the new table so I have added it to the grid. However, before we can actually run the query we have to set it up so that the query will return only unique values and will not return any nulls (addresses,for example) that have no data at this time). Notice that I have clicked the Totals shortcut and left the value in the Totals Row as group by. I have also specified Is Not Null in the first Criteria row.


When you click the Run shortcut, a dialogue appears, advising how many rows are about to be pasted into the new table.


There are 91 records in the customers table and it appears that there are no duplicate addresses. When you click yes the table is created and the selected data pasted into it. (In the sample database, I have saved the query as qmakAddresses.)image

Now is a good time to modify the new table so that it has an appropriate primary key. We will use the primary key later to create a relationship between each of the new tables and the modified customer we will create.

In the sample database, I have repeated this process to create a new State/Province table and a new Country table. When I reviewed the State/Province data, I noticed some inconsistencies. In some cases the field was the full name of the state or province. In other, it was the abbreviation. So, when I modified the table to include a primary key, I added a new field for abbreviation. When the revision of the structure is complete, some data maintenance on the State/Province table will be required to ensure the name is spelled correctly and that each record has both a full name and abbreviation.

The original table also had a special character in the state/province field name. So when I was modifying the table’s design, I changed that field name to remove the slash.

Two tasks remain. First we have to create a new customers table with fields that link to the new address, state/province, and country tables and its own primary key. Then we have to create appropriate relationships for the four new tables we have just created.

We will take a look at creating the normalized customers table in the next part of this tutorial (Update Queries)

Action Queries–Introduction

The name query implies that you are asking a question. Indeed, that is what selection queries do. However, action queries perform actions that change data in some way or other.

Action queries come on four ‘flavours.’

  • Append
  • Make Table
  • Delete
  • Update

It is important to keep in mind that an action query will perform its action on all records that meet its criteria. The same action is applied to all records so an action query is not an appropriate way to modify the data of individual records.

Append queries select data from the query source and append them to a specified table.

Make Table queries select data from the query source and create a new table containing just the selected records.

Delete queries remove all records that match the query criteria from the data source.

Update queries modify the contents of one or more fields in the query’s data source.

You follow the same general rules and procedures for creating action query criteria as you do for creating criteria in simple selection queries. Before executing an action query is very important to view the records selected. Otherwise you may end up making unwanted changes to your data. As an added precaution against unwanted data changes, you should also back up the database or at least the table(s) that will be affected by the query.

To create an action query start by creating a selection query in design view. In Access 2003 look for the Select Query Type Icon on the Query Design toolbar:


In Access 2007 and 2010 look for the Query Type group on the Query Tools Design Tab.


Depending on the query type you choose, the design grid will be modified. If you choose a Make Table or Append query type, a dialogue will appear to allow you to enter the name of the target table.

In the next four articles in this series, we will look at the specifics of each of the action query types in detail.

Parameter Queries

Parameters simplify query creation by allowing single query to produce different results depending on values you enter when you view the query. This lesson is intended to introduce the theory of parameters and the basics of using them in queries. The examples we are using are not forms driven. In a ‘real’ application all user interaction with the data is done through forms and the use of parameters is much more streamlined than the following examples might lead you to think.


In this query definition, the year, 2011, is entered as a literal value in the calculation of the review date. The only thing this query can calculate is a review date in 2011. Without the use of parameters, every year we would have to create a new query or re-design this one in order to calculate a new set of review dates.

Now, let’s look at a similar query, this time with a parameter in the calculation instead of a literal year value. Only one small modification is required. In place of 2011 for  the imagefirst argument of the DateSerial function, we put in [year]. Parameters are always enclosed in square brackets. Whatever text you type between the brackets will be used to prompt the user when the query is run. This text should not be the same as any of the field names in the query’s data source.

When you run the query, Access displays an input box with the text you placed between the square brackets as its prompt:



If you type 2011 in the input box and press enter or click OK, Access will substitute that value for the parameter in the calculation and produce a list of review dates in the year 2011.

When it is time for 2012 reviews, you can run the query again, this time entering 2012 into the input box instead of 2011.


Parameters are most frequently used to specify query criteria. Here’s an example that uses a parameter to specify the month in which increases come into effect. So, when


the query runs, an input box requesting the month appears. The payroll department could use a query like this to determine which employees to update on the first run of any given month. The last column of the grid handles cases when the user does not enter a month and causes the full list to be displayed.


The Parameters Dialogue

While most parameter queries do not require its use, the parameters dialogue serves two important purposes. First it lets you control the order in which the parameters are requested. Second it lets you specify a data type for the value to be entered.

imageIt is not always possible to clearly indicate the data type that you expect the user to enter. For example, the month value in the example queries in this article is a number. That is because the effective month calculation produces a number between 1 and 12. By specifying a byte type, you are ensuring that the user will receive a more meaningful error message if they type a month name than if you did not use the parameters dialogue to explicitly state that a numeric value is expected. To open the parameters dialogue, right-click in the top portion of the query designer and select Parameters.

If the query refers to the same parameter more than once, it is important that the references have identical spelling. Otherwise, Access will treat the variations as additional parameters.

Summary Queries

Download the demonstration database for this article from

In the previous article in this series, we looked at creating calculated query fields. While they not strictly speaking doing calculations, totals queries provide the means to summarize data, in which data can be grouped and numeric fields totalled or otherwise summarized.

For example, in the example database for this article, there is a table of employee salary information. The table stores current and past salaries for each employee along with the date that the person’s salary became effective. A query can be used to produce a list showing each person’s current salary along with the date he or she started receiving payment at that rate.


The graphic shows a query open in design view. This is the Access 2010 designer. In Access 2003 and earlier look on the query design toolbar for the totals (S) button. When you click it, the Total row will open in the query grid. When you click a cell in the Total row, a dropdown list opens with the available choices.


Each time a person’s salary changes, a new record is created in the salaries table. As you can see in the field list, the fields in this table are EmployeeID, to identify the related record in the employees table, Salary, the new salary, and SalaryEffectiveDate, the date on which the salary becomes effective.

As time goes by each employee may have several records in this table, one record for each change in pay. The grouping query allows us to group all salary records for each employee together in the datasheet. If data within the group is sorted into descending order by effective date then the first record in each employee group will be latest effective date and the corresponding salary will be the employees current salary. Keep in mind that if future salary changes have been recorded, you will need to use date criteria to ensure that those future salaries are excluded.


Here is the result of the query in datasheet view.:



This query can now be used as the data source for a report or form to inform management of each person’s current salary level and for how long they have been receiving that salary.

Now, here is a little more involved example. It has been decided to increase every employee’s salary in 2011 by 3% effective the first of the month in which their employment anniversary falls.

Here is the query design to do the calculations.


And the resulting datasheet:


Notice that we had to include the employees table because the query needs to know the employee’s data of hire to determine the anniversary month.

This query is not quite in its final form. We have included the literal value for the year, 2011. That would mean the query could only be used for 2011 salary calculation.

In the next article we will make the query more flexible by making it a parameter query.

Query Calculations

The demonstration database has been updated to include additional fictitious data so that the examples and demonstrations are more ‘realistic.’ You can download the new file from:


LastDollarQuery calculations provide a powerful means to turn raw data into valuable information. Depending on what you are after, there are different ways of performing the calculations:

  • calculated fields
  • grouping and totals

Once you have designed a query to provide the information want, you can use the query as the record source for both forms and report.

Calculated Fields

Standard relational database design practice requires that tables do not include calculated fields (although Access 2010 does include a calculated field type.) Typically a table will include the factors (such as units sold and cost per unit) needed to produce a result (sale amount) the result will be calculated whenever it is needed but it won’t be stored in a table.

The general pattern for a calculated query field is to start with entering a name for the field in the field row of a blank column in the Field row of the grid. Type a colon after the name and then type the formula after the colon. It is standard practice to enclose in square brackets any field references in the formula.

Calculations using numbers are obvious examples but it is also possible to perform calculations on text. For example, people’s names are usually stored in several fields – first name, middle name or initial, and surname. That simplifies management of the names but the result is not particularly nice to look at.

Here’s a simple example. The query, qselEmployees produces a simple list of employees with their date of hire and job title. Here is the basic design view:


In datasheet view each field is in a separate column:


Incidentally, all data in these and other examples in this series is completely fictitious. These aren’t the names of real people.

With a simple calculation, it is possible to display the names in a last name, first name format (“Jones, John”, for example.)

Here is the design view


Notice that the first field of the query is now a calculated field defined like this:

Fullname: [Lname] & ", " & [FName]

The definition starts with Fullname followed by a colon. ‘Fullname’ is the name of the calculated field. A form or report using this query as its record source can refer to this name as the control source for one of its controls.

The ampersand (&) is known as the concatenation operator. This calculation is concatenating (or joining) text. Field names used in the calculation are enclosed in square brackets ([ ]). And because we want to separate the first and last name with a comma followed by a space, we enclose those characters between two double quotes.

Here’s the result (qselEmployeesExtendedFullName):


A slightly different calculation lets us display the names in a first name last name format (qselEmployeesExtendedFirstLast):


A simple variation in the formula was all that was needed to make the change :

Fullname: [FName] & " " & [LName]

FName and LName have switched places and only a single space is concatenated between them. It is still possible to have the results sorted by the person’s last name, for example, simply by adding that field to the grid and unchecking the show box for that column.