How to update multiple Pivot Tables with multiple DataSources? - excel

Hello beautiful people!
I am using Excel 2010 and I am trying to come up with a handy macro which would update all pivot tables in a workbook. I want it to be universal that means to be applicable to ANY workbook which contains pivot tables. By update I do not mean mere Pivot table Refresh as the data in our reports are getting rewritten and all that remains from the original source range is header. This means I need to Change the Data Source to new range dynamically.
Example: I have a Dashboard with 8 pivot tables on Summary sheet. Every Pivot table has its own DataSource on different sheet (pivot1 has data on Sheet1, pivot2 has data on Sheet2, etc.)
I started with a basic Macro which changes/updates the DataSource for a single Pivot table and which I use in various iterations in other macros - taken from here:
ActiveWorkbook.PivotTables(PivotTable1).ChangePivotCache ActiveWorkbook. _
PivotCaches.Create(SourceType:=xlDatabase, SourceData:=SrcData, _
Version:=xlPivotTableVersion15)
Now, my thought-process was like this:
Since the header is what is in the SourceData, I need to find a way to get that range and just expand it to last row with the usual
lastRow = Cells(Rows.Count, 1).End(xlUp).Row
I tried to achieve this with .SourceData property but it appeared to be useless since property .SourceData is Variant type. So I did some digging around the excel forums some more and found a code to convert this into desired type:
Application.Evaluate(Application.ConvertFormula(pt.SourceData, xlR1C1, xlA1))
I believe I implemented this correctly with some string trimming to get desired references but even though it looks great in theory (atleast in my eyes) I am getting runtime error (please see below) and now I am at loss. Therefore, I am turning to you guys to see if you could share some knowledge or offer an advice.
This is the current code I need help with (with explanations):
Sub pivot_updator()
Dim lastRow As Long
Dim pt As PivotTable
Dim ws As Worksheet
Dim dataArea As Range
Dim sSheetTrim As String
Dim sRangeTrim As String
For Each ws In ActiveWorkbook.Sheets
For Each pt In ws.PivotTables
'evaluate String to get Range after conversion from Variant - recovered from another forum
Set dataArea = Application.Evaluate(Application.ConvertFormula(pt.SourceData, xlR1C1, xlA1))
'some trimming to get desired references for lastRow (sheet in which the SourceData are located) and dataArea (starting cell and last column of SourceData)
sSheetTrim = Left(pt.SourceData, 6)
sRangeTrim = Left(dataArea.Address, 8)
lastRow = Sheets(sSheetTrim).Cells(Rows.Count, 1).End(xlUp).Row
'gluing the starting cell and lastcolumn (sRangeTrim) together with ending cell in last row (lastrow)
Set dataArea = Range(sRangeTrim & lastRow)
'test that I am getting correct Range for the pivot SourceData update
MsgBox dataArea.Address
'following gives me the Runtime error -2147024809(80070057)
pt.ChangePivotCache ActiveWorkbook. _
PivotCaches.Create _
(SourceType:=xlDatabase, _
SourceData:=dataArea, _
Version:=xlPivotTableVersion12)
'just to be sure the pivot table refreshes after the SourceData is updated
pt.RefreshTable
Next pt
Next ws
MsgBox "Pivot Update Done"
End Sub
Running this Sub prompts me with Runtime error -2147024809(80070057):
"The PivotTable field name is not valid. To create a pivot table report you must use data that is organized as a list with labeled columns, If you are changing the name of a PivotTable field, you must type a new name for the field."
What it should do is cycle through all Pivot Tables and expand their respective DataSource ranges to last row.
Since this is supposed to be all-time universal macro, you should be able to test it yourself on any workbook which contains pivot table.
I will be grateful for any suggestion and advice, also feel free to criticize my coding in general, I am fairly new to VBA. Thank you for spending your time looking into my issue and investing your beautifulness into it.

