Excel Sumproduct from 2 tables with multiple matching criteria - excel

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])

Related

Excel formul to count Table Range using month from date text

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.

Generate a master table based on a unique identifier with columns for the data from each table/worksheet

I am currently working in Excel but I am willing to consider solutions in (free) database software. The data I have is organized by time period, one worksheet for each period. The worksheets all have the same column structure.
Here is an example of the data from the 2017-Q1 worksheet:
| Time Period | Product ID | Prod. Category ID | Product Scale |
| 2017 - Q1 | 0012345678 | 012345 | 3 |
| 2017 - Q1 | 0023456789 | 012345 | 1 |
| 2017 - Q1 | 0033333588 | 022235 | 3 |
| 2017 - Q1 | 0123333333 | 022235 | 1 |
Here is an example of the data from the 2017-Q2 worksheet:
| Time Period | Product ID | Prod. Category ID | Product Scale |
| 2017 - Q2 | 0012345678 | 012345 | 5 |
| 2017 - Q2 | 0033333588 | 022235 | 7 |
| 2017 - Q2 | 0123333333 | 025444 | 5 |
| 2017 - Q2 | 0145555578 | 025444 | 1 |
The things that I am particularly focused on are:
Product ID 0023456789 does not appear in 2017-Q2, so the corresponding column in the master worksheet should show something (it could be blank, it could be NULL, it could be N/A, it doesn't matter as long as it is always the same.) The same thing needs to happen when a Product ID first appears in a later time period, the time periods before that should have the same N/A (or NULL, or blank, or whatever) This means that in order to form the master worksheet, I need to make a consolidated list of all of the Product IDs from all of the time periods, since not all of the Product IDs are present in every time period.
Product ID 0123333333 changed Prod. Category ID, so I need to always pull at least the Prod. Category ID column and the Product Scale column for each time period, just to be sure if it has changed or not.
I want to generate a master worksheet as follows (abbreviating column names just for space constraints here in this website):
| Product ID | 2017-Q1PCID | 2017-Q1 PS | 2017-Q2 PCID | 2017-Q2 PS |
| 0012345678 | 012345 | 3 | 012345 | 5 |
| 0023456789 | 012345 | 1 | N/A | N/A |
| 0033333588 | 022235 | 3 | 022235 | 7 |
| 0123333333 | 022235 | 1 | 025444 | 5 |
| 0145555578 | N/A | N/A | 025444 | 1 |
I have 12 different time period worksheets, so the master worksheet would need to first make a master list of unique Product IDs and then have 24 additional columns, because there are 2 columns for each time period (one column for the Product Category ID and another for the Product Scale ID, for that time period.) Furthermore, I should mention that each time period worksheet has 8,000-12,000 Product ID records, so ideally the solution should be able to handle thousands of rows fairly quickly.
TLDR : use index() match() .
Assuming the table 1 is located at "Sheet1" and the "Product ID" in the last table is located in cell A1, put this in B2 :
=IFERROR(INDEX('2017-Q1'!C:C,MATCH(A2,'2017-Q1'!B:B,0)),"")
and in C2 :
=IFERROR(INDEX('2017-Q1'!D:D,MATCH(A2,'2017-Q1'!B:B,0)),"")
and drag downwards.
Idea : 'load' the answer if the Product ID is matched in the source sheet, and put blank if no Product ID match is found.
Hope it helps. (:

Using Slicers/Filters in a Table

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.

Copy data from one sheet (data) to another (summary)

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.

excel sorting/filtering/and counting data automatically using macro

so I have an excel datasheet (sheet1) with a few columns.
company | product code | date | name |
and a few rows that fill all the above details.
e.g.
company 1 | adar1 | 01/sep/14 | title1 |
company 2 | adar2 | 04/sep/14 | title2 |
company 3 | adar1 | 09/sep/14 | title3 |
company 4 | adar3 | 10/oct/14 | title4 |
company 1 | adar2 | 18/oct/14 | title5 |
company 2 | adar1 | 04/nov/14 | title6 |
company 3 | adar3 | 09/nov/14 | title7 |
company 4 | adar3 | 01/nov/14 | title8 |
etc.
As you see the company names differ.
the product names differ.
dates vary
and name/titles we do not care about.
what I want to do is to apply some sort of filter that can do the following:
PER MONTH, to count the same product names PER COMPANY, and create rows/columns on a new sheet (sheet2) with the following format:
company | quantity | product code | date in specific format below |
company 1 n adar1 201409
company 1 n adar2 201409
company 2 n adar1 201409
company 1 n adar2 201409
company 1 n adar1 201410
company 1 n adar2 201411
etc etc
meaning company 1 used n number of adar1 product, n number of adar2 product on september 2014 (201409)
company 2 used n number of adar1 and n of adar3 on september 2014 and october 2014 etc,
I believe you get the point.
I can do all the above with pivot tables but I would like to know whether there is a macro or some function/code that can do all the above automatically on sheet2 upon entering new values on sheet1.
Thank You.
I would go about this with a SQL something like this:
SELECT * FROM (SELECT s.company, COUNT(s.company) as quantity, s.product_code, "20"&right(s.date,2)&iif(right(left(s.date,6),3)="jan","01",iif(right(left(s.date,6),3)="feb","02","03"))&left(s.date,2) as new_date FROM [Sheet1$] as s GROUP BY s.company,s.product_code,s.date) ORDER BY new_date DESC
To use it see Data->External Data->From other sources->Microsoft Query or use my SQL AddIn: http://blog.tkacprow.pl/?page_id=130.
The SQL above is still missing a mapping for months mar-dec. I am sure you will be able to add them by yourself.
NOTE1: To refresh this when values are updated you can add a macro button that refreshes the table or override Worksheet Change event in VBA.

Resources