Sometimes one little customer lookup turns into a whole pile of values you need in your code: address, city, state, ZIP code, country, phone number, whatever. You can certainly run a separate DLookup for every field, and for a quick one-time job that is perfectly fine. But once you need the same lookup in several forms or procedures, copying that logic everywhere gets messy fast.
A handy VBA pattern is to let a Function return one clear success or failure value, while using ByRef parameters to send several related values back to the procedure that called it. Think of the Function as requesting a customer information packet. The Boolean result tells you whether the packet arrived, and the ByRef variables contain the address pieces inside it.
A VBA Function technically has one normal return value. In this situation, that return value is best used as a Boolean. Your Function can return True when it successfully finds the customer and fills in the requested information, or False if the customer cannot be found.
The additional values are passed into the Function using parameters declared ByRef. ByRef means "by reference." Instead of giving the Function a copy of a variable, you give it access to the actual variable from the calling procedure. If the Function changes that variable, the calling code sees the changed value as soon as the Function is finished.
So conceptually, you might have a Function named GetCustomerInfo. It receives a CustomerID and several String variables for Address, City, State, ZIP, and Country. The Function looks up the customer record, fills those variables, and returns True if it found a matching record.
That means the calling code can do something like this: declare a few local String variables, call the Function, test the Boolean result, and then place the returned values into the controls on the form. You get one clean call instead of five separate DLookups scattered around the database like sticky notes on the coffee maker.
The lookup routine itself should start by clearing the output variables and setting its own return value to False. This is important. If the lookup fails, you do not want old values hanging around in those variables and making it look like the lookup worked when it did not. Nothing like showing the last customer's address on a new order to make everybody have a bad day.
Inside the Function, a DAO recordset is a good choice when you need several fields from the same record. Open a recordset for the selected CustomerID, check whether it contains a record, and then copy the address fields into the ByRef output variables. Use Nz when appropriate so Null values from the table do not cause problems when assigning text values.
If a record is found, set the Function result to True. If no record is found, leave the output strings blank and return False. The calling procedure can then decide what to do. On an Order form, you might populate the shipping address fields when the lookup succeeds, or display a "Customer not found" message if it fails.
One little detail worth mentioning: do not name your local variables exactly the same as the controls or fields on your form if it makes the code confusing. Instead of using names like Address and City for everything, use something like AddressString, CityString, StateString, and ZipString for the local variables. Then it is obvious when you are working with a variable versus a form control.
This approach is especially useful when the same customer information is needed in more than one place. Maybe you need it on an Order form, a Quote form, a shipping label routine, and a button that creates a packing slip. Put the lookup logic in one public Function in a standard module, and every part of the database can use the same routine.
If the Customer table changes later, you update one Function instead of trying to remember where you copied and pasted those five DLookups three years ago. Future-you will appreciate it. Present-you might not remember writing it, but future-you will still appreciate it.
Are multiple DLookups wrong? Nope. Not at all. If you have a simple one-off task and only need a field or two, DLookup is often quick and easy. The ByRef pattern is not about declaring war on DLookup. It is about keeping related work together when the same lookup is used repeatedly.
You could also use global variables or TempVars to share information across your Access application. Those have their place, especially when values truly need to be available across forms, reports, macros, and procedures. But they are shared state, which means another procedure can change them, overwrite them, or leave old values behind.
ByRef output parameters are more self-contained. The inputs, outputs, and success test are all visible right in the Function call. That makes the code easier to read, easier to debug, and less likely to turn into a mystery later on.
The basic pattern is simple: use the Function's direct return value for success or failure, use ByRef parameters for the related data, initialize everything safely, and keep the lookup logic in one reusable place. It is a useful little VBA Lego to keep in your toolbox.
The embedded video includes the full Access walkthrough, including building the recordset lookup, creating the public Function, and calling it from an Order form.
Live long and prosper,
RR