Example I will be using is a list of people who I buy sweets off, tabled into preference and sortable by category. I have a table of values in the format:
Option 1|Option 2|Option 3|Catagory 1 | Category 2 |
--------|--------|--------|-----------|------------|
Bob | Mary | Jane | Candy | Sherbert |
James | Bob | Jane | Choc | Cadbury Bar|
Jane | | | Candy | Haribo |
Jane | Mary | James | Candy | Millions |
... | ... | ... | ... | ... |
I want to be able to use dropdown boxes or slicers to pick the name and first category and show a filtered list of the final category (2) and the "rank" based on them being primary, secondary or tertiary option, for example as I select Jane:
Name |^| Catagory 1|^| Category 2 | Rank |
-------|-------------|------------|-------|
Jane | Candy | Sherbert | 3 |
| Haribo | 1 |
| Millions | 1 |
or:
Name |^| Catagory 1|^| Category 2 | Rank |
-------|-------------|------------|-------|
Jane | Choc | Cadbury Bar| 3 |
(depending on how it is picked)
Comment Questions:
What Excel version are you using? 2013
Is this no-vba task? Preferably no-vba, if you chose to answer in VBA please consider that it needs to be tailored to explaining how to implement the solution to someone with limited excel-vba experience.
Can you use two tables (1 input, 1 output) or you want the layout to change dynamically? Using 2 tables is fine, feel free to impress by using dynamic tables if you so wish (dynamic tables may win the bounty over an answer without them)
Can you explain how field Rank is calculated? For example Rank=3 in case of Jane | Choc | Cadbury Bar ? In the row for Cadbury bar, Jane is the 3rd option, hence rank 3. If she was second she would be rank 2 or if first, rank 1.
You have Name (Jane) in the first three columns. May the same Name (Jane) appear in any of the three columns? Can you explain the reason of having names in three columns? If the name may appear in any of the column then they have to be unique - is that so? Consider the data as lines first, perhaps reading right-to-left may help. The Category 2 column is the only column that contains unique values. Any name can appear any number of times in the first 3 columns but none of the names can be duplicated in the same row: Jane cannot be the option 1, 2 and 3 for a single item (for example)
Here's a VBA solution which uses standard Excel components
Your workbook should have two sheets named SourceData and Calculation with two tables having the same names as in pictures below:
Now add VBA code that will process your data. Press Alt+F11 to open the code editor. Add a new module:
Now in that module insert this code:
Const SOURCE_DATA_SHEET As String = "SourceData"
Const SOURCE_DATA_TABLE As String = "SourceData"
Const CALCULATION_SHEET As String = "Calculation"
Const CALCULATION_TABLE As String = "Calculation"
Const S_OPTION_1 As Integer = 1
Const S_OPTION_2 As Integer = 2
Const S_OPTION_3 As Integer = 3
Const S_CATEGORY_1 As Integer = 4
Const S_CATEGORY_2 As Integer = 5
Const C_WIDTH As Integer = 4
Const C_NAME As Integer = 1
Const C_CATEGORY_1 As Integer = 2
Const C_CATEGORY_2 As Integer = 3
Const C_RANK As Integer = 4
Function GetTable(sheetName As String, tableName As String) As ListObject
Set GetTable = Worksheets(sheetName).ListObjects(tableName)
End Function
Sub ClearTable(dataTable As ListObject)
dataTable.AutoFilter.ShowAllData
dataTable.Sort.SortFields.Clear
If dataTable.ListRows.Count >= 1 Then
dataTable.DataBodyRange.Delete
End If
End Sub
Sub InsertRow(ByRef dataTable As ListObject, ByRef dataRow As Variant)
dataTable.ListRows.Add
dataTable.ListRows(dataTable.ListRows.Count).Range = dataRow
End Sub
Sub CalculateRanks(ByRef dataTable As ListObject, ByRef destinationTable As ListObject)
Dim newRow(1 To C_WIDTH) As Variant
Dim nameValue As Variant
For Each dataRow In dataTable.ListRows
With dataRow
newRow(C_CATEGORY_1) = .Range(S_CATEGORY_1).Value
newRow(C_CATEGORY_2) = .Range(S_CATEGORY_2).Value
For Each optionNumber In Array(S_OPTION_1, S_OPTION_2, S_OPTION_3)
nameValue = .Range(optionNumber).Value
If nameValue <> "" Then
newRow(C_NAME) = nameValue
newRow(C_RANK) = optionNumber
InsertRow destinationTable, newRow
End If
Next optionNumber
End With
Next dataRow
End Sub
Sub Main()
Dim sourceTable As ListObject
Dim destinationTable As ListObject
Set sourceTable = GetTable(SOURCE_DATA_SHEET, SOURCE_DATA_TABLE)
Set destinationTable = GetTable(CALCULATION_SHEET, CALCULATION_TABLE)
ClearTable destinationTable
CalculateRanks sourceTable, destinationTable
End Sub
On your Calculation sheet, add a button that you will use to run the code. Under "Developer" tab (if you don't see this tab, go to Excel Options / Customize Ribbon / Check the "Developer" checkbox in "Main Tabs" list) insert a button form control:
And link your macro Main to the button:
You're done! Now every time you click the button, the Calculation table will get refreshed. You may use filters to show the desired content.
Here's your dynamic table, no VBA solution. You just need to add a single helper column.
First, pick some sort of delimiter. I chose "!". .
Next, in an adjacent column put the following big honking formula:
[Name of person]|
----------------|
=IFERROR(LEN(LEFT(CONCATENATE("!",A2,"!",B2,"!",C2,"!"),FIND(CONCATENATE("!",$F$1,"!"),CONCATENATE("!",A2,"!",B2,"!",C2,"!"))-1))-LEN(SUBSTITUTE(LEFT(CONCATENATE("!",A2,"!",B2,"!",C2,"!"),FIND(CONCATENATE("!",$F$1,"!"),CONCATENATE("!",A2,"!",B2,"!",C2,"!"))-1),"!",""))+1,"")|
...|
What this does is tell you how many exclamation points occurred in the concatenated list of names before the value in F1 (which, in my workbook is the column header where I put "Jane"). (It then adds one, so that the top spot is "1", and not "0"). If the name in the header row is not found, the cell is left blank.
Once you've got this in place, your process for getting your filtered list is:
1. Enter the name of the person in F1.
2. Filter out blanks from F1.
3. Filter Category 1 as desired.
Note that in my formulas, the columns match up as follows:
Col A |Col B |Col C |Col D |Col E |Col F |
Option1|Option2|Option3|Category1|Category2|[Name of person]|
These formulas produce the following output:
Option 1|Option 2|Option 3|Category 1 | Category 2 |Jane |
--------|--------|--------|-----------|------------|-----|
Bob | Mary | Jane | Candy | Sherbert |3 |
James | Bob | Jane | Choc | Cadbury Bar|3 |
Jane | | | Candy | Haribo |1 |
Jane | Mary | James | Candy | Millions |1 |
... | ... | ... | ... | ... |... |
Or, for James...
Option 1|Option 2|Option 3|Category 1 | Category 2 |James |
--------|--------|--------|-----------|------------|----- |
Bob | Mary | Jane | Candy | Sherbert | |
James | Bob | Jane | Choc | Cadbury Bar|1 |
Jane | | | Candy | Haribo | |
Jane | Mary | James | Candy | Millions |3 |
... | ... | ... | ... | ... |... |
If you are familiar with using SQL statements in Excel, it is very easy. You can use the approach I presented here Filter ShowDetails of PivotTable. If you are not, do not worry, just download my file, feed your table and use it.
The idea in SQL is:
Select Name=[Option 1],[Category 1],[Category 2],Rank=1 union all
Select Name=[Option 2],[Category 1],[Category 2],Rank=2 union all
Select Name=[Option 3],[Category 1],[Category 2],Rank=3
* I changed the headings for using SQL statements in Excel does not allow column names ending with numbers - stupid bug learnt the hard way.
It produces the following output table:
With such an output table the following part is very easy. You do not even need slicers. Just use filters of the table to get the desired results:
You might be willing to have your output table without rows which do not contain names (i.e. row 7 or 11). Then simply use SQL clause:
WHERE Name is not null
You can adjust this code so that the output table generates automatically on file open.
Related
I have an excel workbook where I am trying to count the number of apples in a named table. The workbook has multle sheets each named Jan, Feb, Mar, etc. with a corresponding table range of the same name.
My main worksheet has a list of months as columns and fruit as rows, I want to use a countif or suitable function to count the number of each fruit per month using the column heading as the worksheet portion of the formula.
This is what I have tried, this works, but has to be manually coded for each month, i would prefer it be more dynamic.
=COUNTIF(JAN[Labels],$A2)
Note: A2 contains the word apple
I have tried to get the month from the column date but it doesnt work
=COUNTIF(TEXT(E25,"mmm")[Labels],$A2)
This is roughly what the "master" table should look like (for clarity)
| | Jan-20 | Feb-20 | Mar-20 | .... |
| Apple | 4 | 3 | 5 | ... |
| Pear | 5 | 4 | 9 | ... |
EDIT:
Just to assist with anyone trying to help, this is roughly what a table on another sheet will look like:
| invoice | labels|
| 12535 | Apple |
| 12536 | Pear |
| 12537 | Apple |
This table would be a named table of Jan or Feb, etc.
Please try this:-
=COUNTIF(INDIRECT(TEXT(G2,"mmm")),"A")
G2 contains a proper date.
Here is a variation of the above where column 2 of the table is specified as the range to count in.
=COUNTIF(INDEX(INDIRECT(TEXT(G2,"mmm")),0,2),"B")
If you must use column captions to identify the column I would suggest MATCH to find it.
OK, so I found an answer, combining the above answer by Variatus with an additional new row.
| A | B | C | D |
1| | Jan-20 | Feb-20 | Mar-20 |
2| |JAN[Labels]|FEB[Labels]|MAR[Labels]| <- =UPPER(TEXT(B1,"MMM"))&"[Labels]"
3|Apple | 5 | 7 | 3 | <- =COUNTIF(INDIRECT(B$2),$A3)
4|Pear | 7 | 2 | 9 |
5|Orange| 1 | 3 | 3 |
So formula in B2 makes an uppercase text value of the month from B1 plus the column name Labels.
The formula in B3 (and down) counts the number of instances of the fruit from the named table & column shown in B2.
I need a help please,
I have 2 tables: one has days an employee worked on the project/proposal in a month(Jan, Feb etc.), and the other has employee rates. I need to multiple matching rates and Jan days where all the criteria are same in both table, and then show the sums if Type: "Project" and Year "2018". I need to do this sum for each month separately, but If I find how to do it for one then rest should be simple :)[n/v] means no value.
Table 1
Year | Type | Project | Employee | Jan(days) | Feb(days)
2018 | Project | Apple | John N | 6 | 7
2018 | Project | Apple | Alex T | [n/v] | 8
2017 | Proposal | Banana | Tim C | 8 | [n/v]
2017 | Proposal | Banana | Sena I | 9 | 6
2018 | Project | Kiwi | John N | [n/v] | 6
2018 | Project | Kiwi | Yen T | 4 | 5
Table 2
Year | Type | Project | Employee | Rate
2018 | Project | Apple | John N | 30
2018 | Project | Apple | Alex T | 40
2017 | Proposal | Banana | Tim C | 20
2017 | Proposal | Banana | Sena I | 30
2018 | Project | Kiwi | John N | 10
2018 | Project | Kiwi | Yen T | 40
I can do this with SUMPRODUCT() if I have both rates and days in the same table;
The formula I used if both columns are in the same table is:
=IFERROR(SUMPRODUCT(((Table[Year]= D2)(Table[Type]="Project")(Table[Jan]>0))*(Table[Rate]*Table[Jan]) ),"")
but with 2 separate tables, it doesn't return the correct value.
Thanks in advance
My first instinct would be to suggest writing an SQL query against the worksheets:
SELECT Table1.Year, Table1.Type, Table1.Project, Table1.Employee,
SUM(Table1.[Jan(days)] * Table2.Rate) AS SumJan,
SUM(Table1.[Feb(days)] * Table2.Rate) AS SumFeb,
...
FROM Table1
INNER JOIN Table2
ON Table1.Year = Table2.Year
AND Table1.Project = Table2.Project
AND Table1.Employee = Table2.Employee
WHERE Table1.Year = 2018 AND Table1.Type = 'Project'
GROUP BY Table1.Year, Table1.Type, Table1.Project, Table1.Employee
However, you are on a Mac, so using ADO and CopyFromRecordset is out. Perhaps this is possible via ODBC and Microsoft Query.
Alternatively, you might load all the data from the second sheet into a Scripting.Dictionary (a drop-in replacement for Mac is available here). Then, you could write a function something like this:
Option Explicit
Dim dict As Dictionary
Sub FillDictionary()
Set dict = New Dictionary
' load dictionary from table 2
Dim rows As Variant
rows = Table2.Range("A2").CurrentRegion.value
Dim row As Variant, key As String
For Each row In rows
'assuming the first row has the column headers, we don't want to include those in the dictionary
If row(0) <> "Year" Then
key = Join(Array(row(0), row(1), row(2), row(3)), "_")
dict(key) = row(4)
End If
Next
End Sub
Function GetSumRate(Year As Integer, RowType As String, Project As String, Employee As String, Days As Integer)
If dict Is Nothing Then FillDictionary
Dim key As String
key = Join(Array(Year, RowType, Project, Employee), "_")
If dict.Exists(key) Then GetSumRate = dict(key) * Days
End Function
Then, you could call the function from a worksheet cell:
=GetSumRate(Table[Year], Table[Type], Table[Project], Table[Employee], Table[Jan])
Ok, so I have some data that I want to convert from multiple rows to multiple columns.
My input data looks loosely like this -
+----------+----------------+-----------------+
| SKU | Attribute Name | Attribute Value |
+----------+----------------+-----------------+
| Product1 | Colour | Black |
| Product1 | Size | Large |
| Product1 | Height | 20cm |
| Product1 | Width | 40cm |
| Product2 | Colour | Red |
| Product2 | Width | 30cm |
| Product2 | Size | Large |
| Product3 | Height | 25cm |
| Product3 | Width | 30cm |
| Product3 | Length | 90cm |
| Product3 | Weight | 5kg |
| Product3 | Size | Large |
| Product3 | Colour | Blue |
+----------+----------------+-----------------+
What I want to achieve is an output like this -
+----------+--------+--------+--------+-------+--------+-------+
| SKU | Colour | Height | Length | Size | Weight | Width |
+----------+--------+--------+--------+-------+--------+-------+
| Product1 | Black | 20cm | | Large | | 40cm |
| Product2 | Red | | | Large | | 30cm |
| Product3 | Blue | 25cm | 90cm | Large | 5kg | 30cm |
+----------+--------+--------+--------+-------+--------+-------+
I've tried Pivot tables, but you can only return numeric values, rather than the text values I'm looking for.
I know I could probably achieve it using a number of step looking up values and filling them, but I feel like there should be a more simplistic way to achieve this. Maybe it's something better achieved in database rather than a spreadsheet.
Any help would be very much appreciated.
You can do this in ̶5̶ ̶s̶t̶e̶p̶s̶ 4 steps with Powerquery. This is in-built for 2016 and a free add-in from Microsoft from 2013 on wards ( or 2010 Professional Plus with Software Assurance). See info https://www.microsoft.com/en-gb/download/details.aspx?id=39379
The advantage is you can easily add rows to the source and simply refresh the query.
1) You select any cell in the range, then in 2016 Get & Transform tab, earlier version use the Powerquery tab, select data from table. A window will pop up with your range of data in:
2) Transform > Pivot column > Attribute Name column for Attribute Value in Values Column (used advanced options to select "Don't aggregate")
3) Drag columns around to desired arrangement
4) Home > Close and load to sheet
Here is a version without the column re-ordering
Edit:
Thanks to #Ron Rosenfeld for reminding me that truly null values don't need replacing with blanks as they will appear as blanks when written to the sheet.
So this step was removed:
4) Highlight columns to replace nulls in and go to transform > replace values > and
Value to Find: null
Replace With:
You could do this using a helper column and then match it using index + match. Not as simple as you thought, but does work.
1) Add helper column to your data (call it 'Helper'). =concat(SKU,'Attribute Name')
2) Use a pivot to get a unique list of SKUs in the rows so that it's easy to update once the data changes. (I'm assuming this is in column A and values start at row 4).
3) Use another pivot to get a unique list of Attributes in the columns next to the other pivot. Then you have the structure of your results. (I'm assuming the first value is in B3).
4) Index match the values of the table =index('Attribute Value', match(concat($A4,B$3),'Helper',0))
Note though that this only works when each combination of SKU and Attribute is unique.
This assumes that the data is in columns A through C:
Sub croupier()
Dim i As Long, N As Long, vA As String, vB As String, vC As String
Dim rw As Long, cl As Long
' setup column headers
Columns(2).SpecialCells(2).Offset(1).Copy Range("D1")
Columns(4).RemoveDuplicates Columns:=1, Header:=xlNo
Columns(4).SpecialCells(2).Copy
Range("E1").PasteSpecial Transpose:=True
Columns(4).SpecialCells(2).Clear
' setup row headers
Columns(1).SpecialCells(2).Copy Range("D1")
Columns(4).RemoveDuplicates Columns:=1, Header:=xlYes
' deal the data
N = Cells(Rows.Count, "A").End(xlUp).Row
For i = 2 To N
vA = Cells(i, 1)
vB = Cells(i, 2)
vC = Cells(i, 3)
cl = Rows(1).Find(what:=vB, after:=Range("A1")).Column
rw = Columns(4).Find(what:=vA, after:=Range("D1")).Row
Cells(rw, cl) = vC
Next i
End Sub
I am trying to populate one worksheet (summary) with data from a large data dump held in another worksheet (data).
This is going to be sent to budget holders within my organisation who will be able to filter on their cost centre and view their staff group.
Summary worksheet
This is structured as follows:
| | A | B | C | D |
| 1 | Please choose a cost centre: [Textfield] |
| 2 | | | | |
| 3 | POSITION | STAFF GROUP | PAY GRADE | |
| 4 | [Data will go here]
| 5 | [Data will go here]
| 6 | [Data will go here]
Data worksheet
This is structured as follows (although with 3,600 rows and not 4):
| | A | B | C | D |
| 1 | POSITION | STAFF GROUP | PAY GRADE | COST CENTRE |
| 2 | Typist | A&C | Junior | 3000 |
| 3 | Manager | A&C | Mgmt | 3200 |
| 4 | Typist | A&C | Junior | 3000 |
| 5 | Receptionist | A&C | Junior | 3000 |
The Exam Question
Using the textfield to select cost centre 3000, how can I populate the Summary worksheet with the two rows of data from the Data worksheet? Almost as if you could through a pivot table. Could this be done with formulae or will VBA be required?
METHOD #1
This is the formula way to achieve the filter.
On the Summary sheet, select the range A4:C22.
Click the Formula Bar at the top of the worksheet and paste the following formula:
=IFERROR(INDEX(Data!A2:C22,SMALL(IF($D$1=Data!$D$2:$D$22,ROW(Data!$D$2:$D$22)-ROW(Data!$D$2)+1),ROW()-3),{1,2,3}),"")
This is an array formula and must be confirmed with Ctrl+Shift+Enter.
Now, as you update the value in cell D1, the filtered results will appear.
Note: this assumes that you will have no more than 20 filtered results. If that assumption is incorrect then you will need to select a deeper range before you confirm the array-formula.
Note: this assumes that the data on the data sheet do not extend further down than row 22. If the data do, then change the references of 22 in the formula to a larger number.
METHOD #2
This is a VBA soltuion.
Place the following procedure in a standard code module:
Public Sub Barry()
Const DATA = "data!a1"
Const OUTPUT = "a3:c3"
Const FILTER_VALUE_ADDRESS = "d1"
Const FILTER_COLUMN = 4
Dim rCrit As Range, rData As Range
Set rData = Range(DATA).CurrentRegion
Set rCrit = rData.Resize(2, 1).Offset(, rData.Columns.Count + 2)
rCrit(1) = rData(1, FILTER_COLUMN): rCrit(2) = Range(FILTER_VALUE_ADDRESS)
rData.AdvancedFilter xlFilterCopy, rCrit, Range(OUTPUT)
rCrit.Clear
End Sub
Switch to the Summary sheet and press Alt-F8 to bring up the Macro Dialog.
Run Barry.
So I've been searching for the information I need and have not really been able to find a simple solution, though it seems like there should be one. Basically, I have the following
John | Doe | 123 Wallaby Ln | 00123 | | |
John | Doe | | 00123 | xxx | yy |
Jane | Doe | | 01234 | | zz |
Jane | Doe | bleep blop ln | | xx | |
And I need
John | Doe | 123 Wallaby Ln | 00123 | xxx| yy |
Jane | Doe | bleep blop ln | 01234 | xx | zz |
Basically pretty simple, I need to merge cells with the same Column 1 & Column 2 data to get as comprehensive and concise a list of data. You'd think this would be readily available through google as a simple formula but I have only found VBA solutions (I have never used VBA before, or macros for that matter so I'm not sure how to use them or fix errors in them). Any help is greatly appreciated.
Thanks in advance!
The easiest option is to merge the content of A & B in one column ( insert a new column in C)
C1 =A1&" "&B1
Roll down the formula
Sort per column C
Make sure you have descriptive name on row 1 to describe your column.
Select the complete table
Create a pivot table, drop the C column in the row section to obtain the list of unique name.
Copy the list of unique names in a new sheet
and then look at vlookupall describe here excel vlookup with multiple results to create your own function.