Dec 10

One of the most fundamental programming tasks is writing code that interacts with a database. This week I’ll demonstrate a basic method of retrieving data from an Access database and displaying it on an Excel worksheet. Being a programming column, of course, I’ll show how to do this programmatically. That said, if you haven’t already done so, be sure to look into Microsoft Query for bringing data into Microsoft Excel using the user interface.

Any time you interact with a database, you’ll need to use a special application programming interface (API) that allows you to communicate with the database. For most purposes, you can use one of the versions of ActiveX Data Objects, commonly referred to as ADO.

Prior to using ADO in your project however, you need to add a reference to it in the VBE. To do this, select Tools/References from the Visual Basic Editor menu and check the checkbox next to the latest version of ADO on your computer as illustrated in Figure 1.


n46eopfigure1.jpg
Figure 1 - Adding a reference to Microsoft ActiveX Data Objects
Click to enlarge

Listing 1 shows how to connect to the Northwind sample database, execute a query, and copy the results on to a worksheet. Enter the following listing in the code module associated with Sheet1 (you could also insert a new module and enter it there).

Listing 1: This listing demonstrates basic data retrieval from an Access database.

'NOTE: Set a reference in the VBE to the latest version of
' the Microsoft ActiveX Data Objects Library on your computer.

Sub DatabaseExample()
Dim rst As ADODB.Recordset
Dim sConnection As String
Dim sSQL As String
Dim rg As Range

On Error GoTo ErrHandler

' This is the range that will receive the data.

Set rg = ThisWorkbook.Worksheets(1).Range("a1")

' The database connection string. Double-check the path
' to the Northwind database on your computer.

sConnection = "Provider=Microsoft.Jet.OLEDB.4.0;" & _
"Data Source=C:\Program Files\Microsoft Office\" & _
"OFFICE11\SAMPLES\northwind.mdb"

' The query to execute

sSQL = "SELECT LastName, FirstName, Title FROM employees"

' Create & Open the recordset

Set rst = New ADODB.Recordset
rst.Open sSQL, sConnection

' Copy to the range

rg.CopyFromRecordset rst
rg.CurrentRegion.Columns.AutoFit

' Close the recordset when you're done with it.

rst.Close

ExitPoint:
Set rst = Nothing
Set rg = Nothing
Exit Sub

ErrHandler:
MsgBox "Sorry, an error occured. " & Err.Description, vbOKOnly

' resume at the ExitPoint label to clean up object variables
Resume ExitPoint
End Sub

Running listing 1 produces the results shown in Figure 2.


n46eopfigure2.jpg
Figure 2: Success! - Click to enlarge

Querying a database programmatically using ADO generally involves four tasks. First, you need to define a connection string. A connection string is simply the details required by ADO to locate and connect to the desired database. While I’ve just defined a connection string in this example, it is also common to use the ADO Connection object to handle the details associated with connecting to a database.

Second, you need to build the SQL statement that will return the data that you’re interested in. ADO has a Command object that you can use to do this — or you could just define a SQL statement and store it in a string as I have in the example.

Third, you need to use the ADO Recordset object to execute the query (or command) against the connection and store the results.

Finally, once you have the results in a Recordset, you need to display them somehow. Using the CopyFromRecordset method associated with a Range object is the easiest way to go about this. In a future column I’ll demonstrate a technique that is a little more flexible.

Dec 10

While my earlier tips have used Excel examples, the name of this column is NOT Essential Excel Programming, so I suppose it is time to take a look at some useful tips related to other Office applications. One of the most useful properties in the Word object model is the Information property of the Selection object.

The Selection object represents the current selection in a window or pane. It is useful when you are writing macros that interact with the user.

When programming in Word, you frequently need to know things such as which page the user is working on, the number of pages in the document, the line number of the insertion point, the column number of the insertion point, and on and on.

Surprisingly, you only need to know one property to determine the answer to these questions and many more:

Selection.Information(wdInformation)

where wdInformation represents the information you want returned.

The Information property is unique, unlike other properties I have discussed in this column. The Information property is a parameterized property. Generally properties do not use parameters. The Information property, however, accepts a wdInformation enumeration consisting of 35 options, shown in Figure 1.


n44eopfigure1.jpg
Figure 1: Browsing Members of the wdInformation enumeration

As shown in Listing 1, using the Information property is very easy.

Listing 1: A simple demonstration of the Selection.Information property

Sub SelectionInformation()
Debug.Print "Selection starts at column: " & _
Selection.Information(wdFirstCharacterColumnNumber)

