0

This seems like it should be really simple but I am struggling to find a solution.

I have a large table containing project team members by role, one row for each team member on each project. Here is a simplified sample of what it looks like:

Project Role Team Member
Alpha Project Manager Will
Alpha Business Analyst John
Alpha Business Analyst Amy
Alpha Developer Sally
Alpha Developer Joe
Alpha Developer Pete
Beta Project Manager Robert
Beta Business Analyst John
Beta Developer Frank
Beta Developer Bruce

As you can see, our projects often have multiple team members per role, and I'm trying list the appropriate names under a column for each role, one row per project:

Project Project Manager Business Analyst Developer
Alpha Will John
Amy
Sally
Joe
Pete
Beta Robert John Frank
Bruce

When I try to use a crosstab query in MS Access, I have to choose either the first or last name; I cannot find a way to list all of them.

When I try a pivot table in MS Excel, I can get each role to appear as a column, but the names still appear in the first column rather than within the column based on the team member's role.

A solution in either MS Access or MS Excel would meet my needs.

Thank you!

Dan
  • 1
  • How many possible roles? Probably need VBA. Check out Allen Browne's ConcatRelated function. Would have to call the function for each Role column you want to generate. http://allenbrowne.com/func-concat.html – June7 Apr 26 '22 at 02:01
  • In Excel you could do this using Power Query – Ron Rosenfeld Apr 26 '22 at 02:21
  • This is a problem of presentation. In MsAccess, one can design a report to do it (then the result is RTF/PDF, etc). How do you want to see the results? If you just want tabular data (with two rows, where Bussines Analysts John and Amy are separated by a line-break, then VBA is the best way). – tinazmu Apr 26 '22 at 05:33
  • Tested in Excel and it works nicely, no VBA. Built this expression: `= Table.Group(Source, {"Project", "Role"}, {{"Grp", each Text.Combine([Team Member], "#(lf)"), type text}})` – June7 Apr 26 '22 at 10:13
  • @June7 this looks like it would be my ideal solution! can you share your test file? excel is telling me there something wrong with the formula, so I'm doing something wrong but don't know what. – Dan Apr 26 '22 at 14:27
  • I forgot to mention my attempt was with PowerQuery. Do you have PowerQuery add-in installed? – June7 Apr 26 '22 at 16:33
  • @June7 yes, i have Power Query but I am a beginner. I assume your statement needs to be added using the Advanced Editor? – Dan Apr 26 '22 at 20:41
  • okay, I think I got it to work but my end result is a total of 3 columns (Project, Role, Grp) where I have one row for each role, whereas the end result I am looking for 4 columns (Project, Project Manager, Business Analyst, Developer) with one row for each project. – Dan Apr 26 '22 at 21:13
  • No, I did not use Home > Advanced Editor. I used the PQ Transform tab and its tools to manually build. I followed an on-line tutorial for basics and with a little trial and error adjustment, ended up with working expression. I copy/pasted your sample table to Excel sheet, opened PQ wizard and progressed from there. I did have to select Advanced on the GROUP BY window so could select Project and Role fields. Now, to reproduce this, all I have to do is copy/paste the working expression I posted into the PQ formula bar. – June7 Apr 26 '22 at 21:15
  • Oops, it does not create a column for each role although names are combined. I am trying again. – June7 Apr 26 '22 at 21:19
  • Now I get the Role columns and names are combined but can't figure out how to get comma or line feed in the result. `= Table.Pivot(Source, List.Distinct(Source[Role]), "Role", "Team Member", Text.Combine)`. Apparently, need intermediate steps as shown by @RonRosenfeld answer. I was showing only the final. – June7 Apr 26 '22 at 21:54
  • Got it. 3 steps. Source: `= Excel.CurrentWorkbook(){[Name="Table1"]}[Content]`. Grouped Rows: `= Table.Group(Source, {"Project", "Role"}, {{"Grp", each Text.Combine([Team Member], "#(lf)"), type text}})`. Pivoted Column: `= Table.Pivot(#"Grouped Rows", List.Distinct(#"Grouped Rows"[Role]), "Role", "Grp", List.First)`. Then Home > Close & LoadTo. – June7 Apr 26 '22 at 21:55

2 Answers2

2

To do this in PQ, you have to

  • group by Project
  • group each sub-table by Role
  • Combine the list of Team members using the LF character
  • Pivot each subtable on the Role column

To use PQ if your original table was in Excel

  • Select some cell in your original table
  • Data => Get&Transform => From Table/Range or From within sheet
  • When the PQ UI opens, navigate to Home => Advanced Editor
  • Make note of the Table Name in Line 2 of the code.
  • Replace the existing code with the M-Code below
  • Change the table name in line 2 of the pasted code to your "real" table name
  • Examine any comments, and also the Applied Steps window, to better understand the algorithm and steps
  • If you are loading this back to Excel, be sure to enable word-wrap on the cells with the line feeds in order to make display properly.

