Microsoft Access VBA Tutorials for self-paced learning, Class Modules, SQL Techniques, and AI Integration Guides.

Sum Min Max Avg ParamArray

Sum Min Max Avg ParamArray.

I’m sure your first reaction after reading the title might be, “I already know all that—tell me something new!” If you haven’t come across the last item in the title (the odd one out), then that’s exactly what I plan to share here—so keep reading.

The first four terms are quite familiar; they refer to built-in functions in Microsoft Access and worksheet functions in Excel. We’ll get to the last one a little later, but first, let’s look at how the Min() function works in Excel—and why using it in Microsoft Access presents a few challenges in comparison.

Difference between Excel and Access.

We’re certainly not forgetting the other domain aggregate functions in Access—DCount(), DSum(), DMin(), DMax(), and DAvg().

Let’s start by looking at how the Min() worksheet function works in Excel. It can identify the minimum value from a range of cells in a single column, a row of cells across multiple columns, or even a block of cells spanning several rows and columns.

However, when we return to Microsoft Access, the Min() function behaves differently. It can only be applied to a single column (that is, a single field) within a query, or in the header and footer sections of forms or reports. So, how do we determine the minimum value across multiple fields?

Go through the sample table below to better understand the issue we’re dealing with.

We have received Quotations for Electronic Items from three different Suppliers, and we need to know which quotation is the lowest and from which Supplier. In this case, our Min() Function has no use here unless we reorganize the above data in the following format:

To obtain the desired result from this data, we’ll need to create two queries, setting aside—for now—issues such as duplicate descriptions, supplier names, or the overall table size.

  1. First Query (Total Query):
    Group the data by the Desc field and use the Min() function to determine the lowest value from the Values field.

  2. Second Query:
    Use both the original table and the first query as data sources. Join them on the Desc and MinOfValues fields from the Total Query with the Desc and Values fields of the base table. This will return all records from the table that match both the description and the lowest quoted value.

The ParamArray Method.

I consider these steps to be excessive work, and I know you will agree too. Instead, we can write a User Defined Function with the use of ParamArray and pass the Field Names to the Function and find the Minimum Value from the list. Here is a simple Function with the use of the ParamArray declaration to find the Minimum Value from a List of Values passed to it.

Public Function myMin(ParamArray InputArray() As Variant) As Double
'------------------------------------------------------------------
'Author : a.p.r. pillai
'Date   : November-2008
'URL    : www.msaccesstips.com
'All Rights Reserved by www.msaccesstips.com
'------------------------------------------------------------------
Dim arrayLength As Integer, rtn As Double, j As Integer

'calculate number of elements in Array
arrayLength = UBound(InputArray())

'initialize Null values to 0
For j = 0 To arrayLength
   InputArray(j) = Nz(InputArray(j), 0)
