How to link a Table and a Pivot Table using more than one slicer in Excel? - pivot-table

I had the same problem about using slicers to link table and a Pivot Table. The ansewr provided by [jeffreyweir] was perfect! But in my case, I will need the solution for using 3 or more different slicers.
The initial solution can be found by following the link: How to link a Table and a Pivot Table using Slicers in Excel?
Many tks in advance and I hope I had follow the rules of this fantastic site in the right way! ;-)

Okay, so I've amended the code so that it only needs PivotTable slicers, and it uses those slicer setting to directly filter the Table. Note that you need to change the following lines in the code to match the names of your Table and PivotTable:
Const sPivot As String = "PivotTable1" '<= Change name as appropriate
Const sTable As String = "Table1" '<= Change name as appropriate
This code has to be pasted into the sheet module belonging to the worksheet where the Tables/PivotTables concerned are:
Option Explicit
Private Sub Worksheet_PivotTableUpdate(ByVal Target As PivotTable)
Dim sLastUndoStackItem As String
Dim sc As SlicerCache
Dim si As SlicerItem
Dim vItems As Variant
Dim i As Long
Dim lo As ListObject
Dim lc As ListColumn
Dim sTest As String
Const sPivot As String = "PivotTable1" '<= Change name as appropriate
Const sTable As String = "Table1" '<= Change name as appropriate
If Target.Name = sPivot Then
On Error Resume Next 'in case the undo stack has been wiped or doesn't exist
sLastUndoStackItem = Application.CommandBars(14).FindControl(ID:=128).List(1) 'Standard Commandbar, undo stack
'The above line doesn't seem to work in my version of O365 so we'll use the English language backup
If sLastUndoStackItem = "" Then sLastUndoStackItem = Application.CommandBars("Standard").Controls("&Undo").List(1)
On Error GoTo 0
If sLastUndoStackItem = "Filter" Or sLastUndoStackItem = "Slicer Operation" Then
Set lo = Range(sTable).ListObject
For Each sc In ActiveWorkbook.SlicerCaches
On Error Resume Next
sTest = sc.PivotTables(1).Name
On Error GoTo 0
If sTest = sPivot Then
Set lc = lo.ListColumns(sc.SourceName)
If sc.FilterCleared Then
lo.Range.AutoFilter Field:=lc.Index
Else
ReDim vItems(1 To 1)
For Each si In sc.SlicerItems
If si.Selected Then
i = i + 1
ReDim Preserve vItems(1 To i)
vItems(i) = si.Name
End If
Next si
lo.Range.AutoFilter Field:=lc.Index, Criteria1:=vItems, Operator:=xlFilterValues
ReDim vItems(1 To 1)
End If
End If
Next sc
End If
End If
End Sub
And here it is in action:

For people who have data table on a separate sheet from their pivot table, simply change
Set lo = Range(sTable).ListObject
to
Set lo = Sheets("table_sheet_name").Range(sTable).ListObject
and store the code in the pivot table sheet module.

Related

merge several bits of data from different sheets

