<

"Domain Aggregate" for Concatenating Values by Group in Microsoft Access

Published on
87,598 Points
31,498 Views
46 Endorsements
Last Modified:
Awarded
Editor's Choice
Community Pick
by Patrick G. Matthews

1. Introduction


A common request in the various database zones at Experts Exchange involves returning a concatenated list of the various items in ColumnB for each distinct value in ColumnA from a particular table or query.  For example:

I have a table that currently holds student subject enrolments with the following fields

Student_ID, Subject_ID
1001, 11001
1001, 11023
1001, 11049
1002, 11023
1002, 11046
1003, 11023
etc

So a student can appear in the list a variable number of times depending on how many subjects they are enrolled in. Some students may be in 10 subjects some maybe in as little as one. I want to be able to transpose the information stored in this table and export it in a linear format something similar to.

Student 1, subject 1, subject 2, subject 3 ... subject n
student 2, subject 1, ,,,subject n

Like other databases, Access does not offer native functions that will do this.  One could use subqueries to return columns for each subject as above.  However, the following limitations apply:
In a Jet SQL solution, you would have to know how many detail values to allow for, and you could not simply let the SQL statement determine it dynamically
You could use VBA to dynamically generate a SQL statement, but that is beyond the skill of beginning Access developers, and many intermediate developers as well
Any such scheme relying on subqueries would require a column providing an ordinal for the detail items within the group

To overcome these obstacles, and to make it easier for most developers to seamlessly integrate this sort of functionality into their Access projects, I decided to create a UDF patterned after the more familiar "domain aggregate" functions such as DSum or DCount. So, the "DConcat" function was created.  As the name suggests, this function concatenates the values of a detail column (or detail columns) based on the groupings suggested by criteria the user supplies.

2. The DConcat() Function


Here is the VBA source code for DConcat:

Function DConcat(ConcatColumns As String, Tbl As String, Optional Criteria As String = "", _
    Optional Delimiter1 As String = ", ", Optional Delimiter2 As String = ", ", _
    Optional Distinct As Boolean = True, Optional Sort As String = "Asc", _
    Optional Limit As Long = 0)
    
    ' Function by Patrick G. Matthews, basically embellishing an approach seen in many
    ' incarnations over the years
    
    ' Requires reference to Microsoft DAO library
    
    ' This function is intended as a "domain aggregate" that concatenates (and delimits) the
    ' various values rather than the more usual Count, Sum, Min, Max, etc.  For example:
    '
    '    Select Field1, DConcat("Field2", "SomeTable", "[Field1] = '" & Field1 & "'") AS List
    '    FROM SomeTable
    '    GROUP BY Field1
    '
    ' will return the distinct values of Field1, along with a concatenated list of all the
    ' distinct Field2 values associated with each Field1 value.
    
    ' ConcatColumns is a comma-delimited list of columns to be concatenated (typically just
    '   one column, but the function accommodates multiple).  Place field names in square
    '   brackets if they do not meet the customary rules for naming DB objects
    ' Tbl is the table/query the data are pulled from.  Place table name in square brackets
    '   if they do not meet the customary rules for naming DB objects
    ' Criteria (optional) are the criteria to be applied in the grouping.  Be sure to use And
    '   or Or as needed to build the right logic, and to encase text values in single quotes
    '   and dates in #
    ' Delimiter1 (optional) is the delimiter used in the concatenation (default is ", ").
    '   Delimiter1 is applied to each row in the code query's result set
    ' Delimiter2 (optional) is the delimiter used in concatenating each column in the result
    '   set if ConcatColumns specifies more than one column (default is ", ")
    ' Distinct (optional) determines whether the distinct values are concatenated (True,
    '   default), or whether all values are concatenated (and thus may get repeated)
    ' Sort (optional) indicates whether the concatenated string is sorted, and if so, if it is
    '   Asc or Desc.  Note that if ConcatColumns has >1 column and you use Desc, only the last
    '   column gets sorted
    ' Limit (optional) places a limit on how many items are placed into the concatenated string.
    '   The Limit argument works as a TOP N qualifier in the SELECT clause
    
    Dim rs As DAO.Recordset
    Dim SQL As String
    Dim ThisItem As String
    Dim FieldCounter As Long
    
    On Error GoTo ErrHandler
    
    ' Initialize to Null
    
    DConcat = Null
    
    ' Build up a query to grab the information needed for the concatenation
    
    SQL = "SELECT " & IIf(Distinct, "DISTINCT ", "") & _
            IIf(Limit > 0, "TOP " & Limit & " ", "") & _
            ConcatColumns & " " & _
        "FROM " & Tbl & " " & _
        IIf(Criteria <> "", "WHERE " & Criteria & " ", "") & _
        Switch(Sort = "Asc", "ORDER BY " & ConcatColumns & " Asc", _
            Sort = "Desc", "ORDER BY " & ConcatColumns & " Desc", True, "")
        
    ' Open the recordset and loop through it:
    ' 1) Concatenate each column in each row of the recordset
    ' 2) Concatenate the resulting concatenated rows in the function's return value
    
    Set rs = CurrentDb.OpenRecordset(SQL)
    With rs
        Do Until .EOF
            
            ' Initialize variable for this row
            
            ThisItem = ""
            
            ' Concatenate columns on this row
            
            For FieldCounter = 0 To rs.Fields.Count - 1
                ThisItem = ThisItem & Delimiter2 & Nz(rs.Fields(FieldCounter).Value, "")
            Next
            
            ' Trim leading delimiter
            
            ThisItem = Mid(ThisItem, Len(Delimiter2) + 1)
            
            ' Concatenate row result to function return value
            
            DConcat = Nz(DConcat, "") & Delimiter1 & ThisItem
            .MoveNext
        Loop
        .Close
    End With
    
    ' Trim leading delimiter
    
    If Not IsNull(DConcat) Then DConcat = Mid(DConcat, Len(Delimiter1) + 1)
    
    GoTo Cleanup