Next
'initialize variable with 1st element value
'or if it is zero then a value with high magnitude
rtn = IIf(InputArray(0) = 0, 9999999999#, InputArray(0))

For j = 0 To arrayLength
    If InputArray(j) = 0 Then
 GoTo nextitem
   If InputArray(j) < rtn Then
        rtn = InputArray(j)
    End If
nextitem:
Next

myMin = rtn
End Function

Copy and paste the above Code into a Global Module and save it.

A few simple rules must be kept in mind while writing User Defined Functions using the ParamArray declaration in the Parameter list of the Function.

  1. While declaring the Function, the Parameter Variable InputArray() (or any other name you prefer) must be declared with the keyword ParamArray, in place of ByRef or ByVal we normally use to declare parameters to functions.

  2. The Data Type must be a Variant type.

  3. The ParamArray declaration must be the last item in the Parameter list if the UDF accepts more than one Parameter.

  4. The Optional parameter declarations should not appear before the ParamArray declaration.

  5. Since the data type is Variant, it can accept any value type.

Using the above myMin() Function, we have created a Query on the first Table given above. The SQL and the resulting image of the Query in Datasheet View are shown below.

SELECT MaterialQuote.Desc,
 MaterialQuote.Supplier1,
 MaterialQuote.Supplier2,
 MaterialQuote.Supplier3,
 mymin([supplier1],
[supplier2],
[supplier3]) AS Minimum,
 IIf([minimum]=[supplier1],"Supplier1",IIf([minimum]=[supplier2],"Supplier2",IIf([minimum]=[supplier3],"Supplier3",""))) AS Quote
FROM MaterialQuote;

In the above example, we have used only three Field Values to pass to the Function, and these can vary depending on your requirement.

Modified Version of VBA Code

A modified version of the same function is given below that accepts a Calculation Type value (range 0 to 3) as the first Parameter, and depending on that, we can find the Summary, Minimum, Maximum, or Average values passed to it through the InputArray() Variable.

Option Compare Database

Enum SMMA
    accSummary = 0
    accMinimum = 1
    accMaximum = 2
    accAverage = 3
End Enum

Public Function SMMAvg(ByVal calcType As Integer, ParamArray InputArray() As Variant) As Double
'------------------------------------------------------------------------
'calType : 0 = Summary'        : 1 = Minimum
'        : 2 = Maximum'        : 3 = Average
'------------------------------------------------------------------------
'Author  : a.p.r. pillai'Date    : November 2008
'URL     : www.msaccesstips.com
'All Rights Reserved by www.msaccesstips.com
'------------------------------------------------------------------------
Dim rtn As Double, j As Integer, arrayLength As Integer
Dim NewValue As Variant

On Error GoTo SMMAvg_Err

If calcType < 0 Or calcType > 3 Then
     MsgBox "Valid calcType Values 0 - 3 only", , "SMMAvg()"
     Exit Function
End If

arrayLength = UBound(InputArray())
'Init Nulls, if any,  to 0
For j = 0 To arrayLength
   InputArray(j) = Nz(InputArray(j), 0)
Next

For j = 0 To arrayLength
    NewValue = InputArray(j)
    'skip 0 value
    If NewValue = 0 Then
 GoTo nextitem
    End If
    Select Case calcType
    'Add up values for summary/average
        Case accSummary, accAverage
            rtn = rtn + NewValue
        Case accMinimum
            rtn = IIf(NewValue < rtn, NewValue, rtn)
            rtn = IIf(rtn = 0, 9999999999#, rtn)
        Case accMaximum
            rtn = IIf(NewValue > rtn, NewValue, rtn)
    End Select
nextitem:
Next

'Calc Average
If calcType = accAverage Then
   rtn = rtn / (arrayLength + 1)
End If

SMMAvg = rtn

SMMAvg_Exit:
Exit Function

SMMAvg_Err:
MsgBox Err.Description, , "SMMAVG()"
SMMAvg = 0
Resume SMMAvg_Exit
End Function

The Function name was defined using the first letters of the Calculation Types that the Function can perform, and I hope you like it too.

When any of the values in the InputArray() element is zero, that is ignored and will not be taken as the minimum value.

Sample Runs on Immediate Window:

? SMMAvg (0,0,10,5,7) 'Summary
 Result: 22 

? SMMAvg (1,0,10,5,7) 'Minimum value from 0,10,5,7
 Result: 5
 
? SMMAvg (2,0,10,5,7) 'Maximum value from 0,10,5,7
 Result: 10 
 
? SMMAvg (3,0,10,5,7) 'Average
 Result: 5.5 
  

We can use this Function in Text Boxes on Forms,  Reports, or from other Controls. Use it at your own risk.

Share:

Textbox And Label Inner Margins

Textbox And Label Inner Margins.

Whether it’s a Form or a Report, a well-crafted design always draws the attention of both the user and any onlooker. Everyone designs forms and reports—but if you give the same task to five different people, each will produce a unique result based on their individual skills and sense of artistry unless they all rely on the same built-in wizards.

Most users focus primarily on the information presented in a report and want it organized and accurate. However, how that information is presented is entirely up to you—and it often depends on how much time you can invest in the design process. Remember, you typically design a report only once as part of a project, so it’s worth doing it thoughtfully.

Your report may eventually circulate far and wide, through faxes, emails, or shared databases, to reach a much wider audience. When compared with other reports circulating around, you’d want someone to pause and ask, “Who designed this one?” Fortunately, Microsoft Access provides all the tools you need to create visually appealing, professional-quality reports and forms. With just a bit more time and creativity, you can achieve remarkable results using the simple yet powerful tools Access offers.

Precision Positioning of Data.

Here, I’d like to introduce you to a few important properties of TextBoxes and Labels on a report—and show you how a few simple design adjustments can transform an ordinary layout into a professional, visually appealing report.

The image below shows a Tabular Report created using the Report Wizard in Microsoft Access.

Wizards are excellent tools for quickly laying out all the objects on Forms or Reports with default formatting for font type, size, and style—saving a significant amount of design time. All that’s left is to fine-tune the layout to suit your preferences.

A Sample Project.

If you’d like to try this simple design step by step, import the Shippers table from the sample database at
C:\Program Files\Microsoft Office\Office11\Samples\Northwind.mdb. (That path is for Access 2003 — if you’re using a different version, look for the appropriate \Office##\ folder.) After importing, select the Shippers table, choose Report from the Insert menu, and pick Auto Report: Tabular from the options. Access will generate the report in moments. A preview of the report in Print Preview is shown below:

The modified version of the same Report in Print Preview below:

The transformation was easy with only a few changes to the above design, and I know which change you have noticed first. If I have turned on the borders of the TextBoxes and Labels alone, then the Print Preview will look like the one below:

Make the following changes to the above design:

  1. Delete the thick line under the Header Labels.

  2. Point the Mouse on the vertical ruler to the left of the Header Label Shippers so that it turns into an arrow pointing to the right, and then click and drag along the ruler downwards so that you can select all the Labels and Text Boxes in the Report Header, Page Header, and Detail Sections together.

    Alternatively, you can click on an empty area of the Report and drag the Mouse over all the controls to select them. Do not select the Page Footer Section Controls. We don't need them in this Report.

  3. Display the Property Sheet (View -> Properties) and change the following Values:

    • Border Color = 9868950
    • Special Effect = Flat
    • Border Style = Solid
    • Border Width = Hairline

    You need to change only the Border Color Value; others will be there as default. If not, then change them as given above.

  4. Select all the Field Header Labels alone in the Page Header Section, as we did in Step 2 above. Select Format -> Align -> Left to arrange the labels close together horizontally without leaving gaps between them.

  5. Display the Property Sheet of the selected Labels (View -> Properties) and change the Top Property Value to 0 and Height Property Value to 0.4167 Inches.

    Centralizing Text Vertically.

  6. Centralize the Text horizontally within the Labels by changing the Text Align Property Value to Center, while all the Labels are still in the selected state.

  7. Select all the TextBoxes in the Detail Section together and select Format -> Align -> Left to arrange the TextBoxes close together without leaving gaps between them.

  8. Display the Property Sheet of the TextBoxes (if you have already closed it) and change the Top Property Value to 0 and the Height Property Value to .2917" so that the data lines are not too close and crowded when Previewed/Printed.

  9. If there is a gap below the Labels in the Page Header Section and below the TextBoxes in the Detail Section, then close them by dragging up the Detail Section Header and the Page Footer Bars.

  10. Delete all the Page Footer Section controls. Close the gap by dragging the Report Footer Bar up.

  11. Next, resize the Report Header label containing the Shippers heading so that it spans the combined width of all the field header labels in the Page Header section. You can do this either by adjusting it manually by eye or by opening the Property Sheet for each header label, noting their individual Width property values, adding them together, and then setting the Width property of the Shippers heading label to that total.

  12. Change the Height Property Value to 0.416, and the Text Align Property Value to Center.

  13. Save your Report with a Name of your choice.

    With the above modifications, the Report Print Preview image is shown below and needs to be corrected.

The Report looks good, but with a few more cosmetic changes, it will look even better.

  1. The Field Header Labels' Caption Text must be vertically centered.

  2. The Shipper ID Numbers and other field values are too close to the Border Line, and they should be positioned a little away from the border.

  3. Open the Report in Design View and select all the Field Header Labels together as we did earlier.

  4. Display the Property Sheet and drag the right scroll bar of the Property Sheet down to the bottom. There, you will find the Inner Margin Properties that you can use to position the Text within the Controls.

    NB: These Properties are available only in MS Access 2000 and later versions.

  5. Change the Top Margin Property Value of Header Labels to 0.1".

  6. Select the Text Controls together on the Detail Section and change the Top Margin Property Value to 0.0701".

  7. Select the Shipper ID TextBox in the Detail Section, and change the Right Margin value to 0.1".

  8. Select the Company Name TextBox, and change the Left Margin Value to 0.0597" and set the same value for Phone Number also.

  9. Save your Report and open it in Print Preview. It is similar to the 3rd Image from the top of this page.

Although the explanation may seem lengthy, once you understand the steps, you can complete the design in just a few minutes.

Share:

Multiple Parameters For Query

Multiple Parameters For Query.

Queries are an essential component of data processing, and we rely on them extensively in various ways. One of the main challenges when creating queries is how to filter data in a user-friendly manner, making the process seamless for the user. To address this, we employ several methods that allow users to easily pass values as criteria to the queries.

  1. You can create Parameter Queries by inserting variables, such as:[EnterSalesDate]', Into the Criteria row of a query. When run, the query will prompt the user to enter the parameter value, allowing them to filter records directly. To define the data type for a parameter variable, use the Parameters… option from the Query menu while in Design View.

  2. You can place TextBoxes or Combo Boxes on a Form, where the user can enter or select values before running a Report or viewing data. The underlying queries reference these controls in their Criteria rows—for example, Forms![MyForm]![myDateCombo]. Based on the values entered or selected, the queries filter the data accordingly, producing the desired results in Reports or data views.

  3. Another way to filter records is by specifying a range of values. For example, to retrieve Sales records for a particular period, the query criteria for the Sales Date might be : Between #01/01/2008# AND #03/31/2008# if constants are used. Alternatively, these values can be dynamically passed from TextBoxes on a Form, allowing the user to specify the date range interactively.

    In such cases, I prefer to create a small table—let’s call it a Parameter Table—with a single record and two fields: StartDate and EndDate. Then, I create a Datasheet Form for this table and embed it as a Sub-Form on the Main Form. This allows the user to conveniently enter the date range values directly into the table.

    This table is included in the main query, with the StartDate and EndDate fields placed in the Criteria row using the expression:

    Between [StartDate] AND [EndDate]

    It is important to note that the Parameter Table should contain only one record; otherwise, the main table’s results will be duplicated if the Parameter Table has multiple records. To prevent this, set the Allow Additions property of the Datasheet Form to No, so the user cannot inadvertently add more records.

    When the user clicks a button to generate the Report or other outputs based on this date range, the Parameter Sub-Form can be refreshed first to update the values in the table. After that, the query can be executed to reflect the latest StartDate and EndDate values.

  4. The above example retrieves all data between StartDate and EndDate. However, sometimes we need to filter specific, non-sequential values—for instance, Employee Codes 1, 5, 7, and 8. In such cases, we are forced to enter the codes manually in the Criteria row of the query, using one of several methods, as illustrated in the sample image below:

Query Parameter Input Methods.

I would like to share another method I use to let users select parameter values for reports—by simply checking boxes in a Parameter Table.

For example, assume that our company has several branch offices across the country, and management occasionally requests reports for selected branches. Since branch names remain constant, we can enable users to pick the required branches by placing check marks beside them. The check-marked entries can then serve as criteria for filtering data.

To illustrate this method more clearly (and to keep it simple), let’s use a list of months as an example. We will see how the selected months are used as criteria for the main query. The image below shows how this list of months appears to the user in a datasheet form, displayed as a subform on the main form.

We will need two queries for this process—one to filter the selected months from the list, and a second (the main query) that uses the results of the first query as parameters to filter data for the report.

The first query should return the values 3, 6, 9, and 12, based on the month selections shown in the image above. The following SQL statement can be used to achieve this result:

Query Name: Month_ParamQ

SELECT Month_Parameter.MTH
FROM Month_Parameter
WHERE (((Month_Parameter.[SELECT])=True));

When the user selects or deselects check marks on the parameter screen, these changes may not update in the underlying Month_Parameter table. To ensure the latest selections are reflected, we must refresh the Month_Parameter subform before opening the report that retrieves data from the main query (which uses the above query as its criteria).

To handle this, include the following statement in the On_Click() event procedure of the Print Preview command button:

Private Sub cmdPreview_Click()
     Me.Month_Parameter.Form.Refresh
     DoCmd.OpenReport "myNewReport", acViewPreview
End Sub

Now, how can the selected months filtered in the Month_ParamQ be used in the Main Query as a criterion? The third method we used earlier as a criterion in the first Image given above. I will repeat it below:

IN(1,5,7,8)

Here, we will compare the EmployeeID values with the numbers 1, 5, 7, 8, and select records that match any of these numbers as output.

Similarly, all we need to do here in the Main Query is to write this as a Sub_Query in the Criteria Row to use the Month Values from the Month_ParamQ. The above criteria clause, when written in the form of a sub-query, will look like the following:

IN(SELECT MTH FROM MONTH_PARAMQ)

The User doesn't have to type the Report Parameter values; they can select required items from a list, click a Button, and the Report is ready.

Share:

Form Menu Bars and Toolbars

Form Menu Bars and Toolbars.

During the development of a database, most of our time is spent creating tables, defining relationships, designing forms and reports, and planning process steps that transform raw data into meaningful reports, helping users make timely, informed decisions. Microsoft Access provides a wide range of menus and toolbars that make these design tasks relatively easy.

Once development nears completion, our focus shifts to security and usability—specifically, how users will interact with the database in their daily operations and what actions they should or should not perform. We want to prevent users from tampering with forms, reports, or other design elements and unintentionally disrupting the system.

By properly implementing Microsoft Access security features, we can control what each user or group is allowed to do. Additionally, removing the default menu bars and toolbars—and replacing them with custom menus and toolbars tailored to user needs—helps ensure a clean, intuitive interface for everyday use.

The Form/Report-Property Sheet.

When you open a Form or Report in Normal View, certain settings on the Form/Report's Properties influence the display of Menus or Toolbars associated with them. An image of the Form's Property Sheet is given below:

When you click the drop-down arrow in the Menu Bar property, a list of all custom menu bars you have created (or imported from another database) will appear. You can select the desired menu bar from this list to assign it to the form. Similarly, you can specify custom toolbars and shortcut menu bars in their respective property fields.

You can also enable or disable a form’s shortcut menu by setting its Shortcut Menu property to Yes or No, respectively.

When the form is opened in Normal View, the assigned custom menus and toolbars will automatically appear according to the property settings.

NB: You can go through the following Posts to learn more about designing Custom Menus and Toolbars:

Automating Menus/Toolbars Settings.

When your database contains numerous Forms and Reports, opening each one individually in Design View to set these properties manually can quickly drain the enthusiasm you had while building the database. Fortunately, this tedious task can be automated with a simple VBA routine.

This routine can scan the entire database in less than a minute, updating all Forms and Reports by assigning the specified Custom Menu Bar and Tool Bar names to their corresponding properties automatically.

Simply copy and paste the following VBA code into a global module in your database, and then save the module.

Public Function MenuToolbarSetup()
'-----------------------------------------------------------
'Author : a.p.r. pillai
'Date   : September, 1998
'URL    : www.msaccesstips.com
'All Rights Reserved by www.msaccesstips.com
'-----------------------------------------------------------
Dim ctr As Container, doc As Document
Dim docName As String, cdb As Database
Dim msg As String, msgbuttons As Long

On Error GoTo MenuToolbarSetup_Err

Set cdb = CurrentDb
Set ctr = cdb.Containers("Forms")
msgbuttons = vbDefaultButton2 + vbYesNo + vbQuestion
' Set MenuBar, toolbar properties of Forms
msg = "Custom Menus/Toobar Setup on Forms. " & vbCr & vbCr _& "Proceed...?"
If MsgBox(msg, msgbuttons, "MenuToolbarSetup()") = vbNo Then
   GoTo NextStep
End If
For Each doc In ctr.Documents
  docName = doc.Name  
'Open the Form in Design View and hidden mode   
DoCmd.OpenForm docName, acDesign, , , , acHidden
   With Forms(docName)
     .MenuBar = "MyMainMenu"
     .Toolbar = "MyMainToolBar"
     .ShortcutMenu = True
     .ShortcutMenuBar = "MyShortCut"
   End With  
'Save and Close the Form after change
   DoCmd.Close acForm, docName, acSaveYes
Next

NextStep:
'MenuBar,Toolbar properties of Reports
msg = "Custom Menus/Toobar Setup on Reports. " & vbCr & vbCr _& "Proceed...? "

If MsgBox(msg, msgbuttons, "MenuToolbarSetup()") = vbNo Then
   GoTo MenuToolbarSetup_Exit
End If

Set ctr = cdb.Containers("Reports")
'Reports cannot be opened in hidden mode
For Each doc In ctr.Documents
 docName = doc.Name
 DoCmd.OpenReport docName, acViewDesign
 Reports(docName).MenuBar = "MyMainMenu" 
Reports(docName).Toolbar = "MyReportToolBar" 
DoCmd.Close acReport, docName, acSaveYes
Next

msg = "Custom Menus/Toobar Setup Completed successfully. "

MsgBox msg

Set ctr = Nothing
Set cdb = Nothing

MenuToolbarSetup_Exit:
Exit Function

MenuToolbarSetup_Err:
MsgBox Err.Description
Resume MenuToolbarSetup_Exit
End Function

Run the Code from the Debug Window

Since this is purely a design-time task, you can execute the code directly by placing the cursor anywhere within the procedure and pressing the F5 key, or by calling it from the On_Click() event of a Command Button on a form.

However, remember that this form (the one containing the command button) will also be opened in Design View when the code runs to update property values. If you want to prevent that from happening, include an If...Then condition in your code to bypass this form.

Share:

Seriality Control Finding Missing Numbers

Seriality Control: Finding Missing Numbers.

In accounting and auditing, it is a standard practice to maintain strict control over the use of important documents such as checkbooks, receipt vouchers, payment vouchers, and local purchase orders. The usage of these documents is closely monitored to prevent misuse that could negatively impact the company’s operations or reputation.

These documents are usually printed in books containing 20, 50, or 100 sheets, each bearing sequential serial numbers. All transactions involving these documents are recorded along with their corresponding serial numbers.

Periodic audits are conducted to verify that the serial numbers recorded in the system match the continuity of used documents in hand. Any missing numbers, whether due to cancellation, loss, or other reasons, are investigated and properly documented.

To illustrate this process, we’ll create a sample program that identifies and lists missing serial numbers from recorded transactions. For this, we’ll need the following tables containing the necessary information:

Preparing for Trial Run

  1. Parameter Table: with Start-Number and End-Number values. Uses this number range to find the missing numbers from within the Transaction Table.

  2. Transaction Table: where the actual transaction details of the Documents are recorded, and our program should check and bring out the missing cases.

  3. Missing_List Table: where the missing list of Numbers will be created.

  4. Copy the following VBA Code and paste it into a new Global Module in your Database.

The VBA Code

Option Compare Database
Option Explicit

Type Rec
    lngNum As Long
    flag As Boolean
End Type

Public Function MissingNumbers()
'------------------------------------------------------
'Author : a.p.r. pillai
'Date   : 05/10/2008
'URL    : www.msaccesstips.com
'All Rights Reserved by www.msaccesstips.com
'------------------------------------------------------
Dim db As Database, rst1 As Recordset, rst2 As Recordset
Dim lngStart As Long, lngEnd As Long
Dim ChequeNo As Long, j As Long, ChqSeries() As Rec
Dim NumberOfChqs As Long, k As Integer, bank As String
Dim strSeries As String

On Error GoTo MissingNumbers_Err

'initialize the Report Table
DoCmd.SetWarnings False
DoCmd.RunSQL "DELETE Missing_List.* FROM Missing_List;"
DoCmd.SetWarnings True

Set db = CurrentDb
'Load Cheque Book Start and End Numbers
'from parameter table
Set rst1 = db.OpenRecordset("Parameter", dbOpenDynaset)
Do While Not rst1.EOF
    bank = rst1!bank
    lngStart = rst1!StartNumber
    lngEnd = rst1!EndNumber
' calculate number of cheques
    NumberOfChqs = lngEnd - lngStart + 1
    strSeries = "Range: " & lngStart & " To " & lngEnd

'redimention array to hold all the cheque Numbers
'between Start and End numbers
    ReDim ChqSeries(1 To NumberOfChqs) As Rec

'Generate All cheque Numbers between
'Start and End Numbers and load into Array
    k = 0
    For j = lngStart To lngEnd
        k = k + 1
        ChqSeries(k).lngNum = j
        ChqSeries(k).flag = False
    Next

'Open Cheque Payment Transaction Records
    Set rst2 = db.OpenRecordset("Transactions", dbOpenDynaset)

'Flag all matching cheque Numbers in Array
    k = 0
    rst2.MoveFirst
    Do While Not rst2.EOF
        ChequeNo = rst2![chqNo]
        If ChequeNo >= lngStart And ChequeNo <= lngEnd And rst2![bnkCode] = bank Then
            j = (ChequeNo - lngStart) + 1
            ChqSeries(j).flag = True
        End If
        rst2.MoveNext
    Loop
    rst2.Close

'create records for unmatched items in Report Table
    Set rst2 = db.OpenRecordset("Missing_List", dbOpenDynaset)
    k = 0
    For j = lngStart To lngEnd
        k = k + 1
        If ChqSeries(k).flag = False Then
            With rst2
                .AddNew
                !bnk = bank
                ![MISSING_NUMBER] = ChqSeries(k).lngNum
                ![REMARKS] = "** missing **"
                ![CHECKED_SERIES] = strSeries
                .Update
            End With
        End If
    Next
    rst2.Close

rst1.MoveNext
Loop
rst1.Close

Set rst1 = Nothing
Set rst2 = Nothing
Set db = Nothing

MissingNumbers_Exit:
Exit Function

MissingNumbers_Err:
MsgBox Err & " : " & Err.Description, , "MissingNumbers()"
Resume MissingNumbers_Exit
End Function

To try out the above program, create the first two tables with the same Field Names and data type as suggested by the sample data given above, and enter the same data or similar records of your choice, excluding some serial numbers from the range of values in the Parameter Table.

Create the third Table (Missing_List) with the same Field Names and data type of the sample records shown above, but without adding any records to it.

VBA Code Analysis

In the global area of the Module, we have created a User-Defined Data Type Rec with two elements, lngNum to hold the Serial Number and Flag to mark when a match is found in the Transaction Table, with Long Integer and Boolean data types, respectively. After creating the new data type in the Global area, we have declared an empty array variable ChqSeries() as Rec with the newly created data type within the Program.

The program opens the Parameter Table, starts with the first record, calculates the number of records that come within the given range, and re-dimensions the array to hold all the numbers between the lngStart and lngEnd parameter values.

In the next step, the program generates all the serial numbers between lngStart and lngEnd and fills the chqSeries().lngNum array. The Flag element value is set as False.

Next, open the Transaction Table and scan through it for matching Bank Code and for Cheque Numbers between lngStart and lngEnd, and when a match is found, the 'chqSeries().Flag' is marked as True for that entry within the array, and continues this process till the end of the file is reached.

In this process, if the 'chqSeries().Flag' is not marked as True, then the Serial Number corresponding to that entry is found missing in the Transaction Table. In the next step, we scan through the Array and check for entries with 'chqSeries().Flag' = False, and write them out in the Missing_List.

This process continues for all the records in the Parameter Table.

Note: This method is not the most efficient in terms of processing speed when handling a large volume of transactions. In such cases, it is advisable to filter the data in the Transaction table using a parameterized query and use the filtered dataset in place of the full Transaction table.

This needs extra steps in the program to create a Dynamic Query with SQL Statement just before the following statement: Set rst2 = db.OpenRecordset("Transactions", dbOpenDynaset), replacing Transactions with the Query name.

Share:

Wave Shaped Reminder Ticker

Wave-Shaped Reminder Ticker.

We have already seen how to create and install a Reminder Ticker that runs in a straight line on the Main Screen. We could do this with a few lines of VBA code and a Label control on the Main Screen. We will try something different this time. This ticker runs in a Zigzag form. An image of a sample run of this method is given below:

To create this Ticker, we need a series of labels arranged in a wavelike form, and each one must be named in a way that makes it easy to address them in code. A sample design is given below:


Automating the Label Creation.

There are about 42 identical labels to be created. Even if we create them manually once, arranging them in the required zigzag layout is a challenging task. However, this can be done efficiently with a small program. The program generates a new form, creates all 42 labels, arranges them in a zigzag pattern, assigns them sequential names, such as lbl1 through lbl42, and adjusts their other properties as described above.

  1. Copy the following Code into a Global Module of your Database and save it.
    Public Function ZIGZAG()
    '-----------------------------------------------------------
    'Author  :  a.p.r. pillai
    'Date    :  01/10/2008
    'URL     :  www.msaccesstips.com
    'All Rights Reserved by www.msaccesstips.com
    '-----------------------------------------------------------
    Dim frm As Form, ctrl As Label, t As Long, lngleft As Long
    Dim lngwidth As Long, lngheight As Long, lngtop As Long
    Dim j As Integer, k As Integer, h As Long, G As Long
    
    h = 30: G = 0: t = 0
    lngwidth = 0.1146 * 1440
    lngheight = 0.2083 * 1440
    lngtop = 1 * 1440
    lngleft = 0.16667 * 1440
    Set frm = CreateForm
    For j = 1 To 42
    Set ctrl = CreateControl(frm.Name, acLabel, acDetail, , , lngleft, lngtop, lngwidth, lngheight)
    lngleft = lngleft + lngwidth
    With ctrl
        .Name = "lbl" & j
        .FontName = "Tahoma"
        .FontSize = 8
        .Caption = ""
        .BackStyle = 0
        .ForeColor = 255
     End With
     Next
    G = 0
    For j = 1 To 3
         For k = 1 To 7
            G = G + 1
            Set ctrl = frm.Controls("lbl" & G)
            With ctrl
                .Top = .Top - (h * k)
            End With
            DoEvents
         Next
        t = frm.Controls("lbl" & G).Top
         For k = 1 To 7
             G = G + 1
            Set ctrl = frm.Controls("lbl" & G)
            With ctrl
                .Top = t + (h * 1)
            End With
            t = frm.Controls("lbl" & G).Top
            DoEvents
        Next
    Next
    End Function
    
  2. You can run the above Code directly by placing the cursor in the middle of the Code and pressing the F5 Key, or running from a Command Button's On Click Event Procedure or a Macro.

    Each time the code is executed, it creates a new form with the labels arranged in a zigzag pattern. Once you’ve created it, you can export this form to other projects where you want to install the ZigZag Ticker. Alternatively, you can place the code in a common library database and run it from your new project after attaching the library file to your project.

    Placement of the Ticker Labels.

  3. After creating the Labels, click outside the Labels and drag over them so that all the Labels are selected without disturbing the Labels' arrangement.

  4. Select Copy from the Edit Menu.

  5. Open the Main Switch Board (Control Form) in Design View and paste them.

  6. When all the labels are still in the selected state, drag and place the Labels into a position where you want the Ticker to appear on the Form.

    We have two more Sub-Routines, which are run from the Form_Load() and Form_Timer() Event Procedures. In the Form_Load() Event Procedure, we can create a Text Message in a String either with a constant value or with Field Values from a Table/Query that provides useful information to display to the User as a reminder. Refer to the earlier example Reminder Ticker Form, which uses data from within the Application as a reminder.

    Formatting Ticker Text.

    The Form_Timer() Event Procedure will control the Display of Label values, shifting one character at a time in succeeding labels, giving it a sense of motion.

  7. Copy and paste the following Sub-Routines into the Form Module where you have pasted the above labels.

    Option Compare Database
    Option Explicit
    Dim txt As Variant
    
    Private Sub Form_Load()
        txt = Space(42) & UCase("Excellence is not a matter of chance. It is a matter of Change. It is not a thing to be waited for. It is a thing to be achieved.")
        Me.Timerinterval=250
    End Sub
    
  8. See that the Dim txt As Variant is placed in the Global Area of the Module, which is referenced from the Form_Load() and Form_Timer() Event Procedures.

    Private Sub Form_Timer()
    Dim x As String, k As String, j As Integer, ctrl As Control
    
      x = Left(txt, 1)
      txt = Right(txt, Len(txt) - 1)
      txt = txt & x
      k = Left(txt, 42)
    For j = 1 To Len(k)
        Set ctrl = Me.Controls("lbl" & J)
        Ctrl.Caption = Mid(k, j, 1)
    Next
    End Sub 
    

    Disable Ticker on inactive Form.

  9. The following lines of code are useful if you plan to disable the ticker when the Main Form is inactive and run it when the Main Form is active again, so that other processes are not interrupted by the Ticker.

Private Sub Form_Deactivate()
   Me.TimerInterval = 0
End Sub

Private Sub Form_Activate()
   Me.TimerInterval = 250
End Sub

Download


Download Demo Database Access2007.zip



Download Demo Database Access2002-03.zip

Share:

No Data and Report Error

No Data and Report Error.

Report Source Query or Table can end up with no output records. In that case, some of the controls with the formula in the Report will show #Error. An image of a sample report is given below:

The #Error message at the top-right corner appears because the control contains a formula intended to display the reporting period. Similarly, the controls to the right of the word TOTAL—which display subtotals, totals, and detail line values—also show errors. This occurs when the report’s underlying query returns no records for the period selected by the user.

Although this is not a critical issue, it is considered poor practice to present or archive such a report with visible errors, especially if it needs to be shared as a NIL REPORT or retained for audit or future reference.

The modified version of the report shown below resolves this issue. It includes a clear comment, displays zero values in the summary controls, and correctly prints the reporting period.

I have made a few modifications to the Report Design to add a hidden label at the footer of the Report with the Caption: *** Congratulations *** Nothing Pending to show up when there are no output Records for the Report. The Visible Property of the label is set to No. In the Detail Section under the Description Column, it shows *** NIL REPORT ***. The period for which the Report is prepared is also shown to the right, above the Detail Section headings.

The Report Period (DateFrom and DateTo) is normally entered into a Parameter Table and joined with the Report Source Table in a Query to use them for criteria and for displaying on the Report.


Few Changes in the Report

Created two Text Controls (with the names From and To, respectively) at the Report Header Section to the right of the Control name STAFFNAME to load the DateFrom and DateTo Values from the Report Parameter Table with the DLookup() Function:

=DLookUp("DateFrom","Report_Param")

The second control has the expression to read DateTo from the Report_Param Table, and both values are used in the expression (=" Period: " & [frm] & " To " & [To]) to format the values to show the output as in the second image given above. These are all the cosmetic changes required in the Report.

Temporary Table for Report.

The major change is creating a temporary table with a single blank record, which should have the same structure as the Source Table or Query, and attaching it to the Report. If your Report is using a Table as Report Source Data, then make a copy of the structure of the Table and add a tmp_ prefix to the table name, like tmp_myReport. If it is a Query, then create a Make-Table Query using the Report Source Query and create a temporary table. Add a blank record in the temporary table. If your Report Table has a Text Field that is displaying the value on the Report, then type *** NIL REPORT *** in that field. Fill numeric fields with 0 and keep all other fields Empty.

How the Trick Works.

The trick is that when the Report is opened by the User, we will check whether the original Report Source Table or Query has any records in it or not. If not, swap the Temporary Table with the Report Source Table or Query. The hidden Label's Visible Property will be set to Yes to display the comment *** CONGRATULATIONS *** NOTHING PENDING. Since the temporary table has a single blank record, the Summary Controls will not show errors.

We need a few lines of VBA Code in the Report_Open() Event Procedure to check and swap the Report Source Table or Query.

A few lines of VBA Code.

Private Sub Report_Open(Cancel As Integer)
Dim i As Integer
i = DCount("*", "myReport")
If i = 0 Then
   Me.RecordSource = "tmp_MyReport"
   Me.lblMsg.Visible = True
End If
End Sub

Copy and paste the above code in the Report's VBA Module and make changes to insert the correct Table/Query and tmp_myReport names.

Share:

Lost Links of External Tables

Lost Links of External Tables.

We have already learned several methods to work with external data sources. Linking them to an MS Access database or directly opening them in Queries by setting Source Database and SourceConnectStr Properties. In either case, the Source Data must be present in its original location at all times.

However, there is always a possibility that the links to some of these tables may be lost—for instance, if a source table is accidentally deleted or renamed. Such issues typically go unnoticed until we attempt to work with the linked tables, often resulting in errors, which may appear unexpectedly.

To alleviate this problem, run a check on the linked tables as soon as the Database is open for normal operations. If any of the linked Tables are not in place, then warn the User about it and shut down the Application if it has serious implications.

How do we determine whether a linked external table has lost its connection with the Database or not? It is easy to attempt to open the linked table, and if it is in error, you can be sure the table link is missing. 

There may be several tables in a database, local tables or linked ones. How can we single out the linked ones alone and open them to check the status? Again, this is not a serious issue, and you already have the answer if you have gone through the earlier Articles explaining several methods of accessing external data and the usage of Connection Properties of Linked Tables and Queries.

The Connection Property Value.

We need a small VBA routine to iterate through the Table definitions and check the Connect Property value, and if it is set with a Connect String, then it is a linked table; otherwise, it is a local table. When we encounter a linked table, we will attempt to open it to read data. If this process triggers an Error, then we will prepare a list of such cases and display it at the end to inform the User so that she can initiate appropriate remedial action to rectify the error.

A sample VBA routine is given below. Copy and paste the program into a Global Module and save it.

Public Function LostLinks()
'----------------------------------------------------
'Author : a.p.r. pillai
'URL    : www.msaccesstips.com
'Date   : 21/09/2008
'----------------------------------------------------
Dim msg As String, tbldef As TableDef
Dim strConnect As String, cdb As Database
Dim rst As Recordset, strTableName As String
Dim strDatabase As String, loc As Integer
Dim loc2 As Integer

On Error Resume Next

Set cdb = CurrentDb
For Each tbldef In cdb.TableDefs
    strConnect = tbldef.Connect

    If Len(strConnect) > 0 Then
       strTableName = tbldef.NAME
       Set rst = cdb.OpenRecordset(strTableName, dbOpenDynaset)
       If Err > 0 Then
          If Len(msg) = 0 Then
             msg = "The following Linked Tables are missing:" & vbCr & vbCr
          End If
          msg = msg & strTableName & vbCr
          Err.Clear
        End If
        rst.Close
    End If
Next

If Len(msg) > 0 Then
    MsgBox msg, , "LostLinks()"
End If

End Function

Call the Routine from an Autoexec Macro or from the Form_Load() Event Procedure of the Application's Startup or Main Screen.

Earlier Post Link References:

Share:

Link External Tables with VBA

Link External Tables with VBA.

We all know how to link to a table from external data sources manually.

  1. Highlight Get External Data from the File Menu.

  2. Select Link Tables from the displayed options.

  3. Select the file type: dBase, Excel, etc., in the Files of Type control.

  4. Track the location of the file and select it.

  5. Click the link to attach the selected table to the Current Database.

If you are linking an external table from a Network Location, use the UNC (Universal Naming Conventions) type location reference (like \\hosfs03\accounts\myDatabase\...), rather than using a mapped drive location reference like H:\MyDatabase. 

You can even use your Local Drive's share name in this manner. 

\\yourPCName\C$\Databases\myDatabase.mdb

This method ensures that even if the drive mapping changes—for example, from H:\ to K:\ or any other letter—MS Access can still locate the linked tables without issues. Otherwise, you would need to manually update the table locations using Tools → Database Utilities → Linked Table Manager to refresh the changed path references.

We have already seen that we can work with external tables without linking them permanently to the current database.

Here, we will link external Tables to the Current Database using VBA. After linking the table, we will print the contents of five records into the Debug Window and delete the link.

The Steps to follow.

We will go through the following steps to link a Table to a Database with VBA:

  1. Create a temporary Table Definition (Tabledef) without any Field Definitions in the Current Database.

  2. Load the Connect Property of tabledef. with a connection string value

  3. Link the external Table to the temporary Table definition (Tabledef)

  4. Add the temporary Table definition to the Tabledefs Group.

  5. Rename the temporary Table to match the Source Table Name.

The VBA Functions.

We will write two VBA Functions for our examples. Copy and paste the following VBA code into a Global Module of your MS Access Database and save it:

Public Function LinkMain()
Dim strConnection As String
Dim sourceTable As String

strConnection = ";DATABASE=D:\Program Files\Microsoft office\Office\Samples\Northwind.mdb;TABLE=Orders"

sourceTable = "Orders" 'Access Table Name

LinkExternal strConnection, sourceTable

End Function
Function LinkExternal(ByVal conString As String, sourceTable As String)
Dim db As Database, i As Integer, j As Integer
Dim linktbldef As TableDef, rst As Recordset

Set db = CurrentDb
Set linktbldef = db.CreateTableDef("tmptable") 'create temporary table definition

linktbldef.Connect = conString 'set the connection string
linktbldef.SourceTableName = sourceTable 'attach the source table
db.TableDefs.Append linktbldef 'add the table definition to the group
db.TableDefs.Refresh 'refresh the tabledefinitions

linktbldef.NAME = sourceTable 'rename the tmptable to original source table name

'open the recordset and print 5 records in the debug window
Set rst = db.OpenRecordset(sourceTable, dbOpenDynaset)
i = 0
Do While i < 5 And Not rst.EOF
  For j = 0 To rst.Fields.Count - 1
     Debug.Print rst.Fields(j).Value,
  Next: Debug.Print
  rst.MoveNext
  i = i + 1
Loop
rst.Close

db.TableDefs.Delete sourceTable 'remove to stay the table linked
db.Close
Set rst = Nothing
Set linktbldef = Nothing
Set db = Nothing

End Function

How it works.

The first program, LinkMain(), calls the LinkExternal() Subroutine with strConnection and SourceTable name as parameters. Northwind.mdb sample database and Orders Table are passed as parameters. Open the Debug Window (Immediate Window) by pressing Ctrl+G. Click anywhere within the LinkMain() program and press F5 to run the code and print five records of the Orders table from the Northwind.mdb database.

The LinkExternal() Program performs the five steps of action explained above.

Replace the strConnection and sourceTable with the following sample values for opening a dBase Table:

strConnection = "dBase IV;HDR=NO;IMEX=2;DATABASE=D:\msaccesstips" sourceTable = "Branches" 'Access Table Name

Tip: If you don't have a dBase Table to try the Code, then export a Table from MS Access into the dBase format and run the Code with changes.

Change the Database Folder name and the Table name with your own dBase Folder and Table names.

For Excel-based Tables, two methods are given below.

  1. Use Worksheet Reference (Sheet1$) as the source Table location. The $ symbol is necessary with the Worksheet name:

    strConnection = "Excel 5.0;HDR=YES;IMEX=2;DATABASE=D:\msaccesstips\Branch.xls" sourceTable = "Sheet1$" 'Excel Sheet Name Reference
    

    The topmost row contents of the table area will be used as Field Names.

    strConnection = "Excel 5.0;HDR=YES;IMEX=2;DATABASE=D:\msaccesstips\Branch.xls" sourceTable = "BranchNames" 'Excel Range Name Reference
    
  2. Excel Range Name, Branch Names will be used as the Table location. The first line is the same as above for this example, also.

Earlier Post Link References:

Share:

Source Connect Str Property and ODBC

'Source Connect Str' Property and ODBC.

We have already seen that the SourceConnectStr property, when used together with the 'Source Database' property in an MS Access Query, allows us to open and work directly with external data sources such as dBase, FoxPro (Versions 2.5 or 3.0), and Excel tables.

We also learned how to include these property specifications within an 'IN clause' directly in the SQL statement of a query.

However, for data sources such as AS/400 (iSeries), SQL Server, and FoxPro (via newer database engines), Access requires an ODBC (Open Database Connectivity) connection string. This connection string defines how Access communicates with these external systems, specifying the driver, data source name, authentication credentials, and other parameters needed to establish the connection.

ODBC Connection String.

The best way to learn and understand more about the Connection String Syntax of different ODBC Data Sources is to go through the following steps and look at the Connection String of the Linked Table:

  1. Create an ODBC DSN (Data Source Name). Refer to the Post Linking with IBM AS400 Tables.

  2. Link the Table from the source directly using File -> Get External Data -> Link Table.

  3. Select ODBC Databases in the Files of Type control.

  4. Select the ODBC DSN that you have created from the displayed list.

  5. Click OK. If you have not created a DSN, you can create a new one by selecting the New... Command Button.

  6. Select the Table to link with your MS-Access Database.

  7. After linking the Table, select the linked Table.

  8. Select Design from the Database Menu. You will receive a warning message saying that the Linked Table Structure cannot be modified. Click Yes to the Prompt: Do you want to open it anyway?

  9. Display the Property Sheet (View ->Properties).

Description Property of Table.

On the Description Property of the Table Structure, you will find the ODBC String that can be used directly on the Query's SourceConnectStr Property.

A few examples of ODBC Connection String Values are given below:

AS400 (iSeries) Table:
  • ODBC;DSN=myData;UID=UserID;PWD=Password;TABLE=PAVUP.APC161D
SQL Server:
  • ODBC;DSN=MyData;UID=UserID;PWD=Password;DATABASE=Parts
FoxPro:
  • ODBC;DSN=Visual FoxPro Tables;UID=;PWD=;SourceDB=C:\MyFoxpro;SourceType=DBF;Exclusive=No;BackgroundFetch=Yes;Collate=GENERAL;Null=Yes;Deleted=Yes

As shown in the examples above, the DSN Name, User ID, Password, and other parameters in the ODBC connection string are specific to each data source and must be entered accurately to establish a valid connection to their respective tables.

In the case of the AS400 (iSeries) ODBC connection string, the table name and library (or folder) name are separated by a dot (.), following the convention used in IBM systems, for example, `MYLIB.MYTABLE`.

For more details on setting up such connections, you can refer to the earlier discussion titled “Linking with IBM AS400 Tables”, which explains how to properly link AS400 (iSeries) tables to a Microsoft Access database using ODBC drivers and connection parameters.

Earlier Post Link References:

Share:

PRESENTATION: ACCESS USER GROUPS (EUROPE)

Translate

PageRank

Post Feed


Search

Popular Posts

Blog Archive

Powered by Blogger.

Labels

Forms Functions How Tos MS-Access Security Reports msaccess forms Animations msaccess animation Utilities msaccess controls Access and Internet MS-Access Scurity MS-Access and Internet External Links Queries Array Class Module msaccess reports Accesstips msaccess tips WithEvents Downloads Objects Menus and Toolbars MsaccessLinks Process Controls Art Work Collection Object Property msaccess How Tos Combo Boxes ListView Control Query VBA msaccessQuery Calculation Dictionary Object Event Graph Charts ImageList Control List Boxes TreeView Control Command Buttons Controls Data Emails and Alerts Form Custom Functions Custom Wizards DOS Commands Data Type Key Object Reference ms-access functions msaccess functions msaccess graphs msaccess reporttricks Command Button Report msaccess menus msaccessprocess security advanced Access Security Add Auto-Number Field Type Form Instances ImageList Item Macros Menus Nodes Recordset Top Values Variables msaccess email progressmeter Access2007 Copy Excel Expression Fields Join Methods Microsoft Numbering System RaiseEvent Records Security Split SubForm Table Tables Time Difference Utility WScript Workgroup Wrapper Classes database function msaccess wizards tutorial Access Emails and Alerts Access Fields Access How Tos Access Mail Merge Access2003 Accounting Year Action Animation Attachment Binary Numbers Bookmarks Budgeting ChDir Color Palette Common Controls Conditional Formatting Data Filtering Database Records Defining Pages Desktop Shortcuts Diagram Disk Dynamic Lookup Error Handler Export External Filter Formatting Groups Hexadecimal Numbers Import Labels List Logo Macro Mail Merge Main Form Memo Message Box Monitoring Octal Numbers Operating System Paste Primary-Key Product Rank Reading Remove Rich Text Sequence SetFocus Summary Tab-Page Union Query User Users Water-Mark Word automatically commands hyperlinks iSeries Date iif ms-access msaccess msaccess alerts pdf files reference restore switch text toolbar updating upload vba code