Debug.Print "Selection starts at line: " & _
Selection.Information(wdFirstCharacterLineNumber)

Debug.Print "The selection starts on page " & _
Selection.Information(wdActiveEndPageNumber) _
& ". There are " & _
Selection.Information(wdNumberOfPagesInDocument) & _
" page(s) in the document."
End Sub

To test this listing, enter the text =Rand(12,12) and press Enter to easily add a bunch to test data into a document. Then, experiment with the listing by placing the cursor at different locations in the document and running the macro. A sample of the output is shown below.

Listing 2: Example output from the SelectionInformation procedure

Selection starts at column: 11
Selection starts at line: 8
The selection starts on page 2. There are 5 page(s) in the document.

For details regarding the types of operations you can perform with Selection.Information and the correct enumeration value for each operation, visit

http://msdn2.microsoft.com/en-us/library/bb213848.aspx

Dec 10

One time-consuming, error-prone technique commonly repeated is scaling numbers in Excel using formulas rather than number formats. For example, to scale a number by thousands, people use formulas such as =A1*.001 or =A1/1000. The problem with this practice is that it is time-consuming to setup or modify and it tends to be error-prone if the cell is involved in a chain of calculations.

A much simpler approach is to use custom number formats. For example, it is possible to display a number scaled by thousands using this number format:

#,##0,

Using custom number formats does not change the underlying value, and the formats are trivial to modify.

To take this a step further, it is relatively simple to add a scaling feature using a combination of named ranges, a simple drop-down list using the data validation feature, and a pinch of VBA. As an example, consider the worksheet shown in Figure 1. (NOTE: Although these images show Excel 2007, the same results can be achieved using Excel 2003.)

n43eopfigure1.jpg
Figure 1: Step 1 - Name the range of numbers that should be scaled.

The first step is to create a named range consisting of all the cells that should be scaled. In Figure 1, I’ve named the range Scale_Range.


n43eopfigure2.jpg
Figure 2: Step 2 - Create a named range for the cell that specifies the scaling factor.

The next step is to create a named range for the cell that contains the scaling factor options. In Figure 2, I’ve named this Scale_Factor. Additionally, I’ve added data validation to this cell to provide the list of options and the drop-down functionality.

The final step is to add the code shown in Listing 1 to the Sheet1 object in the Visual Basic Editor.

Listing 1: A method for providing scaling capabilities

Private Sub Worksheet_Change(ByVal Target As Range)
If Target = Me.Range("Scale_Factor") Then
ScaleNumbers
End If
End Sub

Private Sub ScaleNumbers()
Select Case Me.Range("Scale_Factor").Value
Case Is = "(in $)"
Me.Range("Scale_Range").NumberFormat = "#,##0"
Case Is = "(in thousands of $)"
Me.Range("Scale_Range").NumberFormat = "#,##0,"
Case Is = "(in millions of $)"
Me.Range("Scale_Range").NumberFormat = "#,##0,,"
Case Else
Me.Range("Scale_Range").NumberFormat = "#,##0"
End Select
End Sub

The Worksheet_Change procedure is an event handler that Excel calls automatically anytime something changes on Sheet1. The Target parameter represents the range object (i.e., cell) that changed. If the cell that changed is the cell containing the scaling choices, the ScaleNumbers procedure is called.

The ScaleNumbers procedure examines the value of the Scale_Factor named range and — depending on the desired scaling — uses the NumberFormat property to set a custom number format.

As you can see in Figure 3, the number appears scaled on the worksheet but the underlying value of the cell is unchanged.


n43eopfigure3.jpg
Figure 3

Dec 10

Last week I discussed simple error handling techniques such as no error handling (the default experience), ignoring errors, and quit (gracefully) the first time an error occurs. This week I’m going to explain a more flexible technique that allows different actions to be taken depending on the type of error that occurs.

The key to this technique is using the Err object to gather information about the type of error that occurred and then take an appropriate action. Nearly every type of error that can occur at runtime is assigned a number and description and this information is surfaced through the Err object. The Err object is an object that is automatically created by VBA whenever an error occurs.

As an example, consider Listing 1 below.

Listing 1: Flexible Error Handling

Sub FixErrorStrategy()
Dim x As Integer
Dim z As Integer

' If an error occurs, resume execution
' with the statement following the
' ErrHandler label.
On Error GoTo ErrHandler

z = 0

For x = 1 To 10
Debug.Print x / z
Next

