Вы находитесь на странице: 1из 12

Smart Access

Solutions for Microsoft Access


TM
Developers
1 Data Modeling for the Access
Newcomer, Part 1
Glenn Lloyd
5 Simplifying Queries
Russell Sinclair
8 Access Answers:
All in the Family
Doug Steele
12 November 2005 Downloads
November 2005
Volume 13, Number 11
Accompanying files available online at
www.pinnaclepublishing.com
Applies to
Access 2000
Applies to
Access 95
Applies to
Access 97
2000 2000 2002 2002
Applies to
Access 2002
Applies to
Access 2003
2003 2003
Data Modeling
for the Access
Newcomer, Part 1
Glenn Lloyd
Thorough, thoughtful, and accurate data modeling should be the starting point of
detailed database design. But a surprising number of developers have little or no
understanding of data modeling and shy away from what sounds like a non-
profitable and time-consuming task. Glenn Lloyd looks at the typical design pitfalls
that trap Access beginners and shows the basic techniques that ensure success.
M
Y call came from a community service organization that provides
specialized services both locally and remotely across the vast expanse
of Northern Ontario. They were unable to solve a problem with their
Access databasea problem that was both embarrassing and damaging to
their relations with their membership (and to the community at large). Several
months earlier, one of their key members had died. Now, despite their best
efforts, his widow and the other organizations with which he had been
associated were still receiving their periodic mailings addressed to him. As I
listened to the story, I realized that the problem indicated a faulty database
design. The staff that had developed the database had no training or
understanding of relational design principles and rationale. What they had
known was how they wanted to see their data laid out in reports and had
designed a data structure that matched those report layouts.
What made the problem embarrassing was who had died. The
organizations membership is derived from local community organizations
located in various centers across Northern Ontario. Board members can
represent one or more of these community organizations. The member
whose death triggered the problem was a prominent member of one of the
communities and either officially or unofficially represented a number of the
constituent organizations. This was a very visible mistake.
The original sin
One of the goals of the original database was to have targeted mailing lists so
2000 2000 2002 2002 2003 2003
2 www.pinnaclepublishing.com Smart Access November 2005
that mailings could be restricted to specific individuals
or groups according to the purpose of the mailing.
The original database designers concluded that they
could best accomplish this objective by subdividing
membership data into several tables, one for each of
the eight or nine categories that best described the
community organizations the members served. This
meant that some individuals, including the recently
deceased member, had multiple entries, one in each of
the relevant membership tables. The member whose
information had brought the problem to light had at least
four or five of these entries.
When all is said and done, a database is nothing
more or less than a model of the real world. So, before
an efficient relational database can be designed and
implemented, the developer or developers must have an
in-depth understanding of the nature of the real world
that the database will model. A membership database,
for example, doesnt contain real people. A database
contains information that may be about real people.
That information describes who the real people are, where
they live, the membership category to which they belong,
special skill sets they bring to the group, and any other
information the owners of the database need to retain
about their members.
Data modeling refers to the first step of detailed
database design. Data modeling is the basis of the
ultimate table and relationship design that, when
implemented, becomes the databases structure. Data
modelings purpose is to translate the real-world
requirements (described either formally or informally)
into a formal data structure.
The process of data modeling is as vital to database
design and implementation as the structure thats
produced because the process requires you to study the
organization and the information you want the database
to track. As a result, when you follow the process you
come to know your data very well and move from mere
assumption and speculation to in-depth knowledge of the
organization and its information needs. Along the way,
you define your database structure.
While you may have already worked out an intuitive
solution to the problem, dont pat yourself on the back.
Without a process, you cant guarantee that you would
have spotted the problem before it became a problem.
And you dont know that youll do as well with every
other problem that you face. Data modeling can be done a
number of different ways, but Ill walk you through a
simple three-step data modeling process. By the end of
this article youll see how this process would have led,
inevitably, to a practical solution to my clients problem.
Step 1: Make a list
In this first step, its best to step back from any thoughts
about how youll eventually organize the data. Your first
step is do a simple brain dump of everything the
database is required to track. A formal or informal
requirements analysis is the best guide for this step.
For example, assume that the database in question is
a membership database. The company requesting the
database (client, boss, or whoever has determined that the
database should be developed) has set out several basic
pieces of information they want to know about members.
Name and address are obvious, of course. In addition,
however, they also want to have some indication of
special skills or capabilities the member has to offer the
organization, whether the member is a director or
executive board member, and the organization or
organizations that the member represents along with the
category to which the external organization belongs.
Step 2: Separate subjects and descriptions
The database requirements analysis provides only a guide
of what the database is required to track. Those items
arent all of the same kind:
Some of the items in the list are distinct types of
subjects for the database.
Others describe or classify the subjects.
Typically, your initial brain dump will generate a
similar mixture of subjects and descriptions. The goal of
the second step is to clearly identify and separate subjects
from their descriptions.
The technical RDBMS name for the subjects is
entity. Of course, the database would be a rather limited
tool if all it maintained was a list of entities. In fact, the
real purpose of the database is to organize and store bits
and pieces of descriptive information (called attributes)
about the databases entities. Entities correspond to tables
in the ultimate database; attributes correspond to the
tables fields. Category or classification information
presents a special case because categories themselves are
entities whose members describe other entities.
Two basic questions guide your work in this step:
Which of the list items are entities and which are
attributes (information about a particular entity)?
For example, if members are one of the subjects of
a database (an entity), information describing members
might include name, birth date, gender, physical stature,
and member or account number (attributes). Each of these
attributes speaks directly to who the real member is. By
taking the analysis to this level, you now have a model of
how youll represent a member in the database and can
translate the description into the definition of a table of
members. The translation is quite straightforward. The
entity (or thing) becomes a table; each attribute
becomes a field in the table.
The resulting table is shown in Figure 1 and
incorporates two design standards that I apply to all my
databases. First, each table should have a single field, not
part of the data, that identifies the record. This becomes
Continues on page 4
www.pinnaclepublishing.com 3 Smart Access November 2005
F
U
L
L