ErrHandler:
    
    ' Error is most likely an invalid database object name, or bad syntax in the Criteria
    
    DConcat = CVErr(Err.Number)
    
Cleanup:
    Set rs = Nothing
    
End Function

Open in new window


The DConcat function will produce a concatenated list of all of the values for the specified column(s) in a specified table or query pertaining to the records that meet the criteria specified in the Criteria argument (or all rows in the table/query if no criteria are specified).

The DConcat function takes the following arguments:
ConcatColumns are the detail columns whose values are to be concatenated.  This will usually be a single column, but can be multiple columns.  If more than one column, use commas to delimit the column names.  If column names contain anything other than letters, numerals, or underscores, column names must be enclosed in square brackets
Tbl is the name of table or query from which the data are taken.  If the table/query name contains anything other than letters, numerals, or underscores, the name must be enclosed in square brackets
(Optional) Criteria specifies the criteria to be applied in selecting and grouping the detail information.  To specify multiple criteria, and And or Or.  Be sure to use single-quotes around text values and # around dates/times.  I recommend using square brackets around all column names
(Optional) Delimiter1 is the delimiter to be used when concatenating each result row to the return value.  The default value is comma + space
(Optional) Delimiter2 is the delimiter to be used when concatenating the values in the ConcatColumns for each result row.  The default value is comma + space
(Optional) Distinct indicates whether all instances of the detail information is returned, or just the distinct items.  True by default
(Optional) Sort indicates whether the detail data are sorted before concatenation.  Use "Asc" for ascending (default) or "Desc" for descending.  Any other values are ignored.  Please note that if multiple columns are specified in ConcatColumns and if you use
(Optional) Limit specifies an upper limit for how many detail rows are concatenated.  If less than one or omitted, then no limit is applied

Each time DConcat is called, the code will run a query structured like this:

SELECT {DISTINCT} {TOP <Limit>} <ConcatColumns>
FROM <Tbl>
{WHERE <Criteria>}
{ORDER BY <ConcatColumns> Asc|Desc}

<xxx> ---> function arguments
{xxx} ---> optional items

Open in new window


The code then loops through that result set:
If ConcatColumns specifies more than one column, the column values for each returned row are concatenated using the Delimiter2 value as the delimiter
Those concatenated rows are then concatenated into a larger string, using the Delimiter1 value as the delimiter

3. Implementing DConcat in Your Access Projects


To implement DConcat in your Access project, all you need to do is add the source code above to a "regular" VBA module (i.e., not a class module, nor a code module for a Form or Report).  Once you have done this, you will be free to use the function in your queries, forms, or reports in the Access UI, or you may call DConcat from your other procedures in the VBA project.

4. Examples



 DConcat.mdb

The attached sample file, "DConcat.mdb", includes sample data for you to practice using this function.  The database includes the following five example queries:

Simple list of all accounts (qryAllAccounts)

SELECT DConcat("Account","Sample") AS Accounts
FROM Sample
GROUP BY DConcat("Account","Sample");


Returns:
Accounts
-----------------------------------------------------------------------------
Acct1, Acct10, Acct2, Acct3, Acct4, Acct5, Acct6, Acct7, Acct8, Acct9

Open in new window


List of projects for each account (qryProjectsByAcct)