Beautiful people,
Fortunately, after taking a break and coming back to this issue with clear head, I was able to determine the problem. Even though the dynamic range cell references were set correctly, the scope of the range was not. There was no Sheet information in dataArea which is why the runtime error for invalid pivot table was present. It was probably a result of Application.Evaluate which omitted the sheet reference.
I had to further add a code which trimmed the string address to contain Sheet name on which pivot source data were located.
Please see amended code below:
Sub pivot_updator()
Dim lastRow As Long
Dim pt As PivotTable
Dim ws As Worksheet
Dim dataArea As Range
Dim sSheetTrim As String
Dim sRangeTrim As String
Dim SourceDataSheet As String
Application.ScreenUpdating = False
Application.Calculation = xlManual
For Each ws In ActiveWorkbook.Sheets
For Each pt In ws.PivotTables
'evaluate String to get Range after conversion from Variant - recovered from another forum
SourceDataSheet = Application.ConvertFormula(pt.SourceData, xlR1C1, xlA1)
Set dataArea = Application.Evaluate(Application.ConvertFormula(pt.SourceData, xlR1C1, xlA1))
'implemented fix - trim to get desired Sheet name on which pivot source data are located
sSheetTrim = Split(SourceDataSheet, "!")(0)
sSheetTrim = Split(SourceDataSheet, "'")(1)
sSheetTrim = Split(sSheetTrim, "]")(1)
'some trimming to get desired references for lastRow (sheet in which the SourceData are located) and dataArea (starting cell and last column of SourceData)
sRangeTrim = Left(dataArea.Address, 8)
lastRow = Sheets(sSheetTrim).Cells(Rows.Count, 2).End(xlUp).Row
'gluing the starting cell and lastcolumn (sRangeTrim) together with ending cell in last row (lastrow)
Set dataArea = Sheets(sSheetTrim).Range(sRangeTrim & lastRow)
'test that I am getting correct Range for the pivot SourceData update
'following gives me the Runtime error -2147024809(80070057) // MAYBE the DATA AREA DOES NOT DEFINE SHEET
pt.ChangePivotCache ActiveWorkbook. _
PivotCaches.Create _
(SourceType:=xlDatabase, _
SourceData:=dataArea, _
Version:=xlPivotTableVersion12)
'just to be sure the pivot table refreshes after the SourceData is updated
pt.RefreshTable
Next pt
Next ws
Application.ScreenUpdating = True
Application.Calculation = xlAutomatic
MsgBox "Pivot Update Done"
End Sub
The code is now working fine, I will see if there happen to be any mishaps I had not think of and possibly update the code.
I would like to thank anyone who commented on this question and proposed any solution and also to anyone who viewed it and read through it.
Thank you.

Related

Change automatically the range of the datasource of a pivot table of each worksheet

I want to change automatically the range of the sourcedata of each pivot table of each worksheet. I have the sheet 'DATA' which feeds every pivot table of the workbook. The point is that the range of the DATA sheet is variable. So when I change the DATA sheet I want to refresh all the pivot table adjusting also the rnew range of data.
I wrote the following script, but it is not working. I don't know why:
Sub Prueba_Rango_TD()
Dim ws As Worksheet
Dim LastRow As Integer
LastRow = Cells(Rows.Count, 3).End(xlUp).Row
For Each ws In ThisWorkbook.Worksheets
For Each tbl In ws.ListObjects
tbl.Resize tbl.Range.Resize("DATA!B8:O" & Ultima_fila)
Next tbl
Next ws
End Sub
Can you help me please?
Thank you!
The easy way you do this is by recording a Macro.
Then you will se what type of Objects being used and where the pointer of your first Pivot, something like this:
ActiveSheet.PivotTables("PivotTable1").ChangePivotCache
ActiveWorkbook. _
PivotCaches.Create(SourceType:=xlDatabase, SourceData:=ThisWorkbook.Worksheets("DATA").UsedRange.Address,
Version:=xlPivotTableVersion12)
If all the other sheets points towards the same data Cashe you do it with the loop.
.PivotTables("PivotTable1").ChangePivotCache ("DATA!PivotTable1")
.PivotTables("PivotTable1").RefreshTable

variable referencing a cell date value not passing to a copy paste filter