P
A
G
E

A
D
:
F
M
S
4 www.pinnaclepublishing.com Smart Access November 2005
the tables primary key. Second, each attribute should be
indivisible: I should never need, in any application, to
pull out data inside the attribute. As you can see in
Figure 1, I broke the name down into three component
attributes: first name, middle name, and last name.
Finding entities
Does this table satisfy all of the information needs about
the people in the database? Most certainly not! What it
does satisfy is the need for information that directly
describes each person to the degree that the client and
developer have agreed that the person needs to be
described for this organization and database. In the list of
what to track, in my membership database, members
and organizations are quite clearly distinct entities. A
member isnt an organization and an organization isnt
a member.
Not all decisions are so straightforward. For instance,
is an address an entity or an attribute?
From the data modeling perspective, the answer
depends on whether organizations and/or members
represented in the database happen to share addresses.
In my case, organizations and persons could share
addresses. If an organization and a person can share an
address, then it suggests that the address is a separate
entity with an existence of its own thats independent of
the organization or person it belongs to. Therefore,
addresses are entities and will have a table of their own.
After the obvious physical subjects come the more
conceptual subjects that you may need to track. Depending
on the organizations business rules, you may need
additional entities to track relationships between subjects.
For instance, because addresses are an entity in my
database, I need an entity to track the relationship between
addresses and persons or organizations (or both). In my
database, Ill have an Organization/Address table to track
the relationship between organizations and addresses.
Categories form another problem. For instance, a
the person table? The simple answer is no. A category
doesnt describe the subject to which the category
applies. A category describes how a subject relates to
or interacts with other subjects and with the overall
organization. A category doesnt describe a subject in
isolation from other subjects. A category does have a
relationship with a subject, however, and the relationship
does require an entity.
For instance, look at the category organization type.
Rather than being an attribute of the organization subject,
the organization type describes a relationship among
organizations: Types only make sense if several different
entities share the same type. Since an organization has a
relationship with the organization type (an organization
belongs to a type), you need an entity to track
relationships between organizations and their types:
an Organization/Category table.
The same kind of analysis applies to a persons role
within an organization: directors, officers, and executives.
Each of these terms describes a particular role a member
might have in an organization. In other words, theyre a
category that describes the relationship between an
organization and a member: You cant be a president
unless you have an organization to be president of. So I
need a Membership/Role table to track the relationship
between a member and a role.
While I could have had separate tables for
MemberCategory and OrganizationCategory, I chose
not to. As Figure 2 shows, the categories for organization
and members share a common Categories table with
tables that describe the MemberCategory/Member and
Organization/OrganizationCategory. The rules that drive
this decision are worth explaining. However, youll have
to come back next month for that. L
Glenn Lloyd is a freelance Access database developer and desktop
applications trainer. His present work is solidly grounded in extensive
experience in administration and accounting for charitable
organizations. Recently appointed as a Forum Administrator, Glenn
has been an active member of UtterAccess.com since 2002. He lives
and works in Sudbury, Ontario, Canada.
Data Modeling...
Continued from page 2
Figure 1.
Table of
members.
Figure 2. Organizations, members, and categories.
person can be a president, board
member, or have some other role
in the organization. Dont those
categories describe the person
and, therefore, shouldnt they be
represented by additional fields in
www.pinnaclepublishing.com 5 Smart Access November 2005
Smart Access
Simplifying Queries
Russell Sinclair
Rather than define every query that your users might require,
why not let your users make up their queries as they need
themprovided that theyre not going to be overwhelmed
by the options available to them. Russell Sinclair discusses
how to create a simplified query interface for Access users.
I
F youre reading this article, chances are that you have
a reasonably good idea of how to work with queries
in Access databases. You know how to use the query
designer to get at the data you want and how to use that
data in your applications. However, can you say that your
users have those same skills?
One of the companies Im working with right
nowM7 Database Services at www.m7database.com
specializes in developing Access solutions for small
to mid-sized companies or branch offices for large
companies. These solutions tend to be aimed at a small
group of end users who generally have little or no
database design experience. Many of these people dont
know how to create queries or work with tables and other
Access objects (thats why they hire the experts). So when
the users wanted to start running their own queries, M7
needed a tool that could simplify the query process.
The first design conversations we had about creating
a simplified query tool resulted in all sorts of suggestions,
including the ability to limit fields, group fields, and
calculate totals. We realized, however, that with too many
features, the query builder would simply be a copy of the
the results and export them to other applications.
It also wouldnt hurt if the application worked with
both MDBs and ADPs.
Reading the queries
When I created the query building application, I knew Id
want to be able to allow users to use only a select set of
queries with the tool. I didnt want them to be able to
use just any query in the database because that would
probably make the tool much harder to use. Instead, I
came up with a naming convention for these queries:
Queries that had a prefix of qbf could be used with our
tool. In the code module fdlgQueryBuilder in the sample
database, the ListQueries function in that form returns a
list of all of the qbf queries. The code loops through the
objects in the AllViews, AllFunctions, and AllQueries
properties for the CurrentData object. When it runs across
a query that starts with qbf, it adds the query to a local
table tblQuery with the name of the query, the query type,
and the name without the prefix as the name displayed for
the query to an end user. This data is used to populate the
Query dropdown in the builder form shown in Figure 1.
Once a user selects a query on this form, the code
analyzes it to see what fields it contains and gathers
information on the data contained in those fields. The
way that this is done is different for SQL Server queries
and Access queries. In fact, its probably the first time I
Figure 1. Query builder main screen.
Access query designer. Apart from the fact that
it might get too complicated for users to work
with, we didnt want to reinvent the wheel. If
the users were sophisticated enough to use
these features, they were probably capable of
using the Access query designer.
We finally settled on the features that we
knew would be required:
The query builder would have to base the
data it worked with on a query that we
pre-created for the users. This would
insulate the users from having to use the
designers to create their queries.
The users would have to have the ability
to filter columns in the query to specific
values they would choose.
The users would need to be able to
select the fields that get output, or output
all fields.
The users would need the ability to view
2000 2000 2002 2002 2003 2003
6 www.pinnaclepublishing.com Smart Access November 2005
can think of when I could justify using both DAO and
ADO in the same procedure. If you look at the code tied
to the AfterUpdate event of the cboQuery combo box,
youll see both of these methods.
When working with the Access objects, I referenced
the QueryDef object that represented that query.
This object allowed me to easily loop through the
available fields.
For the SQL Server objects, I had to the use the
OpenSchema function on the ADO Connection object (see
the sidebar, Connection.OpenSchema) calling for the
adSchemaColumns, or adSchemaProcedureColumns
recordset. I then used the data in this recordset to populate
the data in tblField. Although I would have liked to
standardize the code for Access and SQL databases,
not all data providers support all of the schemas this
function can return. This is the case with Access and the
adSchemaProcedureColumns enumeration values.
For each field, I stored the name, position, and type.
This information is used in the criteria sub-form to allow
users to pick the fields from the dropdowns and to help
me format and validate data the user enters.
The user interface is reasonably simple. A user can
select the query from the list and then select a field to
use in the sub-form. In order to maintain the ease of use
of the application, the only comparison operators I
chose to implement were equals, does not equal, greater
than, and less than. The user can select one of these
operators and enter up to three criteria that are ORed
together. I didnt provide users the ability to define how
criteria related to each other. The main reason behind
this decision was to avoid confusion around the order
of operations when mixing ANDs and ORs. What I did,
instead, was to treat each criterion specified as cumulative
so that its ANDed with other criteria. All of these criteria
are stored in tblCriteria and used when the user clicks the
View Results button to generate the SQL statement for the
resulting data.
Viewing the results
Generating SQL is probably something weve all tried at
some point or another. The code in basQueryBuilder is the
result of many lessons learned through past projects. The
code in this module splits the task of building the SQL
string into two units: building the SELECT string, and
building the WHERE clause. The SELECT string is easily
defined by the selected fields in tblCriteria or all fields.
A simple comma-delimited list of fields is built or a
wildcard is used.
The WHERE clause is slightly more complicated.
Whenever one of the value fields is filled in, the
WhereClause function calls the CriteriaString function to
build a criteria string. This function determines the right
operators to use, handles text formatting to use for dates,
number, and Boolean fields, and performs wildcard
conversion. The path that it takes through the function is
very much dependent on the data type of the field thats
being analyzed.
With the SQL statement complete, the application is
ready to submit the statement, retrieve the data, and
present the data to the user. However, I didnt want to
have to go to an external interface for the users to be able
to see the data. I wanted them to be able to open a form
and preview the data in place, with all of the functionality
available that Access can provide. This meant that I had to
modify the design of a form on the fly.
The click event of cmdViewResults in the query
builder dialog takes care of this for me. The SQL
statement is used to open an ADO recordset object. The
code opens the sub-form fsfrQueryResults (which will
display the results) in design view but hidden. The code
then removes any existing controls on the form and then
adds a checkbox for each Boolean field or a textbox for
any other field. This is handled through the Application
.CreateControl method. The form is displayed in
datasheet view so I dont need to worry about the layout
of the controlsthey automatically show up in the
dataset in the order in which theyre created on the form.
With all the controls created, the form is closed and saved.
After that, the parent form for this sub-form is opened
and the ADO recordset I got earlier to the Recordset
property of the sub-form is assigned to its Recordset
property. This allows me to use either SQL Server or
Access data without having the change the code. The
form is shown in Figure 2.
The results form allows the user to preview the
Connection.OpenSchema
The OpenSchema method of the ADO Connection object
returns information about the data store defined in the
connection. It allows you, among other things, to list tables,
queries, constraints, and indexes. It can also be used to list
users in a database and many other things. This function
takes three parameters. The first parameter is a value of the
SchemaEnum enumeration that defines the information you
want to get. The second parameter is an optional array of
restrictions you want to place on the data. Each member of
the array corresponds to a particular column in the result set.
The Access Help has information on what restrictions can be
used with each schema. The final parameter, SchemaID, is a
GUID value thats only used if the schema requested is
adSchemaProviderSpecific. This special schema type allows
you to request information thats custom tailored to the
provider, such as the value {947bb102-5d43-11d1-bdbf-
00c04fb92675}, which will cause the function to return the
list of users connected to an Access database. The result of
the OpenSchema call is a read-only ADO Recordset.
www.pinnaclepublishing.com 7 Smart Access November 2005
H
A
L
F