If your original table is from another source, you will need to access that source from PQ -- there are numerous connectors you can substitute for the first line in the code below

let
    Source = Excel.CurrentWorkbook(){[Name="TeamMembers"]}[Content],
    #"Changed Type" = Table.TransformColumnTypes(Source,{
        {"Project", type text}, {"Role", type text}, {"Team Member", type text}}),

//Group by Project
//  Sub group by Role
//  Pivot on Role
    #"Grouped Rows" = Table.Group(#"Changed Type", {"Project"}, {
        {"roles", each Table.Pivot(
        Table.Group(_,"Role",{
            {"role", each Text.Combine([Team Member],"#(lf)")}
            }),List.Distinct([Role]),"Role","role")}
        }),

//Expand all columns
    roles = List.Distinct(#"Changed Type"[Role]),
    #"Expanded roles" = Table.ExpandTableColumn(#"Grouped Rows", "roles", roles,roles),

//set data types
    typeIt = Table.TransformColumnTypes(#"Expanded roles", List.Transform(Table.ColumnNames(#"Expanded roles"), each {_, type text}))
in
    typeIt

enter image description here

Ron Rosenfeld
  • 53,870
  • 7
  • 28
  • 60
  • I tried your code but I lose the line feeds when loading to worksheet. Looks great in Power Query view. – June7 Apr 27 '22 at 05:28
  • @June7 you just need to enable word wrap on those cells in Excel. Excel has always required that in order to show line feeds. My result table is a screenshot from Excel itself, – Ron Rosenfeld Apr 27 '22 at 08:49
  • Oh, of course. Might add that as a bullet item in your answer. – June7 Apr 27 '22 at 16:42
  • @June7 I did at your suggestion. But it wasn't clear to me what the OP would be using for display purposes. – Ron Rosenfeld Apr 27 '22 at 17:41
0

And for completeness here is an Access solution:

enter image description here

The problem (highlighted) is Access can only put 1 summary inside each cell of the cross-tab but there are multiple Team Members filling some roles (high-lighted). To get around this we recalculate TeamMember to be a rich text list like: Sally<br/>Joe<br/>Pete Fortunately there is already a function for doing this here: Microsoft Access condense multiple lines in a table

Public Function GetList(SQL As String _
                            , Optional ColumnDelimeter As String = ", " _
                            , Optional RowDelimeter As String = vbCrLf) As String
                           
' make sure to add a reference to the latest Microsoft ActiveX Data Objects x.x Library For me that was 6.1
Const PROCNAME = "GetList"
Const adClipString = 2
Dim oConn As ADODB.Connection
Dim oRS As ADODB.Recordset
Dim sResult As String

On Error GoTo ProcErr

Set oConn = CurrentProject.Connection
Set oRS = oConn.Execute(SQL)
sResult = oRS.GetString(adClipString, -1, ColumnDelimeter, RowDelimeter)
If Right(sResult, Len(RowDelimeter)) = RowDelimeter Then
    sResult = Mid$(sResult, 1, Len(sResult) - Len(RowDelimeter))
End If

GetList = sResult
oRS.Close
oConn.Close

CleanUp:
    Set oRS = Nothing
    Set oConn = Nothing

Exit Function
ProcErr:
    ' insert error handler
    Resume CleanUp

End Function

The rich text format in access controls only works with the long text format. So start by converting Role and TeamMember to long text in the table. Unfortunately, GetList must return a string so we can't build the report directly from a query that uses GetList. Instead we build the report based on a similar query:

enter image description here

enter image description here

set the appropriate report controls to richtext then replace the reports(click on red) record source(make sure its reports record source that is changed) with the query that uses getlist. see http://allenbrowne.com/ser-63.html for a another work around involving a union query (memo= long text)

For the final query replace Team Member with a calculated field using GetList.

enter image description here

TeamMember: First(GetList("SELECT TeamMember FROM Sheet1 WHERE Project = '" & [Project] & "' AND Role = '" & [Role] & "'","<br/>","<br/>"))
mazoula
  • 1,221
  • 2
  • 11
  • 20
  • 1
    Rich text is not necessary. I just tested a CROSSTAB using AllenBrowne's ConcatRelated function with short text fields and it works. `First(ConcatRelated("TeamMember","ProjectTeams","Project='" & [Project] & "' AND Role='" & [Role] & "'", "", Chr(13) & Chr(10)))`. – June7 Apr 27 '22 at 16:26
  • And I built a report using CROSSTAB query as RecordSource. – June7 Apr 27 '22 at 16:38
  • Good to know, ConcatRelated returns a variant which must have been interpreted properly. I didn't test variant. So for those who missed it try returning variant from Grouplist or replace grouplist with ConcatRelated and you should be able to build the report directly from the query with the calculated field – mazoula Apr 28 '22 at 05:00