I have a spreadsheet that operators input data in, with the A column being the date, and the data is input by row. The A column is a formula that adds +1 to the date in the previous cell, going all the way down recursively to auto-populate the date as the sheet is filled out.
I have to have a report printed out at the end of every day, and I am trying to use VBA to filter the rows out by a date that the operator inputs on another sheet in cell B2. I need the macro to grab that date value, and pass it as a variable to the filter in order to pull the 12 rows of that date and paste it into a new sheet. Unfortunately, the value it pulls is not being passed, and when I put a MsgBox command in there, it shows it's pulling 12:00 AM and not a date. When using the Date variable, it also breaks the filter on the bottom macro below (trying 2 different versions just to get this working).
I'm not good with VBA, so my macros were pulled from example websites and I tailored them to what I need.
This is one macro I have tried:
Sub For_RangeCopy()
Dim rDate As Date
Dim rSheet As Worksheet
Set rSheet = ThisWorkbook.Worksheets("EOS")
rDate = CDate(rSheet.Range("B2").Value)
MsgBox (rDate)
' Get the worksheets
Dim shRead As Worksheet
Set shRead = ThisWorkbook.Worksheets("Bi-Hourly Report")
Dim shWrite As Worksheet
Set shWrite = ThisWorkbook.Worksheets("Report")
' Get the range
Dim rg As Range
Set rg = shRead.Range("A1").CurrentRegion
With shWrite
' Clear the data in output worksheet
.Cells.ClearContents
' Set the cell formats
'.Columns(1).NumberFormat = "dd/mm/yyyy"
'.Columns(3).NumberFormat = "$#,##0;[Red]$#,##0"
'.Columns(4).NumberFormat = "0"
'.Columns(5).NumberFormat = "$#,##0;[Red]$#,##0"
End With
' Read through the data
Dim i As Long, row As Long
row = 1
For i = 1 To rg.Rows.Count
If rg.Cells(i, 1).Value2 = rDate Or i = 1 Then
' Copy using Range.Copy
rg.Rows(i).Copy
shWrite.Range("A" & row).PasteSpecial xlPasteValues
' move to the next output row
row = row + 1
End If
Next i
End Sub
And here is another Macro I have tried to use. This one actually gives me the 3 header rows which I don't need, but I don't mind, this paste is a reference for the report layout anyway, so the operators won't see this sheet. But this macro does give me the first block of the date range: 1/1/2023. I do know that the "rgCriteria As String" is likely incorrect, but that is how I get anything useful from this macro. If I change that rgCriteria to a Date, it breaks the rgData.AdvancedFilter command, and I haven't learned enough VBA to know why. And my boss wants this done today, although here I am posting here, thus it's not getting done today.
Sub AdvancedFilterExample()
' Get the worksheets
Dim rSheet As Worksheet
Set rSheet = ThisWorkbook.Worksheets("EOS")
Dim shRead As Worksheet, shWrite As Worksheet
Set shRead = ThisWorkbook.Worksheets("Bi-Hourly Report")
Set shWrite = ThisWorkbook.Worksheets("Report")
' Clear any existing data
shWrite.Cells.Clear
' Remove the any existing filters
If shRead.FilterMode = True Then
shRead.ShowAllData
End If
' Get the source data range
Dim rgData As Range, rgCriteria As String
Set rgData = shRead.Range("A1").CurrentRegion
' IMPORTANT: Do not have any blank rows in the criteria range
'Set rgCriteria = rSheet.Range("B2")
rgCriteria = rSheet.Range("B2").Value
MsgBox (rgCriteria)
' Apply the filter
rgData.AdvancedFilter Action:=xlFilterCopy, CriteriaRange:=rgCriteria _
, CopyToRange:=shWrite.Range("A1")
End Sub
I don't know which method of filtering and pasting is best for my situation, but I do know that the faster is better. I'm copying entire rows, and it needs to be efficient because this log contains a lot of data. I only need one of these macros to work, but I will be heavily modifying them and chaining them together with about 5 other filter/copy/paste sequences to follow, along with printOut commands after that, and finalized by clearing the sheets it pastes to, and then re-enabling all the functionality of the sheet (calculations, displaystatusbar, events, and screenupdating) all to make it quicker while the macro is running. All of these reports will be run using the macro with a button click.
Any thoughts or suggestions would be greatly appreciated. I've been struggling with this for a couple of weeks now. I'm at a loss and turning to the community that has helped me with a TON of questions over the past 20 or so years just by a Google search!
Other information:
I'm using Office 365 on a Windows 10/11 machine. The headers of the sheet it filters does contain merged cells as the header is rows 1-3, there is a lot of data in this sheet that grows through the year. 12 rows per day for an entire year. These macros are written in a Module aptly named "Module 1" if that helps. I do have this workbook, and the original log saved on OneDrive that can be shared.
When using Advanced Filter your criteria range should have headers which match your data table.
Sub AdvancedFilterExample()
Dim rSheet As Worksheet, shRead As Worksheet, shWrite As Worksheet
Dim rgData As Range, rgCriteria As Range
Set rSheet = ThisWorkbook.Worksheets("EOS")
Set shRead = ThisWorkbook.Worksheets("Bi-Hourly Report")
Set shWrite = ThisWorkbook.Worksheets("Report")
Set rgData = shRead.Range("A1").CurrentRegion 'source data range
'## criteria range needs to include a matching date header...
Set rgCriteria = rSheet.Range("B3:B4") 'eg. "Date" in B3, date value in B4
shWrite.Cells.Clear ' Clear any existing data
If shRead.FilterMode = True Then shRead.ShowAllData ' Remove the any existing filters
rgData.AdvancedFilter Action:=xlFilterCopy, _
CriteriaRange:=rgCriteria, _
CopyToRange:=shWrite.Range("A1")
End Sub