P
A
G
E

A
D
:
B
L
A
C
K

M
O
S
H
A
N
N
O
N
data or copy it from Access to
some other application. The data
can also be exported from the form
by a button click. The button simply
calls DoCmd.OutputTo to export
the data.
How to use it
The resulting application is
available in the accompanying
download. The application is
designed as an add-in, which allows
it to work with both SQL Server
and Access data without changing
your code. However, if you would
rather integrate the code right into
an application, you can import all
of the objects from the sample
Figure 2. Query results.
database into your own project. All you need to do to start
the application is open the query builder dialog.
Sometimes simplicity in design can lead to a better
user experience. This tool empowers beginner users
with their data in much the same way that the Access
query designer can empower more advanced users.
The feedback weve had on this tool so far has been
extremely favorable. Think about including something
like this in your next project and see how much you can
improve the usability of your application. L
511SINCLAIR.ZIP at www.pinnaclepublishing.com
Russell Sinclair is an MSCD and is the owner of Synthesystems
(www.synthesystems.com), an independent consulting firm specializing
in .NET, SQL Server, and Microsoft Access development. Hes the author
of From Access to SQL Server, an Access developers guide to migrating to
SQL Server, and a Smart Access Contributing Editor.
8 www.pinnaclepublishing.com Smart Access November 2005
Access Answers Smart Access
All in the Family
Doug Steele
This month, Doug Steele looks at how
to handle tables where multiple types
of data are in the same table.
I
LL begin by mentioning that this
problem came from a daycare that
wanted to be able to produce
cards that each parent could carry to
prove that they were entitled to pick up the specific
children. Its not often that you get to help out with a
problem that means this much to so many people.
I have a table where each family member is in a separate
record (imported from another program that has it that
way). Each record has a FamilyId field, as well as a
FamilyPosition field (head, spouse, child). I need to make
a name tag that gets the name of the family head from
one record and the spouses name from another record
and puts them together on the same line. Then I need
to get the names of all of the children together on a
second line.
For the purposes of illustration, Ill assume that the table
has the fields listed in Table 1.
Table 1. Details of the Family table.
Field name Data type
Id AutoNumber (PK)
FirstName Text
LastName Text
FamilyId Long Integer
FamilyPosition Text
Since the data is simplified (it only shows a current
snapshot of the family, so I dont have to worry about
previous spouses), its reasonable to assume that theres at
most a one-to-one relationship between family head and
family spouse. That means I should be able to use SQL to
relate head to spouse.
One way of doing this is to save a couple of queries:
one that returns only family heads, and one that returns
only family spouses. The SQL for these queries would
look like this:
SELECT ID, FirstName, LastName, FamilyId
FROM Family
WHERE FamilyPosition="Head"
SELECT ID, FirstName, LastName, FamilyId
Table 2. Results of running the query on the sample data (given how left joins work, the
empty cells are actually Null, not blank).
HeadFirstName HeadLastName SpouseFirstName SpouseLastName FamilyId
Jennifer Berry 214
David Jones Cheryl Jones 506
Mark Smith Mandy Brown 360
FROM Family
WHERE FamilyPosition="Spouse"
Ill name these two queries qryFamilyHead and
qryFamilySpouse, respectively, and then write a query
that joins the two together:
SELECT Head.FirstName AS HeadFirstName,
Head.LastName AS HeadLastName,
Spouse.FirstName AS SpouseFirstName,
Spouse.LastName AS SpouseLastName,
Head.FamilyId
FROM qryFamilyHead AS Head
LEFT JOIN qryFamilySpouse AS Spouse
ON Head.FamilyId = Spouse.FamilyId;
Running this query against the sample data in the
download database gives the results shown in Table 2.
In Access 2000 and newer, you can actually do this
with only a single query:
SELECT Head.FirstName AS HeadFirstName,
Head.LastName AS HeadLastName,
Spouse.FirstName AS SpouseFirstName,
Spouse.LastName AS SpouseLastName,
Head.FamilyId
FROM
(SELECT ID, FirstName, LastName, FamilyId
FROM Family
WHERE FamilyPosition="Head") AS Head
LEFT JOIN
(SELECT ID, FirstName, LastName, FamilyId
FROM Family
WHERE FamilyPosition="Spouse") AS Spouse
ON Head.FamilyId = Spouse.FamilyId;
Regardless of whether you use one query or two,
these queries wont necessarily give the data in the most
useful format. Usually, given the data shown in Figure 1,
people want to see the names like this:
Jennifer Berry
David & Cheryl Jones
Mark Smith & Mandy Brown
In other words, if theres no spouse, the desired
result should just be HeadFirstName HeadLastName.
If there is a spouse, the query should check whether
HeadLastName and SpouseLastName are the same.
2000 2000 2002 2002 2003 2003
www.pinnaclepublishing.com 9 Smart Access November 2005
If they are, the user will want to see
HeadFirstName & SpouseFirstName
HeadLastName. If not, the desired
result is HeadFirstName
HeadLastName & SpouseFirstName
SpouseLastName. This can be
handled using a couple of IIf
Working with a data domain implies that Im going
to need to work with a recordset. Im also going to need
to create a SQL string to create the recordset, as well as a
variable to use to hold the concatenated values:
Dim rstCurr As DAO.Recordset
Dim strConcatenate As String
Dim strSQL As String
The SQL string needed to create the recordset relies
on the values passed to the function for Expr, Domain,
and Criteria:
strSQL = "SELECT " & Expr & " AS TheValue " & _
"FROM " & Domain
If Len(Criteria) > 0 Then
strSQL = strSQL & " WHERE " & Criteria
End If
So the code opens the recordset and then loops
through the records concatenating the data into a single
variable along with the Separator value.
This code concatenates the values, adding the
separator after each value, and then removes the final
separator at the end:
Set rstCurr = CurrentDb().OpenRecordset(strSQL)
Do While rstCurr.EOF = False
strConcatenate = strConcatenate & _
rstCurr!TheValue & Separator
rstCurr.MoveNext
Loop
If Len(strConcatenate) > 0 Then
strConcatenate = _
Left$(strConcatenate, _
Len(strConcatenate) - Len(Separator))
End If
Once Ive looped through all of the rows in the
recordset, alls that left is to clean up:
rstCurr.Close
Set rstCurr = Nothing
DConcatenate = strConcatenation
End Function
In this situation, the Expr that Im interested in
is FirstName. The Domain, of course, is the table
Family. The only records of interest are those where
FamilyPosition is child and have matching FamilyIds.
For instance, if I want the names of all of the children in
family 506, the call to DConcatenate would be:
DConcatenate("FirstName","Family", _
"FamilyPosition = 'child' And FamilyId = 506")
This call would return Jeremy, Julie, Amy.
If you look in the accompanying database, youll see
Figure 1. Output of query showing Family Head and Spouse information.
statements in the SQL statement:
IIf(IsNull(Spouse.LastName), _
Head.FirstName & " " & Head.LastName, _
IIf(Spouse.LastName=Head.LastName,
Head.FirstName & " & " & Spouse.FirstName & _
" " & Head.LastName,Head.FirstName & " " & _
Head.LastName & " & " & Spouse.FirstName & _
" " & Spouse.LastName))
Figure 1 shows the results of adding those functions
to the query shown earlier.
Okay, that gave me the name of the family head from one
record, and the spouses name from another record, and
puts them together on the same line. How do I concatenate
the names of all of the children together as a single line?
Concatenating multiple related records into a single
result is a fairly common request with one-to-many
relationships, but, unfortunately, its not easily supported
using SQL, so Ill look at creating a function to do it. Even
though I dont appear to have a one-to-many situation
(since I only have a single table), I recognized that the
data could realistically be thought of as comprising two
tablesone for the family, and one for the family
membersso if we have a concatenation function, it
should be of use to us.
Whats required is to return all of the records that
are related to one another, and concatenate them into a
single field. Working with sets of related records is what
Domain Aggregate functions (DAvg, DCount, DLookup,
and so on) are all about, but unfortunately there isnt a
built-in DConcatenate function in Access, so Im going to
create one.
The general syntax for Domain Aggregate functions is
Dfunction(expr, domain[, criteria]), where Expr is a string
expression that identifies the field whose value you want
to work with, Domain is a string expression identifying
the set of records that constitutes the domain (a table
name or a query name), and the optional Criteria is a
string expression used to restrict the range of data on
which the function is performed.
To this, Im going to add an additional optional
parameter, Separator, which will let me specify what
character is supposed to be used to separate the
concatenated values. If not supplied, , (a comma
followed by a blank field) is used:
Function DConcatenate( _
Expr As String, _
Domain As String, _
Optional Criteria As String = vbNullString, _
Optional Separator As String = ", " _
) As String
10 www.pinnaclepublishing.com Smart Access November 2005
that Ive created query qryFamilyNames, which uses the
preceding query and the DConcatenate function to return
both the information on the parents and the information
about the children:
SELECT Head.FirstName AS HeadFirstName,
Head.LastName AS HeadLastName,
Spouse.FirstName AS SpouseFirstName,
Spouse.LastName AS SpouseLastName,
Head.FamilyId,
IIf(IsNull(Spouse.LastName),Head.FirstName &
" " & Head.LastName,
IIf(Spouse.LastName=Head.LastName,
Head.FirstName & " & " & Spouse.FirstName &
" " & Head.LastName,Head.FirstName & " " &
Head.LastName & " & " & Spouse.FirstName &
" " & Spouse.LastName)) AS DisplayName,
DConcatenate("FirstName", "Family",
"FamilyPosition = 'child' And
FamilyId =" & [Head].[FamilyId]) AS Children
FROM qryFamilyHead AS Head
LEFT JOIN qryFamilySpouse AS Spouse
ON Head.FamilyId = Spouse.FamilyId
Figure 2 shows the results of adding that to the query
I showed earlier.
Okay, thats almost what I wanted. Sometimes the children
dont have the same last name as their parents. Can I get the
childs surname shown as well?
The simple answer is that the DConcatenate function can
actually return more than one field. If you change the call
to the previous DConcatenate function to this:
DConcatenate("FirstName & ' ' & LastName",
"Family",
"FamilyPosition = 'child' And
FamilyId =" & [Head].[FamilyId])
the query will return the result shown in Table 3.
Table 3. Children with different surnames, result 1.
Jason Berry, Chloe Berry
Jeremy Jones, Julie Jones, Amy Jones
Brittany Smith, Jessica Brown
If what you want, however, is the result in Table 4,
its going to be a little more work (and it will no longer
be possible to use a generic function such as the
DConcatenate function from earlier).
Table 4. Children with different surnames, result 2.
Chloe & Jason Berry
Amy, Jeremy & Julie Jones
Jessica Brown and Brittany Smith
What has to be done in this case is open a recordset
that returns both FirstName and LastName for the
children in a given family. Youll then need to order the
recordset so that rows with the same LastName are
grouped together.
For the first row in the recordset, the code
concatenates the FirstName to the working
concatenation string. For each subsequent row, the
code determines whether or not the LastName is the
same as the previous LastName. If it is, I concatenate a
comma and the current FirstName to the working string.
If it isnt, I determine whether the last thing added to the
working string was a comma followed by a FirstName,
or just a FirstName. If its a comma, then I replace it with
an ampersand.
In either case, the next step is to add a space and
the previous LastName. Once thats done that, I can
concatenate the previous word followed by the new
FirstName.
I suspect that the code is less complicated than those
instructions. The opening section declares some variables:
Function ConcatChildren( _
FamilyId As Long _
) As String
Dim dbCurr As DAO.Database
Dim rsCurr As DAO.Recordset
Dim intSameLastName As Integer
Dim strChildren As String
Dim strPrevFirstName As String
Dim strPrevLastName As String
Dim strSQL As String
strChildren = vbNullString
I then create the SQL string to return a recordset
for all the children in the specified family, ordered by
LastName (adding FirstName in the ORDER BY clause
isnt critical to the solution):
strSQL = "SELECT FirstName, LastName " & _
"FROM Family " & _
"WHERE FamilyId = " & FamilyId & _
" AND FamilyPosition = 'child' " & _
"ORDER BY LastName, FirstName"
Set dbCurr = CurrentDb
Set rsCurr = dbCurr.OpenRecordset(strSQL)
Now I look at each record in the recordset that
was returned:
With rsCurr
If .RecordCount <> 0 Then
Do While Not .EOF
If strPrevLastName <> !LastName Then
If strPrevLastName doesnt contain anything, then
this is the first record. I use only the first name until I
Figure 2. Query
output showing
Family Head and
Spouse information,
and Children names.
www.pinnaclepublishing.com 11 Smart Access November 2005
Subscribe to Smart Access today and receive a special one-year introductory rate:
Just $129* for 12 issues (thats $20 off the regular rate)
Pinnacle, A Division of Lawrence Ragan Communications, Inc. L 800-493-4867 x.4209 or 312-960-4100 L Fax 312-960-4106
NAME
COMPANY
ADDRESS
CITY STATE/PROVINCE ZIP/POSTAL CODE
COUNTRY IF OTHER THAN U.S.
E-MAIL
PHONE (IN CASE WE HAVE A QUESTION ABOUT YOUR ORDER)
Dont miss another issue! Subscribe now and save!
K Check enclosed (payable to Pinnacle Publishing)
K Purchase order (in U.S. and Canada only); mail or fax copy
K Bill me later
K Credit card: __ VISA __MasterCard __American Express
CARD NUMBER EXP. DATE
SIGNATURE (REQUIRED FOR CARD ORDERS)
* Outside the U.S. add $30. Orders payable in
U.S. funds drawn on a U.S. or Canadian bank.
Detach and return to:
Pinnacle Publishing L 316 N. Michigan Ave. L Chicago, IL 60601
Or fax to 312-960-4106
INS5
find out the last name of the next child. I used a counter
to check whether this is the first name with the given
last name:
If Len(strPrevLastName) = 0 Then
strChildren = strChildren & _
!FirstName
intSameLastName = 1
If strPrevLastName does contain a value, then I
know that Im not on the first record, and that the
previous record has a different last name than the current
record. I want to add the previous last name to the string
that holds my concatenated list. However, I need to check
whether or not theres only one child with the previous
last name (in which case I simply concatenate the
previous last name), or if theres more than one (in which
case I know that I used a comma when concatenating the
previous first name to the list, so I want to change the
comma to an ampersand before we continue). I can use
the variable intSameLastName to tell me how many
children had the same last name:
Else
If intSameLastName = 1 Then
strChildren = strChildren & _
" " & strPrevLastName & _
" and " & !FirstName
Else
strChildren = Left$(strChildren, _
Len(strChildren) - _
Len(strPrevFirstName) - 2)
strChildren = strChildren & _
" & " & strPrevFirstName & _
" " & strPrevLastName & _
" and " & !FirstName
End If
intSameLastName = 1
End If
If the current record has the same last name as the
previous record, all I do is concatenate the current
FirstName (prefixed with a comma) to my concatenation
string. I also have to make sure to increment
intSameLastName so that I can have a count of how
many children have the same last name:
Else
strChildren = strChildren & _
", " & !FirstName
intSameLastName = intSameLastName + 1
End If
Finally, I save the current names, and move onto the
next record:
strPrevFirstName = !FirstName
strPrevLastName = !LastName
.MoveNext
Loop
After the loop is finished, I still have a last name
that hasnt been added to my concatenation string. I
use the same logic as before to determine what to add if
theres only one child with the previous last name, or if
there are many:
If intSameLastName = 1 Then
strChildren = strChildren & _
" " & strPrevLastName
Else
strChildren = Left$(strChildren, _
Len(strChildren) - _
Len(strPrevFirstName) - 2)
strChildren = strChildren & " & " & _
strPrevFirstName & " " & strPrevLastName
End If
End If
End With
12 www.pinnaclepublishing.com Smart Access November 2005
November 2005 Downloads
For access to current and archive content and source code, log in at www.pinnaclepublishing.com.
Smart Access (ISSN 1066-7911)
is published monthly (12 times per year) by:
Pinnacle Publishing
A Division of Lawrence Ragan Communications, Inc.
316 N. Michigan Ave., Suite 300
Chicago, IL 60601
POSTMASTER: Send address changes to Lawrence Ragan Communications, Inc., 316
N. Michigan Ave., Suite 300, Chicago, IL 60601.
Copyright 2005 by Lawrence Ragan Communications, Inc. All rights reserved. No part of this
periodical may be used or reproduced in any fashion whatsoever (except in the case of brief
quotations embodied in critical articles and reviews) without the prior written consent of
Lawrence Ragan Communications, Inc. Printed in the United States of America.
Brand and product names are trademarks or registered trademarks of their respective
holders. Microsoft is a registered trademark of Microsoft Corporation. Access is a trademark or
registered trademark of Microsoft Corporation in the United States and/or other countries and is
used by Ragan Communications, Inc. under license from owner. Smart Access is an independent
publication not affiliated with Microsoft Corporation. Microsoft Corporation is not responsible in
any way for the editorial policy or other contents of the publication.
This publication is intended as a general guide. It covers a highly technical and complex
subject and should not be used for making decisions concerning specific products or
applications. This publication is sold as is, without warranty of any kind, either express or
implied, respecting the contents of this publication, including but not limited to implied
warranties for the publication, quality, performance, merchantability, or fitness for any
particular purpose. Lawrence Ragan Communications, Inc, shall not be liable to the purchaser
or any other person or entity with respect to any liability, loss, or damage caused or alleged
to be caused directly or indirectly by this publication. Articles published in Smart Access do
not necessarily reflect the viewpoint of Lawrence Ragan Communications, Inc. Inclusion of
advertising inserts does not constitute an endorsement by Lawrence Ragan Communications,
Inc., or Smart Access.
Questions?
Customer Service:
Phone: 800-920-4804 or 312-960-4100
Fax: 312-960-4106
Email: PinPub@Ragan.com
Advertising: RogerS@Ragan.com
Editorial: FarionG@Ragan.com
Pinnacle Web Site: www.pinnaclepublishing.com
Subscription rates
United States: One year (12 issues): $149; two years (24 issues): $258
Other:* One year: $179; two years: $318
Single issue rate:
$20 ($25 outside United States)*
* Funds must be in U.S. currency.
Editor: Peter Vogel (peter.vogel@phvis.com)
Contributing Editors: Mike Gunderloy, Danny J. Lesandrini,
Garry Robinson, Russell Sinclair
CEO & Publisher: Mark Ragan
Group Publisher: Michael King
Executive Editor: Farion Grove
Finally, I clean up and return the working
concatenation string:
rsCurr.Close
Set rsCurr = Nothing
Set dbCurr = Nothing
ConcatChildren = strChildren
End Function
Yeah, its a lot of work, but it seems to do the trick!
While the original questioner didnt request this
additional functionality, its fairly straightforward to
extend the model to support allowing additional people to
be associated with each family, so that its possible to pre-
approve a neighbor or relative picking up the children.
You could do this by including a new FamilyPosition
value of, say, friend. You would then use a query along
the lines of:
SELECT FirstName, LastName,
Null, Null, FamilyId,
FirstName & : " & LastName AS DisplayName,
DConcatenate("FirstName", "Family",
"FamilyPosition = 'child' And
FamilyId =" & [FamilyId]) AS Children
FROM Family
The only reason I included the two Null fields in this
query was to ensure that it included the same number of
fields as the original query. In this way, its possible to
UNION together the two queries when trying to produce
the report. L
511STEELE.ZIP at www.pinnaclepublishing.com
Doug Steele has worked with databases, both mainframe and PC,
for many years. Microsoft has recognized him as an Access MVP for
his contributions to the Microsoft-sponsored newsgroups. Check
http://I.Am/DougSteele for some Access information, as well as
Access-related links. He enjoys hearing from readers who have ideas
for future columns, though personal replies arent guaranteed.
AccessHelp@rogers.com.
511SINCLAIR.ZIPRussell Sinclairs sample database is
actually an Access add-in that you can incorporate into
your applications. This add-in provides a simple but
effective interface that empowers users to create their
own SQL queries.
511STEELE.ZIPDoug Steele has provided the sample
code for the complex processing of the wide variety of
family structures and naming conventions that are
common in todays world. And all Doug wanted to do was
produce a list with everyones name presented correctly.

Вам также может понравиться