SELECT Account, DConcat("ProjectNum","Sample","[Account] = '" & [Account] & "'") AS Projects
FROM Sample
GROUP BY Account;


Returns:
Account   Projects
-----------------------------------------------------------------------------
Acct1     11111, 49632
Acct10    50396
Acct2     38278, 42987, 51304
Acct3     42805, 48450, 51853, 51994
Acct4     50167, 51908
Acct5     50819
Acct6     51906
Acct7     54883
Acct8     53683
Acct9     55444

Open in new window


List of users for each account and project (qryUsersByAcctAndProject)

SELECT Account, ProjectNum, DConcat("User","Sample","[Account] = '" & [Account] & 
    "' And [ProjectNum] = " & [ProjectNum]) AS Users
FROM Sample
GROUP BY Account, ProjectNum;


Returns:
Account   ProjectNum   Users
-----------------------------------------------------------------------------
Acct1     11111        User2
Acct1     49632        User1, User2
Acct10     50396        User1
Acct2     38278        User1, User13, User15, User17, User18, User2, User3, User4, User5, User8
Acct2     42987        User1, User10, User13, User15, User2, User3, User5, User8
Acct2     51304        User1
Acct3     42805        User1, User16, User2, User5, User6
Acct3     48450        User1, User3, User5
Acct3     51853        User1
Acct3     51994        User1
Acct4     50167        User1, User7
Acct4     51908        User1, User3
Acct5     50819        User1, User10, User8, User9
Acct6     51906        User1, User3
Acct7     54883        User1, User3
Acct8     53683        User1, User10, User11, User12, User13, User3
Acct9     55444        User1, User10, User12, User14

Open in new window


List of tasks and users by project (qryTasksAndUsersByProject)

SELECT ProjectNum, DConcat("Task,User","Sample","[ProjectNum] = " & [ProjectNum],
    "; ",", ") AS Users
FROM Sample
GROUP BY ProjectNum;


Returns:
ProjectNum   Users
-----------------------------------------------------------------------------
11111        Project Management, User2
38278        Analysis & Model Config Design, User1; Analysis & Model Config Design, User2; Benchmarking Analysis & Findings, User1; Benchmarking Analysis & Findings, User2; Benchmarking Model Building, User1; Benchmarking Model Building, User2; Central Administrator Training, User13; Data Collection, User1; Data Collection, User2; Data Collection Prep, User1; Data Integration, User1; Data Integration, User2; Data Integration, User3; Findings and Recommendations, User1; Findings and Recommendations, User2; Implementation Study, User1; Implementation Study, User15; Implementation Study, User17; Implementation Study, User2; Implementation Study Prep, User1; Implementation Study Prep, User15; Implementation Study Prep, User17; Implementation Study Prep, User2; Implementation Study Prep, User8; Installation, User4; Model Building, User1; Model Building, User2; Model Building, User5; Model Design, User1; Model Design, User2; Planning, User1; Planning, User2; Study Analysis and Findings, User1; Study Analysis and Findings, User18; Study Analysis and Findings, User2
42805        Analysis Add On, User1; Analysis and Model Configuration Design, User2; Analysis and Model Configuration Design, User6; Data Collection, User16; Data Collection, User2; Data Collection, User6; Data Collection Preparation, User16; Data Collection Preparation, User2; Data Collection Preparation, User6; Model Template Delivery and Orientation, User1; Model Template Review, User1; Model Template Review, User5; POC Analysis and XML File Delivery, User1; POC Model Load, User1
42987        Data Integration, User1; Data Integration, User2; Data Integration, User3; Findings and Recommendations, User1; Findings and Recommendations, User2; Installation, User1; Model Building, User1; Model Building, User2; Model Building, User5; Model Configuration Design, User1; Model Configuration Design, User2; Planning, User1; Planning, User2; Rollout - First Market Findings, User1; Rollout - Preparation/Planning, User1; Rollout - Preparation/Planning, User2; Rollout - Support First Market, User1; Rollout - Support First Market, User15; Rollout - Support First Market, User8; WFM - Develop Scheduling Program, User1; WFM - Develop Scheduling Program, User2; WFM - Process Design, User1; WFM - Process Design, User2; WFM - Tool Construction, User1; WFM - Tool Construction, User2; WFM - Tool Construction, User5; WFM - Tool Installation & Training, User1; WFM - Tool Installation & Training, User10; WFM - Tool Installation & Training, User13
48450        Analysis Support, User1; Data Integration, User1; Data Integration, User3; Existing Model Information Capture, User1; Extended Model QA, User1; Matrix, User1; Model Build, User1; Model Build, User5; Model Design, User1; Model Design, User5; Model QA, User1; MSI Delivery, User1; Partial Model Delete, User1; Planning, User1; Role Allocation Matrix Revision, User1; Variance Report I, User1; Variance Report II, User1
49632        Project Management, User2; WES PM, User1
50167        Configuration, User1; Configuration, User7; Final Analysis, User1; Planning and Approach, User7; POC Monitoring, User1; POC Monitoring, User7
50396        WES Project Manager, User1
50819        Analysis and Model Configuration Design, User1; Call Center (Branch) Set-Up and Configuration, User1; Data Collection, User1; Data Collection, User8; Data Collection Prepartation, User1; End User Training, User10; Forecast Build, User9; Forecast Install, User9; Plan Install, User9; Planning, User9; Project Planning, User1; Schedule Build, User9; Schedule Install, User9
51304        Transactions Report, User1
51853        Auto Generate Reports, User1; Consolidate Report Date, User1; Extract Branch Attributes, User1; Monitor Report Progress, User1; Scheduling Query, User1
51906        Configuration, User1; DAM, User3; Planning and Approach, User1
51908        DAM, User1; DAM, User3; Foundation, User1; Foundation, User3; Planning and Approach, User1; Process Analysis Configuration, User1
51994        ABR Analysis (Change Order IIII - 54644), User1; Change In Sales Averaging Period, User1; Change In Wait Time Analysis, User1; CSR Team Consolidation (Change Order II - 54134), User1; Extended Wait Time Pilot (Change Order I - 54135), User1; Wait Time Pilot Prepartation, User1; Wait Time Pilot Support and Data Analysis, User1
53683        Analysis and Model Configuration Design, User1; Data Collection, User1; Data Collection Preparation, User1; Forecast Build, User11; Forecast Build, User3; Forecasting Training, User13; Planning, User1; Planning, User11; Planning, User3; Scheduling, User12; Train the Trainer Training, User10
54883        DAM, User1; DAM, User3; Process Analysis Configuration, User1
55444        Adherence Adapter Development and Test, User1; Adherence Adapter Development and Test, User12; Analysis and Model Configuration Design, User1; Central User Training, User14; Forecast, User14; Forecast Build, User1; Forecast Build, User12; Forecast Build, User14; Planning, User1; Scheduling, User1; Scheduling, User12; Scheduling, User14; Train the Trainer Training, User10

