smart access 1105

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 database—a 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 organization’s 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

Upload: gerson-freire

Post on 23-Nov-2015

34 views

Category:

Documents


0 download

TRANSCRIPT

  • 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.