Method Range of Object Worksheet failed excel VBA

I have been stuck on a particular problem I am having with an excel visual basic macro.
I have written a script to simply paste a dynamic range of data from one worksheet to the other, The sheet that is being pasted to is a running sheet that updates to the next empty row each time new data is sent to it.
For certain ranges of data my code breaks and gives me the 1004 error.
I have looked at multiple similar questions and they don't seem to have given me any solutions.
I will first post my code then a data set that works and a data set that does not.
Private Sub RunningSheetClick_Click()
Dim lastrow As Long
Dim Vals As Range
Dim copyWS As Worksheet
Dim pasteWS As Worksheet
Set pasteWS = Sheets("Full List")
Set copyWS = Sheets("Macro Test")
Setlastrow = Sheets("Full List").Range("A" & Rows.count).End(xlUp).Row + 1
copyWS.Activate
' the following line is where my error appears
Set Vals = copyWS.Range("B3:S" & copyWS.Range("B3").End(xlDown))
Vals.Select
Vals.Copy _
Destination:=pasteWS.Range("A" & lastrow)
End Sub
This is a data set that works and exports correctly to the sheet "Full List"
This is a data set that does not, I believe it has to do with column B and the strings that are in those cells as compared to the integers in the first data set
Any help would be appreciated, thank you for taking the time
This is bad syntax that should never work. You just cannot concatenate a range object onto a string and expect a valid range object. I suspect it works with numbers since copyWS.Range("B3").End(xlDown) is returning its cell value by default. So if the value at the bottom of column B was 99, you would be resolved to Range("B3:S99").
Set Vals = copyWS.Range("B3:S" & copyWS.Range("B3").End(xlDown))
It should be something like,
with copyWS
Set Vals = .Range(.cells(3, "B"), .cells(rows.count, "B").end(xlup).offset(0, 17))
end with

Can't Select a Cell - Very Strange