Open in new window


List of up to 3 tasks and users by project (qryTasksAndUsersByProjectLimit3)

SELECT ProjectNum, DConcat("Task,User","Sample","[ProjectNum] = " & [ProjectNum],
    "; ",", ",True,"Asc",3) AS Users
FROM Sample
GROUP BY ProjectNum;


Returns:
ProjectNum	Users
-----------------------------------------------------------------------------
11111        Project Management, User2
38278        Analysis & Model Config Design, User1; Analysis & Model Config Design, User2; Benchmarking Analysis & Findings, User1
42805        Analysis Add On, User1; Analysis and Model Configuration Design, User2; Analysis and Model Configuration Design, User6
42987        Data Integration, User1; Data Integration, User2; Data Integration, User3
48450        Analysis Support, User1; Data Integration, User1; Data Integration, User3
49632        Project Management, User2; WES PM, User1
50167        Configuration, User1; Configuration, User7; Final Analysis, User1
50396        WES Project Manager, User1
50819        Analysis and Model Configuration Design, User1; Call Center (Branch) Set-Up and Configuration, User1; Data Collection, User1
51304        Transactions Report, User1
51853        Auto Generate Reports, User1; Consolidate Report Date, User1; Extract Branch Attributes, User1
51906        Configuration, User1; DAM, User3; Planning and Approach, User1
51908        DAM, User1; DAM, User3; Foundation, User1
51994        ABR Analysis (Change Order IIII - 54644), User1; Change In Sales Averaging Period, User1; Change In Wait Time Analysis, User1
53683        Analysis and Model Configuration Design, User1; Data Collection, User1; Data Collection Preparation, User1
54883        DAM, User1; DAM, User3; Process Analysis Configuration, User1
55444        Adherence Adapter Development and Test, User1; Adherence Adapter Development and Test, User12; Analysis and Model Configuration Design, User1

Open in new window




=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
If you liked this article and want to see more from this author,  please click here.

If you found this article helpful, please click the Yes button near the:

      Was this article helpful?

label that is just below and to the right of this text.   Thanks!
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
46
Ask questions about what you read
If you have a question about something within an article, you can receive help directly from the article author. Experts Exchange article authors are available to answer questions and further the discussion.
Get 7 days free