' This line will only be reached
' if no errors occur or if
' recoverable errors occur.
MsgBox "Finished"
Exit Sub

ErrHandler:
If Err.Number = 11 Then
MsgBox "Division by zero error. " & _
"Changing denominator to 1."
z = 1
' Resume execution with the statement that
' caused the error.
Resume
Else
MsgBox "Unrecoverable Error: " & _
Err.Number & " - " & Err.Description
End If
End Sub

Listing 1 loops through the numbers 1 through 10 and divides each number by the number stored in variable z (which is mistakenly set to 0 initially). When an error occurs, execution proceeds to the block of code after the ErrHandler label. This code examines the Err.Number and takes action if the error is error number 11 -– the error number associated with division by zero. For all other errors, the procedure will display information about the error and then exit the procedure.

The ErrHandler code block uses the Resume statement after fixing the condition that caused the error to resume execution at the statement that initially caused the error. When the division by zero error occurs, the procedure sets the value of z to 1, then the Resume statement causes execution to resume at the Debug.Print x/z statement. From that point on, the procedure runs as expected and produces the following output in the Immediate window.

1
2
3
4
5
6
7
8
9
10

This error handling pattern is very typical of VBA procedures that require anything more than the most elementary error handling. Stay tuned next week for a great tip on scaling Excel worksheet numbers programmatically.

Dec 10

The focus of this week’s column is error handling. Learning basic error handling is essential knowledge for writing more robust VBA code. I’m going to demonstrate two simple strategies. These strategies assume that you are using the default Break on Unhandled Errors error trapping option. Check the setting for this option by selecting Tools/Options in the Visual Basic Editor. This option is located on the General tab.


n41eopfigure1.jpg
Figure 1: The default runtime error experience

Before moving into basic error handling strategies, I’ll demonstrate the default strategy — no error handling. If there is no error handling present, VBA will display a runtime error when an error occurs and present the option to either End the macro or Debug it. Listing 1 shows an example listing that will generate a division by zero error.

Listing 1: A procedure that does not use error handling.

Sub NoErrorHandlingStratgy()
Dim x As Integer

For x = 1 To 10
Debug.Print x / 0
Next

' This line will only be reached if no errors occur.
MsgBox "Finished - without errors"
End Sub

Running Listing 1 produces the dialog box shown in Figure 1.

The error-handling strategy is simply to ignore any errors and continue executing code. As an example, consider Listing 2. This listing attempts to do the same thing as Listing 1 except it includes the statement On Error Resume Next near the beginning of the procedure.

Listing 2: A procedure that ignores all errors

Sub IgnoreEverythingStratgy()
Dim x As Integer

' If an error occurs, ignore it
' and continue executing with the
' next statement.
On Error Resume Next

For x = 1 To 10
Debug.Print x / 0
Next

' This line will be reached no matter what.
MsgBox "Finished - with or without errors"
End Sub

This strategy is helpful when you have procedures in which the individual statements don’t rely on previous statements. In other words, if one line doesn’t execute due to an error, there are no adverse affects to the remaining statements.

If there a procedure is performing more critical operations and there is a dependency between the various statements in a procedure, it might be better to gracefully quit or exit the procedure the first time that an error occurs. At first, this might sound exactly like what you get by not using any error handling. However, there are critical differences.

First, you get to control the user experience rather than the VBA execution engine. For example, you might not want to give the user the ability to debug your code. Or you might choose to not even notify the user that an error occurred. Finally, if you have other procedures that need to run after the procedure that caused the error, these other procedures can still run. Without error handling, all execution stops and any following procedures will not be run.

To use this strategy, use the On Error GoTo ErrHandler statement. ErrHandler is actually a line label - you can use a different label if you prefer. Listing 3 shows an example of this strategy.

Listing 3: A procedure that uses an error handler

Sub QuitOnFirstErrorStrategy()
Dim x As Integer

' If an error occurs, resume execution
' with the statement following the
' ErrHandler label.
On Error GoTo ErrHandler

For x = 1 To 10
Debug.Print x / 0
Next

' This line will only be reached
' if no errors occur.
MsgBox "Finished without errors"

' Use Exit Sub here to prevent
' running into the error handling code.
Exit Sub

' The following code only runs if an
' error occurs.
ErrHandler:
MsgBox "Did not finish - error: " & _
Err.Description
End Sub

These two basic strategies can significantly improve your ability to write more user friendly and robust code. Next week I’ll demonstrate additional error handling strategies that provide even more flexibility to selectively handle some errors but not others.