Please help!
The last line of my code
'Sheets("Original Data from Server").Cells(4, 2).Select
Will not work! Keep getting error "Select method of range class failed'
Whenever I only run that one line, it works perfect.
But whenever I run the rest of the code, I get the error.
NO ONE can seem to help me figure this out!
Sub FormatAdjacencyReport()
Dim iLastRow As Integer '<-- Interger for Counting Number of Rows
Dim iLastColumn As Integer '<-- Interger for Counting Number of Columns
Dim OriginalOutput As Worksheet '<-- Sheet Containing Orignal Report
Dim AdjacencyData As Worksheet '<-- Sheet Crated for Final Output
Set OriginalOutput = ActiveWorkbook.Worksheets(1)
OriginalOutput.Name = "Original Data from Server"
'Determines how many different rows we have in original output
iLastRow = Range("B1").Rows.End(xlDown).Row
'Txt to Columns for Each Row
For i = 2 To iLastRow
Sheets("Original Data from Server").Cells(i, 5).TextToColumns DataType:=xlDelimited, _
ConsecutiveDelimiter:=True, Space:=True
Next i
'Create a new sheet for our output
Set AdjacencyData = Sheets.Add
AdjacencyData.Name = "Adjacency Data Output"
'Need to Paste the Entire List of Store Numbers into a New Tab
iLastColumn = Sheets("Original Data from Server").Cells(2, 1).Columns.End(xlToRight).Column
'Sheets("Original Data from Server").Cells(4, 2).Select
End Sub
You need to set focus back on Sheets("Original Data from Server") before calling a select on that sheet. So put line 'Sheets("Original Data from Server").Select before the last line. When the line throwing error is being executed, the focus is on another sheet.
To avoid this kind of errors I suggest you stop working with select and/or activate generally, and only use it when absolutely necessary.
Your code could look like this:
Sub FormatAdjacencyReport()
Dim iLastRow As Long '<-- for Counting Number of Rows
Dim iLastColumn As Long '<-- for Counting Number of Columns
Dim OriginalOutput As Worksheet '<-- Sheet Containing Orignal Report
Dim AdjacencyData As Worksheet '<-- Sheet Crated for Final Output
Set OriginalOutput = ActiveWorkbook.Worksheets(1)
OriginalOutput.Name = "Original Data from Server"
'Determines how many different rows we have in original output
iLastRow = OriginalOutput.Range("B1").End(xlDown).Row ' changed
iLastColumn = OriginalOutput.Range("A2").End(xlToRight).Column ' changed
'Txt to Columns for Each Row
For i = 2 To iLastRow
OriginalOutput.Cells(i, 5).TextToColumns DataType:=xlDelimited, _
ConsecutiveDelimiter:=True, Space:=True
Next i
'Create a new sheet for our output
Set AdjacencyData = Sheets.Add
AdjacencyData.Name = "Adjacency Data Output"
'Need to Paste the Entire List of Store Numbers into a New Tab
' OriginalOutput.Activate -- not needed
' OriginalOutput.Cells(4, 2).Select -- not needed
OriginalOutput.Range("B4:B17").Value = AdjacencyData.Range("A10:A23") ' direct copy!
End Sub
1- work with qualified ranges/cells whenever you work on more than one sheet or workbook. See the line where iLastRow is determined.
2- keep table limit calculation in one place (as soon as the limits are fixed)
3- avoid an explict qualifier (Sheets("Original Data from Server").) in favor of the assigned variable (OrignalOutput). This way you can fully qualify (with workbook name and sheet name) in just one place. Imagine you'd change the sheet name later...
4- use direct assignment from range to range for copying. This circumvents the reference problem (if using qualifiers!), and the clipboard contents are kept intact.

How to put query results into a datatable with Excel VBA and ADO?

I want to use ADO via ODBC to pull records from a database table and put them in an Excel worksheet. I can do this. Ultimately, I want the data to be contained within an Excel Table. I know how to do this manually by selecting the appropriate cells and using the Insert menu to create the Table. How can I do this in my VBA code to have the returned query results be placed into the target worksheet in an Excel Table? I tried using the Macro recorder but the generated code was not helpful.
Something like this?
Add this code after you have imported the data. I am assuming the following. Please amend accordingly.
The data is imported in Cell A1 of Sheet1
Row 1 has column Headers
Sub Sample()
Dim LastRow As Long, LastCol As Long
Dim ws As Worksheet
Dim rng As Range
Set ws = Sheets("Sheet1")
LastRow = ws.Range("A" & Rows.Count).End(xlUp).Row
LastCol = ws.Cells(1, Columns.Count).End(xlToLeft).Column
Set rng = Range("$A$1:$" & Split(Cells(, LastCol).Address, "$")(1) & "$" & LastRow)
With ws
.ListObjects.Add(xlSrcRange, rng, , xlYes).Name = "Table1"
.ListObjects("Table1").TableStyle = "TableStyleLight2"
End With
End Sub
If you click the From Other Sources button on the Data tab you should see your ODBC listed. You can then specify the table to connect to. You will then have a refreshable Table that contains your data, in other words it combines what you're already doing with what you want to do into one step. Based on what you said in your comments I think this is the way to go, but let me know if I'm missing something.

Resources