-
Smart AccessSolutions for Microsoft AccessTM Developers
1 Data Modeling for the AccessNewcomer, Part 1Glenn Lloyd
5 Simplifying QueriesRussell Sinclair
8 Access Answers:All in the FamilyDoug Steele
12 November 2005 Downloads
November 2005Volume 13, Number 11
Accompanying files available online
atwww.pinnaclepublishing.com
Applies toAccess 2000
Applies toAccess 95
Applies toAccess 97
20002000 20022002
Applies toAccess 2002
Applies toAccess 2003
20032003
Data Modelingfor the AccessNewcomer, Part 1Glenn Lloyd
Thorough, thoughtful, and accurate data modeling should be the
starting point ofdetailed database design. But a surprising number
of developers have little or nounderstanding of data modeling and
shy away from what sounds like a non-profitable and time-consuming
task. Glenn Lloyd looks at the typical design pitfallsthat trap
Access beginners and shows the basic techniques that ensure
success.
MY call came from a community service organization that
providesspecialized services both locally and remotely across the
vast expanseof Northern Ontario. They were unable to solve a
problem with theirAccess databasea problem that was both
embarrassing and damaging totheir relations with their membership
(and to the community at large). Severalmonths earlier, one of
their key members had died. Now, despite their bestefforts, his
widow and the other organizations with which he had beenassociated
were still receiving their periodic mailings addressed to him. As
Ilistened to the story, I realized that the problem indicated a
faulty databasedesign. The staff that had developed the database
had no training orunderstanding of relational design principles and
rationale. What they hadknown was how they wanted to see their data
laid out in reports and haddesigned a data structure that matched
those report layouts.
What made the problem embarrassing was who had died.
Theorganizations membership is derived from local community
organizationslocated in various centers across Northern Ontario.
Board members canrepresent one or more of these community
organizations. The memberwhose death triggered the problem was a
prominent member of one of thecommunities and either officially or
unofficially represented a number of theconstituent organizations.
This was a very visible mistake.
The original sinOne of the goals of the original database was to
have targeted mailing lists so
20002000 20022002 20032003
-
2 www.pinnaclepublishing.comSmart Access November 2005
that mailings could be restricted to specific individualsor
groups according to the purpose of the mailing.The original
database designers concluded that theycould best accomplish this
objective by subdividingmembership data into several tables, one
for each ofthe eight or nine categories that best described
thecommunity organizations the members served. Thismeant that some
individuals, including the recentlydeceased member, had multiple
entries, one in each ofthe relevant membership tables. The member
whoseinformation had brought the problem to light had at leastfour
or five of these entries.
When all is said and done, a database is nothingmore or less
than a model of the real world. So, beforean efficient relational
database can be designed andimplemented, the developer or
developers must have anin-depth understanding of the nature of the
real worldthat the database will model. A membership database,for
example, doesnt contain real people. A databasecontains information
that may be about real people.That information describes who the
real people are, wherethey live, the membership category to which
they belong,special skill sets they bring to the group, and any
otherinformation the owners of the database need to retainabout
their members.
Data modeling refers to the first step of detaileddatabase
design. Data modeling is the basis of theultimate table and
relationship design that, whenimplemented, becomes the databases
structure. Datamodelings purpose is to translate the
real-worldrequirements (described either formally or
informally)into a formal data structure.
The process of data modeling is as vital to databasedesign and
implementation as the structure thatsproduced because the process
requires you to study theorganization and the information you want
the databaseto track. As a result, when you follow the process
youcome to know your data very well and move from mereassumption
and speculation to in-depth knowledge of theorganization and its
information needs. Along the way,you define your database
structure.
While you may have already worked out an intuitivesolution to
the problem, dont pat yourself on the back.Without a process, you
cant guarantee that you wouldhave spotted the problem before it
became a problem.And you dont know that youll do as well with
everyother problem that you face. Data modeling can be done anumber
of different ways, but Ill walk you through asimple three-step data
modeling process. By the end ofthis article youll see how this
process would have led,inevitably, to a practical solution to my
clients problem.
Step 1: Make a listIn this first step, its best to step back
from any thoughtsabout how youll eventually organize the data. Your
firststep is do a simple brain dump of everything the
database is required to track. A formal or informalrequirements
analysis is the best guide for this step.
For example, assume that the database in question isa membership
database. The company requesting thedatabase (client, boss, or
whoever has determined that thedatabase should be developed) has
set out several basicpieces 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 ofspecial
skills or capabilities the member has to offer theorganization,
whether the member is a director orexecutive board member, and the
organization ororganizations that the member represents along with
thecategory to which the external organization belongs.
Step 2: Separate subjects and descriptionsThe database
requirements analysis provides only a guideof what the database is
required to track. Those itemsarent all of the same kind:
Some of the items in the list are distinct types ofsubjects for
the database.
Others describe or classify the subjects.
Typically, your initial brain dump will generate asimilar
mixture of subjects and descriptions. The goal ofthe second step is
to clearly identify and separate subjectsfrom their
descriptions.
The technical RDBMS name for the subjects isentity. Of course,
the database would be a rather limitedtool if all it maintained was
a list of entities. In fact, thereal purpose of the database is to
organize and store bitsand pieces of descriptive information
(called attributes)about the databases entities. Entities
correspond to tablesin the ultimate database; attributes correspond
to thetables fields. Category or classification informationpresents
a special case because categories themselves areentities whose
members describe other entities.
Two basic questions guide your work in this step:Which of the
list items are entities and which areattributes (information about
a particular entity)?
For example, if members are one of the subjects ofa database (an
entity), information describing membersmight include name, birth
date, gender, physical stature,and member or account number
(attributes). Each of theseattributes speaks directly to who the
real member is. Bytaking the analysis to this level, you now have a
model ofhow youll represent a member in the database and
cantranslate the description into the definition of a table
ofmembers. The translation is quite straightforward. Theentity (or
thing) becomes a table; each attributebecomes a field in the
table.
The resulting table is shown in Figure 1 andincorporates two
design standards that I apply to all mydatabases. First, each table
should have a single field, notpart of the data, that identifies
the record. This becomes
Continues on page 4
-
www.pinnaclepublishing.com 3Smart Access November 2005
FULL PAG
E AD:
FMS
-
4 www.pinnaclepublishing.comSmart Access November 2005
the tables primary key. Second, each attribute should
beindivisible: I should never need, in any application, topull out
data inside the attribute. As you can see inFigure 1, I broke the
name down into three componentattributes: first name, middle name,
and last name.
Finding entitiesDoes this table satisfy all of the information
needs aboutthe people in the database? Most certainly not! What
itdoes satisfy is the need for information that directlydescribes
each person to the degree that the client anddeveloper have agreed
that the person needs to bedescribed for this organization and
database. In the list ofwhat to track, in my membership database,
membersand organizations are quite clearly distinct entities.
Amember isnt an organization and an organization isnta member.
Not all decisions are so straightforward. For instance,is an
address an entity or an attribute?
From the data modeling perspective, the answerdepends on whether
organizations and/or membersrepresented in the database happen to
share addresses.In my case, organizations and persons could
shareaddresses. If an organization and a person can share
anaddress, then it suggests that the address is a separateentity
with an existence of its own thats independent ofthe 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 moreconceptual
subjects that you may need to track. Dependingon the organizations
business rules, you may needadditional entities to track
relationships between subjects.For instance, because addresses are
an entity in mydatabase, I need an entity to track the relationship
betweenaddresses and persons or organizations (or both). In
mydatabase, Ill have an Organization/Address table to trackthe
relationship between organizations and addresses.
Categories form another problem. For instance, a
the person table? The simple answer is no. A categorydoesnt
describe the subject to which the categoryapplies. A category
describes how a subject relates toor interacts with other subjects
and with the overallorganization. A category doesnt describe a
subject inisolation from other subjects. A category does have
arelationship with a subject, however, and the relationshipdoes
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 amongorganizations: Types only make
sense if several differententities share the same type. Since an
organization has arelationship with the organization type (an
organizationbelongs to a type), you need an entity to
trackrelationships between organizations and their types:an
Organization/Category table.
The same kind of analysis applies to a persons rolewithin an
organization: directors, officers, and executives.Each of these
terms describes a particular role a membermight have in an
organization. In other words, theyre acategory that describes the
relationship between anorganization and a member: You cant be a
presidentunless you have an organization to be president of. So
Ineed a Membership/Role table to track the relationshipbetween a
member and a role.
While I could have had separate tables forMemberCategory and
OrganizationCategory, I chosenot to. As Figure 2 shows, the
categories for organizationand members share a common Categories
table withtables that describe the MemberCategory/Member
andOrganization/OrganizationCategory. The rules that drivethis
decision are worth explaining. However, youll haveto come back next
month for that. s
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 ofmembers.
Figure 2. Organizations, members, and categories.
person can be a president, boardmember, or have some other
rolein the organization. Dont thosecategories describe the
personand, therefore, shouldnt they berepresented by additional
fields in
-
www.pinnaclepublishing.com 5Smart Access November 2005
Smart Access
Simplifying QueriesRussell Sinclair
Rather than define every query that your users might require,why
not let your users make up their queries as they needthemprovided
that theyre not going to be overwhelmedby the options available to
them. Russell Sinclair discusseshow to create a simplified query
interface for Access users.
IF youre reading this article, chances are that you havea
reasonably good idea of how to work with queriesin Access
databases. You know how to use the querydesigner to get at the data
you want and how to use thatdata in your applications. However, can
you say that yourusers have those same skills?
One of the companies Im working with rightnowM7 Database
Services at www.m7database.comspecializes in developing Access
solutions for smallto mid-sized companies or branch offices for
largecompanies. These solutions tend to be aimed at a smallgroup of
end users who generally have little or nodatabase design
experience. Many of these people dontknow how to create queries or
work with tables and otherAccess objects (thats why they hire the
experts). So whenthe users wanted to start running their own
queries, M7needed a tool that could simplify the query process.
The first design conversations we had about creatinga simplified
query tool resulted in all sorts of suggestions,including the
ability to limit fields, group fields, andcalculate totals. We
realized, however, that with too manyfeatures, 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 withboth MDBs and
ADPs.
Reading the queriesWhen I created the query building
application, I knew Idwant to be able to allow users to use only a
select set ofqueries with the tool. I didnt want them to be able
touse just any query in the database because that wouldprobably
make the tool much harder to use. Instead, Icame up with a naming
convention for these queries:Queries that had a prefix of qbf could
be used with ourtool. In the code module fdlgQueryBuilder in the
sampledatabase, the ListQueries function in that form returns alist
of all of the qbf queries. The code loops through theobjects in the
AllViews, AllFunctions, and AllQueriesproperties for the
CurrentData object. When it runs acrossa query that starts with
qbf, it adds the query to a localtable tblQuery with the name of
the query, the query type,and the name without the prefix as the
name displayed forthe query to an end user. This data is used to
populate theQuery dropdown in the builder form shown in Figure
1.
Once a user selects a query on this form, the codeanalyzes it to
see what fields it contains and gathersinformation on the data
contained in those fields. Theway that this is done is different
for SQL Server queriesand Access queries. In fact, its probably the
first time I
Figure 1. Query builder main screen.
Access query designer. Apart from the fact thatit might get too
complicated for users to workwith, we didnt want to reinvent the
wheel. Ifthe users were sophisticated enough to usethese features,
they were probably capable ofusing the Access query designer.
We finally settled on the features that weknew would be
required:
The query builder would have to base thedata it worked with on a
query that wepre-created for the users. This wouldinsulate the
users from having to use thedesigners to create their queries.
The users would have to have the abilityto filter columns in the
query to specificvalues they would choose.
The users would need to be able toselect the fields that get
output, or outputall fields.
The users would need the ability to view
20002000 20022002 20032003
-
6 www.pinnaclepublishing.comSmart Access November 2005
can think of when I could justify using both DAO andADO in the
same procedure. If you look at the code tiedto the AfterUpdate
event of the cboQuery combo box,youll see both of these
methods.
When working with the Access objects, I referencedthe QueryDef
object that represented that query.This object allowed me to easily
loop through theavailable fields.
For the SQL Server objects, I had to the use theOpenSchema
function on the ADO Connection object (seethe sidebar,
Connection.OpenSchema) calling for theadSchemaColumns, or
adSchemaProcedureColumnsrecordset. I then used the data in this
recordset to populatethe data in tblField. Although I would have
liked tostandardize the code for Access and SQL databases,not all
data providers support all of the schemas thisfunction can return.
This is the case with Access and theadSchemaProcedureColumns
enumeration values.
For each field, I stored the name, position, and type.This
information is used in the criteria sub-form to allowusers to pick
the fields from the dropdowns and to helpme format and validate
data the user enters.
The user interface is reasonably simple. A user canselect the
query from the list and then select a field touse in the sub-form.
In order to maintain the ease of useof the application, the only
comparison operators Ichose to implement were equals, does not
equal, greaterthan, and less than. The user can select one of
theseoperators and enter up to three criteria that are
ORedtogether. I didnt provide users the ability to define
howcriteria related to each other. The main reason behind
this decision was to avoid confusion around the orderof
operations when mixing ANDs and ORs. What I did,instead, was to
treat each criterion specified as cumulativeso that its ANDed with
other criteria. All of these criteriaare stored in tblCriteria and
used when the user clicks theView Results button to generate the
SQL statement for theresulting data.
Viewing the resultsGenerating SQL is probably something weve all
tried atsome point or another. The code in basQueryBuilder is
theresult of many lessons learned through past projects. Thecode in
this module splits the task of building the SQLstring into two
units: building the SELECT string, andbuilding the WHERE clause.
The SELECT string is easilydefined by the selected fields in
tblCriteria or all fields.A simple comma-delimited list of fields
is built or awildcard is used.
The WHERE clause is slightly more complicated.Whenever one of
the value fields is filled in, theWhereClause function calls the
CriteriaString function tobuild a criteria string. This function
determines the rightoperators to use, handles text formatting to
use for dates,number, and Boolean fields, and performs
wildcardconversion. The path that it takes through the function
isvery much dependent on the data type of the field thatsbeing
analyzed.
With the SQL statement complete, the application isready to
submit the statement, retrieve the data, andpresent the data to the
user. However, I didnt want tohave to go to an external interface
for the users to be ableto see the data. I wanted them to be able
to open a formand preview the data in place, with all of the
functionalityavailable that Access can provide. This meant that I
had tomodify the design of a form on the fly.
The click event of cmdViewResults in the querybuilder dialog
takes care of this for me. The SQLstatement is used to open an ADO
recordset object. Thecode opens the sub-form fsfrQueryResults
(which willdisplay the results) in design view but hidden. The
codethen removes any existing controls on the form and thenadds a
checkbox for each Boolean field or a textbox forany other field.
This is handled through the Application.CreateControl method. The
form is displayed indatasheet view so I dont need to worry about
the layoutof the controlsthey automatically show up in thedataset
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 openedand the ADO recordset I got
earlier to the Recordsetproperty of the sub-form is assigned to its
Recordsetproperty. This allows me to use either SQL Server orAccess
data without having the change the code. Theform is shown in Figure
2.
The results form allows the user to preview the
Connection.OpenSchemaThe 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 7Smart Access November 2005
HALF PA
GE AD:
BLACK M
OSHANN
ON
data or copy it from Access tosome other application. The
datacan also be exported from the formby a button click. The button
simplycalls DoCmd.OutputTo to exportthe data.
How to use itThe resulting application isavailable in the
accompanyingdownload. The application isdesigned as an add-in,
which allowsit to work with both SQL Serverand Access data without
changingyour code. However, if you wouldrather integrate the code
right intoan application, you can import allof the objects from the
sample
Figure 2. Query results.
database into your own project. All you need to do to startthe
application is open the query builder dialog.
Sometimes simplicity in design can lead to a betteruser
experience. This tool empowers beginner userswith their data in
much the same way that the Accessquery designer can empower more
advanced users.The feedback weve had on this tool so far has
beenextremely favorable. Think about including somethinglike this
in your next project and see how much you can
improve the usability of your application. s
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.comSmart Access November 2005
Access Answers Smart Access
All in the FamilyDoug Steele
This month, Doug Steele looks at howto handle tables where
multiple typesof data are in the same table.
ILL begin by mentioning that thisproblem came from a daycare
thatwanted to be able to producecards that each parent could carry
toprove that they were entitled to pick up the specificchildren.
Its not often that you get to help out with aproblem that means
this much to so many people.
I have a table where each family member is in a separaterecord
(imported from another program that has it thatway). Each record
has a FamilyId field, as well as aFamilyPosition field (head,
spouse, child). I need to makea name tag that gets the name of the
family head fromone record and the spouses name from another
recordand puts them together on the same line. Then I needto get
the names of all of the children together on asecond line.
For the purposes of illustration, Ill assume that the tablehas
the fields listed in Table 1.
Table 1. Details of the Family table.
Field name Data typeId AutoNumber (PK)FirstName TextLastName
TextFamilyId Long IntegerFamilyPosition Text
Since the data is simplified (it only shows a currentsnapshot of
the family, so I dont have to worry aboutprevious spouses), its
reasonable to assume that theres atmost a one-to-one relationship
between family head andfamily spouse. That means I should be able
to use SQL torelate head to spouse.
One way of doing this is to save a couple of queries:one that
returns only family heads, and one that returnsonly family spouses.
The SQL for these queries wouldlook like this:
SELECT ID, FirstName, LastName, FamilyIdFROM FamilyWHERE
FamilyPosition="Head"
SELECT ID, FirstName, LastName, FamilyId
Table 2. Results of running the query on the sample data (given
how left joins work, theempty cells are actually Null, not
blank).
HeadFirstName HeadLastName SpouseFirstName SpouseLastName
FamilyIdJennifer Berry 214David Jones Cheryl Jones 506Mark Smith
Mandy Brown 360
FROM FamilyWHERE FamilyPosition="Spouse"
Ill name these two queries qryFamilyHead andqryFamilySpouse,
respectively, and then write a querythat joins the two
together:
SELECT Head.FirstName AS HeadFirstName,Head.LastName AS
HeadLastName,Spouse.FirstName AS SpouseFirstName,Spouse.LastName AS
SpouseLastName,Head.FamilyIdFROM qryFamilyHead AS HeadLEFT JOIN
qryFamilySpouse AS SpouseON Head.FamilyId = Spouse.FamilyId;
Running this query against the sample data in thedownload
database gives the results shown in Table 2.
In Access 2000 and newer, you can actually do thiswith only a
single query:
SELECT Head.FirstName AS HeadFirstName,Head.LastName AS
HeadLastName,Spouse.FirstName AS SpouseFirstName,Spouse.LastName AS
SpouseLastName,Head.FamilyIdFROM(SELECT ID, FirstName, LastName,
FamilyIdFROM FamilyWHERE FamilyPosition="Head") AS HeadLEFT
JOIN(SELECT ID, FirstName, LastName, FamilyIdFROM FamilyWHERE
FamilyPosition="Spouse") AS SpouseON Head.FamilyId =
Spouse.FamilyId;
Regardless of whether you use one query or two,these queries
wont necessarily give the data in the mostuseful format. Usually,
given the data shown in Figure 1,people want to see the names like
this:
Jennifer BerryDavid & Cheryl JonesMark Smith & Mandy
Brown
In other words, if theres no spouse, the desiredresult should
just be HeadFirstName HeadLastName.If there is a spouse, the query
should check whetherHeadLastName and SpouseLastName are the
same.
20002000 20022002 20032003
-
www.pinnaclepublishing.com 9Smart Access November 2005
If they are, the user will want to seeHeadFirstName &
SpouseFirstNameHeadLastName. If not, the desiredresult is
HeadFirstNameHeadLastName & SpouseFirstNameSpouseLastName. This
can behandled using a couple of IIf
Working with a data domain implies that Im goingto need to work
with a recordset. Im also going to needto create a SQL string to
create the recordset, as well as avariable to use to hold the
concatenated values:
Dim rstCurr As DAO.RecordsetDim strConcatenate As StringDim
strSQL As String
The SQL string needed to create the recordset relieson 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 loopsthrough the
records concatenating the data into a singlevariable along with the
Separator value.
This code concatenates the values, adding theseparator after
each value, and then removes the finalseparator 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 therecordset, alls
that left is to clean up:
rstCurr.Close Set rstCurr = Nothing DConcatenate =
strConcatenation
End Function
In this situation, the Expr that Im interested inis FirstName.
The Domain, of course, is the tableFamily. The only records of
interest are those whereFamilyPosition is child and have matching
FamilyIds.For instance, if I want the names of all of the children
infamily 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 functionsto the query
shown earlier.
Okay, that gave me the name of the family head from onerecord,
and the spouses name from another record, andputs them together on
the same line. How do I concatenatethe names of all of the children
together as a single line?
Concatenating multiple related records into a singleresult is a
fairly common request with one-to-manyrelationships, but,
unfortunately, its not easily supportedusing SQL, so Ill look at
creating a function to do it. Eventhough I dont appear to have a
one-to-many situation(since I only have a single table), I
recognized that thedata could realistically be thought of as
comprising twotablesone for the family, and one for the
familymembersso if we have a concatenation function, itshould be of
use to us.
Whats required is to return all of the records thatare related
to one another, and concatenate them into asingle field. Working
with sets of related records is whatDomain Aggregate functions
(DAvg, DCount, DLookup,and so on) are all about, but unfortunately
there isnt abuilt-in DConcatenate function in Access, so Im going
tocreate one.
The general syntax for Domain Aggregate functions
isDfunction(expr, domain[, criteria]), where Expr is a
stringexpression that identifies the field whose value you wantto
work with, Domain is a string expression identifyingthe set of
records that constitutes the domain (a tablename or a query name),
and the optional Criteria is astring expression used to restrict
the range of data onwhich the function is performed.
To this, Im going to add an additional optionalparameter,
Separator, which will let me specify whatcharacter is supposed to
be used to separate theconcatenated values. If not supplied, , (a
commafollowed 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.comSmart Access November 2005
that Ive created query qryFamilyNames, which uses thepreceding
query and the DConcatenate function to returnboth the information
on the parents and the informationabout 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' AndFamilyId =" & [Head].[FamilyId]) AS ChildrenFROM
qryFamilyHead AS HeadLEFT JOIN qryFamilySpouse AS SpouseON
Head.FamilyId = Spouse.FamilyId
Figure 2 shows the results of adding that to the queryI showed
earlier.
Okay, thats almost what I wanted. Sometimes the childrendont
have the same last name as their parents. Can I get thechilds
surname shown as well?
The simple answer is that the DConcatenate function canactually
return more than one field. If you change the callto the previous
DConcatenate function to this:
DConcatenate("FirstName & ' ' &
LastName","Family","FamilyPosition = 'child' AndFamilyId =" &
[Head].[FamilyId])
the query will return the result shown in Table 3.
Table 3. Children with different surnames, result 1.
Jason Berry, Chloe BerryJeremy Jones, Julie Jones, Amy
JonesBrittany 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 longerbe possible to use a
generic function such as theDConcatenate function from
earlier).
Table 4. Children with different surnames, result 2.
Chloe & Jason BerryAmy, Jeremy & Julie JonesJessica
Brown and Brittany Smith
What has to be done in this case is open a recordsetthat returns
both FirstName and LastName for thechildren in a given family.
Youll then need to order therecordset so that rows with the same
LastName aregrouped together.
For the first row in the recordset, the codeconcatenates the
FirstName to the workingconcatenation string. For each subsequent
row, thecode determines whether or not the LastName is thesame as
the previous LastName. If it is, I concatenate acomma and the
current FirstName to the working string.If it isnt, I determine
whether the last thing added to theworking string was a comma
followed by a FirstName,or just a FirstName. If its a comma, then I
replace it withan ampersand.
In either case, the next step is to add a space andthe previous
LastName. Once thats done that, I canconcatenate the previous word
followed by the newFirstName.
I suspect that the code is less complicated than
thoseinstructions. The opening section declares some variables:
Function ConcatChildren( _ FamilyId As Long _) As String
Dim dbCurr As DAO.DatabaseDim rsCurr As DAO.RecordsetDim
intSameLastName As IntegerDim strChildren As StringDim
strPrevFirstName As StringDim strPrevLastName As StringDim strSQL
As String
strChildren = vbNullString
I then create the SQL string to return a recordsetfor all the
children in the specified family, ordered byLastName (adding
FirstName in the ORDER BY clauseisnt 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 thatwas returned:
With rsCurr If .RecordCount 0 Then Do While Not .EOF If
strPrevLastName !LastName Then
If strPrevLastName doesnt contain anything, thenthis is the
first record. I use only the first name until I
Figure 2. Queryoutput showingFamily Head andSpouse
information,and Children names.
-
www.pinnaclepublishing.com 11Smart 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. s
800-493-4867 x.4209 or 312-960-4100 s 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!
q Check enclosed (payable to Pinnacle Publishing)q Purchase
order (in U.S. and Canada only); mail or fax copyq Bill me laterq
Credit card: __ VISA __MasterCard __American Express
CARD NUMBER EXP. DATE
SIGNATURE (REQUIRED FOR CARD ORDERS)
* Outside the U.S. add $30. Orders payable inU.S. funds drawn on
a U.S. or Canadian bank.
Detach and return to:Pinnacle Publishing s 316 N. Michigan Ave.
s Chicago, IL 60601Or fax to 312-960-4106
INS5
find out the last name of the next child. I used a counterto
check whether this is the first name with the givenlast name:
If Len(strPrevLastName) = 0 Then strChildren = strChildren &
_ !FirstName intSameLastName = 1
If strPrevLastName does contain a value, then Iknow that Im not
on the first record, and that theprevious record has a different
last name than the currentrecord. I want to add the previous last
name to the stringthat holds my concatenated list. However, I need
to checkwhether or not theres only one child with the previouslast
name (in which case I simply concatenate theprevious last name), or
if theres more than one (in whichcase I know that I used a comma
when concatenating theprevious first name to the list, so I want to
change thecomma to an ampersand before we continue). I can usethe
variable intSameLastName to tell me how manychildren 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 theprevious
record, all I do is concatenate the currentFirstName (prefixed with
a comma) to my concatenationstring. I also have to make sure to
incrementintSameLastName so that I can have a count of howmany
children have the same last name:
Else strChildren = strChildren & _ ", " & !FirstName
intSameLastName = intSameLastName + 1 End If
Finally, I save the current names, and move onto thenext
record:
strPrevFirstName = !FirstName strPrevLastName = !LastName
.MoveNext Loop
After the loop is finished, I still have a last namethat hasnt
been added to my concatenation string. Iuse the same logic as
before to determine what to add iftheres only one child with the
previous last name, or ifthere 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.comSmart 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 PublishingA Division of Lawrence Ragan Communications,
Inc.
316 N. Michigan Ave., Suite 300Chicago, IL 60601
POSTMASTER: Send address changes to Lawrence Ragan
Communications, Inc., 316N. Michigan Ave., Suite 300, Chicago, IL
60601.
Copyright 2005 by Lawrence Ragan Communications, Inc. All rights
reserved. No part of thisperiodical may be used or reproduced in
any fashion whatsoever (except in the case of briefquotations
embodied in critical articles and reviews) without the prior
written consent ofLawrence Ragan Communications, Inc. Printed in
the United States of America.
Brand and product names are trademarks or registered trademarks
of their respectiveholders. Microsoft is a registered trademark of
Microsoft Corporation. Access is a trademark orregistered trademark
of Microsoft Corporation in the United States and/or other
countries and isused by Ragan Communications, Inc. under license
from owner. Smart Access is an independentpublication not
affiliated with Microsoft Corporation. Microsoft Corporation is not
responsible inany 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 complexsubject and should not be used for
making decisions concerning specific products orapplications. This
publication is sold as is, without warranty of any kind, either
express orimplied, respecting the contents of this publication,
including but not limited to impliedwarranties for the publication,
quality, performance, merchantability, or fitness for anyparticular
purpose. Lawrence Ragan Communications, Inc, shall not be liable to
the purchaseror any other person or entity with respect to any
liability, loss, or damage caused or allegedto be caused directly
or indirectly by this publication. Articles published in Smart
Access donot necessarily reflect the viewpoint of Lawrence Ragan
Communications, Inc. Inclusion ofadvertising inserts does not
constitute an endorsement by Lawrence Ragan Communications,Inc., or
Smart Access.
Questions?
Customer Service:Phone: 800-920-4804 or 312-960-4100Fax:
312-960-4106Email: [email protected]
Advertising: [email protected]
Editorial: [email protected]
Pinnacle Web Site: www.pinnaclepublishing.com
Subscription rates
United States: One year (12 issues): $149; two years (24
issues): $258Other:* One year: $179; two years: $318
Single issue rate:$20 ($25 outside United States)*
* Funds must be in U.S. currency.
Editor: Peter Vogel ([email protected])Contributing Editors:
Mike Gunderloy, Danny J. Lesandrini,
Garry Robinson, Russell SinclairCEO & Publisher: Mark
Ragan
Group Publisher: Michael KingExecutive Editor: Farion Grove
Finally, I clean up and return the workingconcatenation
string:
rsCurr.Close Set rsCurr = Nothing Set dbCurr = Nothing
ConcatChildren = strChildrenEnd 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 toextend
the model to support allowing additional people tobe 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 FamilyPositionvalue of,
say, friend. You would then use a query alongthe lines of:
SELECT FirstName, LastName,Null, Null, FamilyId,FirstName &
: " & LastName AS DisplayName,
DConcatenate("FirstName", "Family","FamilyPosition = 'child'
AndFamilyId =" & [FamilyId]) AS ChildrenFROM Family
The only reason I included the two Null fields in thisquery was
to ensure that it included the same number offields as the original
query. In this way, its possible toUNION together the two queries
when trying to producethe report. s
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.
[email protected].
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.