I am trying to merge several bits of data from different sheets. After a few questions and attempts (at arrays, thanks to Stackoverflow before for help with this), i think a dictionary may be best. The final outcome is a populated table that holds all data for each individual entry. (depending on the entry the data in a column in raw data could be in different locations)
The data can include multiple entries for one person. But the data for each entry is different depending on the stage of entry. For example, if the data in column 3 would be in column 5 of the final table if a condition was stage 1, however if condition was stage 2, the same data that was in column 3 could be column 10 of the final table.
https://www.youtube.com/watch?v=o8fSY_4p93s
Following this video tutorial ondictionaires, i think i could o through the dtaa and find each individual entry and then add the corresponding variables for the case. E.g. find data for Steve Smith, if steve smith exists then if stage 1, add data to variable stagedate1, if stage2 add data to stage2date and so on. If not found add entry and find the stage.
Similar to the video, where he finds the corresponding data for each customer, and adds sales and volumes, i could do the same if an if function is round before to identify which datastage and to then put the value in correct variable.
I know there will be a million way to do it, but this seems simple and effective.
Sub Dictionary()
Dim dict As Dictionary
Set dict = ReadData()
Call WriteDict(dict)
End Sub
Function ReadData()
Dim dict As New Dictionary
Dim DataWs As Worksheet: Set DataWs = ThisWorkbook.Sheets("DATA")
Dim PoolOfWeekWs As Worksheet: Set PoolOfWeekWs = ThisWorkbook.Sheets("Pool of the week")
Dim LastrowData As Long: LastrowData = DataWs.range("A" & Rows.Count).End(xlUp).Row
Dim LastColData As Long: LastColData = DataWs.Cells(1 & DataWs.Columns.Count).End(xlToLeft).Column
Dim LastColDataString As String: LastColDataString = Split(Cells(1, LastColData).Address, "$")(1)
Dim DataRange As range: Set DataRange = DataWs.range("A1:" & LastColDataString & LastrowData)
Dim DataArr As Variant: DataArr = DataWs.range("A1:AO" & LastrowData)
Dim range As range: Set range = DataWs.range("A1").CurrentRegion
Dim i As Long
Dim CandidateProcessID As String, CandidateName As String, FirstName As String, ProcessStatus As String, PQLDate As Date, oCandidate As ClsCandidate
For i = 2 To range.Rows.Count
CandidateProcessID = range.Cells(i, 10).Value
CandidateName = range.Cells(i, 16).Value
FirstName = range.Cells(i, 17).Value
ProcessStatus = range.Cells(i, 9).Value
If dict.Exists(CandidateProcessID) = True Then
Set oCandidate = dict(CandidateProcessID) 'CODE ERRORS HERE AFTER A FEW ROWS (Comes across a
Else an entry that is already in the dictionary)
Set oCandidate = New ClsCandidate
dict.Add CandidateProcessID, oCustomer
End If
oCandidate.CandidateName = oCandidate.CandidateName
oCandidate.FirstName = oCandidate.FirstName
oCandidate.ProcessStatus = oCandidate.ProcessStatus
oCandidate.PQLDate = oCandidate.PQLDate
Next i
Set ReadData = dict
End Function
Sub WriteDict(dict As Dictionary)
Dim key As Variant, oCandidate As ClsCandidate
For Each key In dict
Set oCandidate = dict(key)
Debug.Print key, oCandidate.CandidateName, oCandidate.FirstName, oCandidate.ProcessStatus, oCandidate.PQLDate
Next key
End Sub
I believe the error is object error. It stops and debugs.
That would be "Object Required", and it's absolutely consistent with a very common trap: add Option Explicit at the top of every module, and now VBA won't let you run the code until it knows what oCustomer is.
Option Explicit being missing has allowed the code to run with oCustomer undeclared: simply, a new Variant/Empty pointer is created on-the-spot for that local identifier ("oCustomer"), such that every iteration that "works" is just preparing the ground to make the ones that don't, blow up:
If dict.Exists(CandidateProcessID) = True Then
Set oCandidate = dict(CandidateProcessID) '<~ Variant/Empty is retrieved here: "object required"
Else
Set oCandidate = New ClsCandidate
dict.Add CandidateProcessID, oCustomer '<~ Variant/Empty is added here
End If
The Variant/Empty is successfully retrieved from the dictionary: the problem is that the Set oCandidate instruction on the left side of that = operator says the right-hand side of the operation must be an object reference. Variant/Empty fails to satisfy this requirement, and an "object required" run-time error is raised.
The bug isn't with the retrieval: it's with the storage!
You can easily find bugs like this with static code analysis tooling, such as Rubberduck (disclosure: that's my website).

Filter a table with an array of criteria derived from another table

In another question I figured out how to filter a table with for each row from another table. Now I need to filter again another table with an array of criteria from the filtered table.
When I use the code I tagged here underneath, I recieve an error; Type mismatch.
Ultimately I need to print every result of this loop.
Edit: This question is now solved. The code underneath is working brialliantly!
Sub LoopDoorAfdelingV4()
Dim myTable As ListObject
Dim myTable2 As ListObject
Dim c As Long
Dim myArray As Variant
Dim myGroupIDFilter As Variant
Dim myGroupNameFilter As Variant
Set myTable = ActiveSheet.ListObjects("TabelGroupID")
Set myGroupIDFilter = myTable.ListColumns(1).Range
Set myGroupNameFilter = myTable.ListColumns(2).Range
Set myTable2 = ActiveSheet.ListObjects("TabelAfdelingenIntern")
For c = 2 To myTable.ListRows.Count
ActiveSheet.Range(myTable2).AutoFilter Field:=1, Criteria1:=myGroupNameFilter(c)
Set myfilteredgroup = myTable2.ListColumns(2).DataBodyRange.SpecialCells(xlCellTypeVisible)
With Application
myArray = .Transpose(myfilteredgroup)
End With
Worksheets("Vorige werkdag").Range("$E$2:$P$10000").AutoFilter Field:=5, Criteria1:=myArray, _
Operator:=xlFilterValues
Worksheets("Vorige werkdag").PrintOut Copies:=1, Collate:=True, _
IgnorePrintAreas:=False
Next c
End Sub
I'd speculate that this is due to the way you've set the Array will generate a 2D array and the Criteria1:=myArray is expecting a 1D array.
If your data is in a column you can use
With Application
myArray = .Transpose(myfilteredgroup.SpecialCells(xlCellTypeVisible))
End With
and if it is a row use
With Application
myArray = .Transpose(.Transpose(myfilteredgroup.SpecialCells(xlCellTypeVisible)))
End With

Using arrays to Filter Pivot table labels

Does anyone know a way to filter a PT label row based on an array of values. I tried this but got an error:
Run-time error '5' - invalid argument or procedure call
Sub test()
Dim PT As PivotTable
Dim PF As PivotField
Dim StrArr() As Variant
StrArr = Array("89905-0496", "89905-0497", "89907-0492", "89587-0499", "89585-0498")
Set PT = Sheet5.PivotTables(1)
Set PF = PT.PivotFields("[HFM LEDGER ACCOUNTS].[CONCATENATED ACCOUNT].[CONCATENATED ACCOUNT]")
PF.ClearLabelFilters
PF.PivotFilters.Add2 Type:=xlCaptionEquals, Value1:=StrArr
End Sub
This is my closest approach, it is UGLY! but works:
Try with this sample data:
Name Number
lol 2
cheese 2
foo 6
ball 5
lol 5
cheese 3
Make a pivot table using the sample data that will sum the numbers of each "Name"
Lets say you want to filter the "foo" and "lol" values in the pivot field "Name".
Here is the code:
Option Explicit
Sub add_filters_to_pivot()
Dim filters As Variant
Dim pivot_field As PivotItem
Dim pivot_filter As String
Dim element As Variant
Dim filter_exist As Boolean
filters = Array("foo", "lol")
ActiveSheet.PivotTables("PivotTable1").PivotFields("Name").ClearAllFilters
For Each pivot_field In ActiveSheet.PivotTables("PivotTable1").PivotFields("Name").PivotItems
With ActiveSheet.PivotTables("PivotTable1").PivotFields("Name")
pivot_filter = pivot_field
filter_exist = filter_array(filters, pivot_filter)
If filter_exist <> True Then
.PivotItems(pivot_filter).Visible = False
End If
End With
Next pivot_field
End Sub
Public Function filter_array(array_to_filter As Variant, filter_string As String) As Boolean
Dim filtered_array As Variant
Dim i As Integer
filtered_array = Filter(array_to_filter, filter_string, True, vbTextCompare)
On Error GoTo is_false
For i = 0 To (UBound(filtered_array) + 1)
If filtered_array(i) = filter_string Then
filter_array = True
Exit Function
End If
Next i
is_false:
filter_array = False
End Function
If your PivotTable is based on OLAP, then yes, you can filter a PivotTable on an array in one assignment. This is really handly because you can feed such an PivotTable either an array of things you want to keep, or an array of things you want to filter out. See Pivot Table filter out only 1 option
It's easy to make your PivotTable the OLAP kind...simply select 'Add to Data Model' when you initially create the PivotTable. In other words, Power Pivot PivotTables are OLAP PivotTables.
If you have a 'normal' PivotTable, then you need to iterate through it. There are many potential bottlenecks when doing this, and if you don't code around them, then it might take minutes to iterate through a PivotField with as little as 20,000 PivotItems in it. See my post at http://dailydoseofexcel.com/archives/2013/11/14/filtering-pivots-based-on-external-ranges/ for more on what to watch out for, and then see my answers at the following links that program around these bottlenecks:
Filtering on SlicerItems
Filtering on PivotItems

Storing headers in string array VBA

New to VBA and am trying to essentially lookup column headers in on sheet to another and if match copy data over...
I was told that I can store my headers in a string array then loop through and compare the headers to my array to see if they match.
ie: For each c In
Sheet1.Range("A1:BA1").offset(rownumber -1)
But I'm not sure what that means? How do I store my headers in a string array? Sorry if this is a super basic question. I have googled it and not found anything explaining how to do this or what it means.
My Project:
research data on sheet1. If there is an issue I want to click a button that will copy only the matching column data to a new row in a Specified sheet. From there the data will be reviewed and then another button to export the data to an MS SQL table.
ie:
Sheet1
A B C D E
ID CUR Region Amount Y/N
1 USD NA $54 Y
Sheet2
A B C D E
Region CUR Amount Type Misc
So if Column E = Y then copy all the relevant data in that row to a new sheet:
Sheet2 (output)
A B C D E
Region CUR Amount Type Misc
NA USD $54 Null Null
Sheet2 has columns not in Sheet1 and vice versa... Also the order of the columns are not the same in each sheet. The real sheets are huge with many columns and the row count will change everytime I refresh my data. I need this to loop until Col A in Sheet1 is null.
How do I store my headers in a string array?
A very practical way:
Dim hdlist As String
Dim sep As String
hdlist = "ID|CUR|Region|Amount|Y/N" ' Change this line
sep = "|"
Dim hdnames() As String
hdnames = Split(hdlist, sep, -1, vbBinaryCompare)
Then you can use a For loop to traverse the array.
Here's a piece of code that I've thrown together that I think meets your needs. I think the variable names are self explanatory, but if not, please follow up.
The code searches each cell in the header row of the origin sheet to see if it exists in the destination sheet. If so, it copies over the corresponding information.
Dim wsOrigin As Worksheet
Dim wsDest As Worksheet
Dim nCopyRow As Long
Dim nPasteRow As Long
Dim rngFnd As Range
Dim rngDestSearch As Range
Dim cel As Range
Const ORIGIN_ROW_HEADERS = 1
Const DEST_ROW_HEADERS = 1
Set wsOrigin = Sheets("Sheet1")
Set wsDest = Sheets("Sheet2")
nCopyRow = ActiveCell.Row
nPasteRow = wsDest.Cells(Rows.Count, 1).End(xlUp).Row + 1
Set rngDestSearch = Intersect(wsDest.UsedRange, wsDest.Rows(DEST_ROW_HEADERS))
For Each cel In Intersect(wsOrigin.UsedRange, wsOrigin.Rows(ORIGIN_ROW_HEADERS))
On Error Resume Next
Set rngFnd = rngDestSearch.Find(cel.Value)
If rngFnd Is Nothing Then
'Do Nothing as Header Does not Exist
Else
wsDest.Cells(nPasteRow, rngFnd.Column).Value = wsOrigin.Cells(nCopyRow, cel.Column).Value
End If
On Error GoTo 0
Set rngFnd = Nothing
Next cel

Copy only records that do not exist in the target table

Having two tables (the source and target) intend to copy only the records from the source table that do not exist in the target table (making the comparison with the value of a specific cell in each record). I thought to do it using arrays, but as I am new in this area, needed help.
Examples:
Source Table
ID Date Description
115 01-Ago Description1
120 05-Ago Description2
130 03-Ago Description5
110 08-Ago Description4
105 06-Ago Description6
Destination Table
ID Date Description
130 03-Ago Description5
110 08-Ago Description4
I want to add in the target table records from the source table that do not exist in the target table (ID's 115,120,105 in this example). Thank you!
I'm almost there. After consulting some other questions, I need something like this:
Sub Tests()
Dim MyArray() As String
Dim tgtLastRow, srcLastRow As Integer
Dim rngTarget, rngSource, cel As Range
Dim Delim As String
Delim = "#"
tgtLastRow = Range("H1").End(xlDown).Row
srcLastRow = Range("A1").End(xlDown).Row
Set rngTarget = Range("H2:H" & tgtLastRow)
Set rngSource = Range("A2:A" & srcLastRow)
MyArray = rngTarget.Value
strg = Join(MyArray, Delim)
strg = Delim & strg
For Each cel In rngSource
If InStr(1, strg, Delim & cel.Value & Delim, vbTextCompare) Then
Else
'Copy the row or range here
End If
Next cel
End Sub
But now, I have one of two problems:
If I declare MyArray as string type I have problems loading values to array
If I declare MyArray as variant type I have problems in the Join
Can anyone help-me please??
All you need is to use Either Collection object, or Dictionary Object. These objects help a lot when you try to find the unique records.
Let us take an example, We have two sheets: Source and Target.
You need to loop through Column A in both sheets and move the data from Source Worksheet to target Worksheet. Following is the code, not tested, but it should do the trick. I have added comments so you can easily understand and fit this in your situation easily
Dim ids As Collection
Sub MoveData()
On Error GoTo MoveData_Error
Set ids = New Collection
Dim sourceRange As Range
Dim idRange As Range
Dim cell As Range
Set sourceRange = Range("A1:A100") 'assign your source range here. Code will try to pick ID in this range, and check in ID Range
Set idRange = Range("A1:A100") ' assign your target range here. Code will find the ID in this range
'load all ids from target range in the collection.
On Error Resume Next ' suppressing the error if any duplicate value is found
For Each cell In idRange.Cells
ids.Add cell.Value, cell.Value ' adding in collection to maintain a unique collection
Err.Clear
Next cell
On Error GoTo MoveData_Error
'now I have information about all the availabe IDs in id collection. Now I will loop through to check
For Each cell In sourceRange
If ShouldCopy(cell) Then
'write your code to copy
End If
Next cell
On Error GoTo 0
Exit Sub
MoveData_Error:
MsgBox "Error " & Err.Number & " (" & Err.Description & ") in procedure MoveData of VBA Document Sheet1"
End Sub
Public Function ShouldCopy(cell As Range) As Boolean
On Error GoTo ShouldCopy_Error
If cell.Value = "" Or IsEmpty(cell.Value) Then Exit Function
ids.Add cell.Value, cell.Value ' if error occurs here, then it means the id has been already moved or found in the ID worksheet range
ShouldCopy = True
On Error GoTo 0
Exit Function
ShouldCopy_Error:
ShouldCopy = False
End Function
If you face any issues in understanding and need any help, please let me know.
Thanks,
V
Add a lookup to your source data flagging each record as either present or absent and then bounce your macro off of that column (i.e only move it into target if the lookup = absent).

Resources