VBA: Refresh Power query and subsequently update pivot tables - vba

I have an Excel file that comprises a sheet "DATA", which contains data extracted using a Power Query, and three other sheets containing pivot tables that are all dependent on these extracted data in "DATA".
I wantto create a VBA that (1) automatically refreshes/reruns the Power Query specified above when the Excel file is opened and (2) subsequently updates the three pivot tables to mirror the updated data.
I have made several attempts and looked for similar questions on stackoverflow, but without succeeding:
While it was possible for me to refresh the data by refreshing the connection, the Pivot tables were not updated accordingly.
One VBA code that, among others, did not yield the desired results (i.e. subsequently updating the Pivot table) is:
Sub test()
Dim ws As Worksheet
Dim pt As PivotTable
'ActiveWorkbook.RefreshAll 'make sure the refresh in bg property is false for all connections
ActiveWorkbook.Connections("Name of Query").Refresh
For Each ws In ActiveWorkbook.Worksheets
For Each pt In ws.PivotTables
pt.RefreshTable
Next pt
Next ws
End Sub
Thank you!

You must be sure that "background refresh" is set to false:
Sub Refresh_All_Data_Connections()
Dim objConnection, bBackground
For Each objConnection In ThisWorkbook.Connections
'Get current background-refresh value
bBackground = objConnection.OLEDBConnection.BackgroundQuery
'Temporarily disable background-refresh
objConnection.OLEDBConnection.BackgroundQuery = False
'Refresh this connection
objConnection.refresh
'Set background-refresh value back to original value
objConnection.OLEDBConnection.BackgroundQuery = bBackground
Next
ThisWorkbook.RefreshAll
End Sub
Update: added refreshall to refresh all pivot tables.
Rereading your question if you just want to refresh a specific query (assuming sh is set):
sh.ListObjects("Name of Table").QueryTable.refresh BackgroundQuery:=False
ThisWorkbook.RefreshAll

Related

Refresh all does refresh the connections but doesn't refresh the tables

I have three tables which pull data from SQL database.
When I click refresh all from the data menu, the connections refresh but only sum of the tables are refreshed.
When I right-click>refresh the tables themselves, they reflect the correct information (telling me that the connection was refreshed indeed).
I tried ActiveBook.RefreshAll and the following code too:
Dim wks As Worksheet
Dim qt As QueryTable
For Each wks In Worksheets
For Each qt In wks.QueryTables
qt.Refresh BackgroundQuery:=False
Next qt
Next wks
Set qt = Nothing
How can I refresh the table using VBA code?
You should access the connections through Workbook. Example,
Sub Test()
Dim con As WorkbookConnection
For Each con In ThisWorkbook.Connections
' Your Conditions, for example, check connection name etc.
' If con.Name = "MyConnection" Then
con.Refresh
' Else
' Do your thing
' End if
Next
End Sub

After data update, preserve cell values instead of formulas and delete data sheet

I have a xlsx spreadsheet with a bunch of sheets that either contain data or formulas. Lets say sheet1 has the data and sheet2 has formulas referring to the data in sheet1. I'm trying to do the following when sheet1 is updated with new data (coming from a SAS program):
convert sheet2 to only values (i.e. remove the formulas behind)
delete sheet1
save file
I would need to automate this througout the spreadsheet and have the macro/program run automaticcaly when there's an update.
So far, here's what I got (pasting values instead of formulas):
Sub Sample()
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("tab2")
ws.UsedRange.Value = ws.UsedRange.Value
End Sub
I am really not familiar with VBA.
You've got the values only in Sheet2.
You want to delete Sheet1 (there should be a pop-up to check if you really want to delete the sheet... we're going to turn that off before you do, then back on after):
Application.DisplayAlerts = False
Sheets("Sheet1").Delete
Application.DisplayAlerts = True
Then to .SaveAs (prompts for file name to save as)
ThisWorkbook.SaveAs Filename:=fName
Try this:
Sub Sample()
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("tab2")
ws.UsedRange.Value = ws.UsedRange.Value
Application.DisplayAlerts = False
Sheets("sheet1").Delete
Application.DisplayAlerts = True
ActiveWorkbook.Save
End Sub
Why not just scratch the idea of two sheets and extrapolate on the answer I got in my question.
Use formula to update cells, but as values
Try a macro with
Range("A1:P10").Select
With Selection
.Value = .Value
End With
This will turn every formula in the range you've selected into it's value. Just don't try using Cells.Select. When I tried it, I got a memory related error. If your spreadsheets are various sizes, then calculate the bottom cell first.
Sub Convert()
' Table Bottom
Dim TB As Long
' Set Table Bottom Variable
TB = Range("A65000").End(xlUp).Row 'finds bottom row
' Change Formulas in Range to Values
Range("A1:P" & TB).Select
With Selection
.Value = .Value
End With
End Sub
Sometimes I have to change the column for finding the bottom row depending on the spreadsheet, but this is the general idea.
I think this will accomplish what you're ultimately trying to do if you put this at the end of the code that is pulling the updates, or if you just run it separately after the updates have been completed. It doesn't seem like your goal is to have two different spreadsheets. Just one spreadsheet that is only values.

Refreshing Tables with VBA

I have two tables, one with a list of cities( we'll call this City List), and another with data points that correspond with those cities ( Call this The Data Table). The Data Table, is connected to a Select query that I built in MS SQL Server. This Select query/ Data Table has a single Where clause in which I have substituted the SQL criteria and replaced a ? in order to make it a parameter when connected to Excel.
Now that I have that out of the way, I'll explain what I'm trying to accomplish. I want to loop through the City List and for each city in the list, update The Data Table to reflect the data points for the city. Ultimately, I would like to loop through and each time The Data Table is refreshed, it saves a copy of the workbook with that specific table.
I have posted my current code down below, but my issue is that the table never refreshes with the current data that corresponds with the city that is currently selected via the loop. With that being said, if I hit the escape key to break out of the VBA macro, the table will then refresh with whatever the last city was before I stopped the macro.
Code:
Sub Macro1()
Dim WS As Worksheet
Dim WS2 As Worksheet
Dim CT As Variant
Dim MSG As String
Set WS = Sheets("Sheet1")
Set WS2 = Sheets("Sheet2")
CT = Range("A1").CurrentRegion
For i = 2 To UBound(CT, 1)
MSG = ""
For J = 1 To UBound(CT, 2)
WS.Range("$D$2").Value = CT(i, J) //Places the city into Cell $D$2 which is where The Data Table looks to for the parameter.
Exit For
Next J
ActiveWorkbook.Connections("Query from Database").Refresh
WS2.ListObjects(1).Refresh
Next i
End Sub
It's almost as though the macro is running too fast for the table to catch up and refresh. I've tried adding some wait times into the code, in hopes that it would give it enough time to allow the table to refresh, but that had no affect. I have also turned off Background Refresh, and that doesn't seem to do anything either. Right now it just loops through the city table, and with each city it shows that the query is refreshing, but after the query is finished refreshing, it goes onto the next city without ever updating The Data Table. HELP!
There are a couple of things I think you need to do -- maybe you've already done them.
When you set up your parameter/bind variable (which you have done), point it to a specific cell. Then, within your SQL Server query, make sure the parameter is bound to that range every time:
Forgive me if I'm overstating the obvious, but for those that don't know you get to this dialog by right-clicking the table and selecting Table->Parameters.
From there, as you iterate through your main table (the one with the cities in it), you can just take the value from each row in that table and update the cell with the binding parameter.
Something like this should work:
Sub RefreshAllCities()
Dim ws1, ws2 As Worksheet
Dim loCities, loDataTable As ListObject
Dim lr As ListRow
Set ws1 = ActiveWorkbook.Worksheets("Sheet1")
Set ws2 = ActiveWorkbook.Worksheets("Sheet2")
Set loCities = ws1.ListObjects("CityList")
Set loDataTable = ws2.ListObjects("DataTable")
' get rid of those pesky warnings to overwrite files
Application.DisplayAlerts = False
For Each lr In loCities.ListRows
ws2.Cells(1, 2).Value = lr.Range(1, 1).Value
loDataTable.QueryTable.Refresh BackgroundQuery:=False
ActiveWorkbook.SaveAs Filename:="C:\temp\" & lr.Range(1, 1).Value & ".xlsx", _
FileFormat:= xlOpenXMLWorkbook
Next lr
Application.DisplayAlerts = True
End Sub
I assume you wanted .xlsx files in this example. This will clobber any embedded VBA, which is actually a nice bonus, as the recipients of the filtered datasets won't have to be exposed to that. If you want xlsm or xlsb, that's easy enough to change.
By default Excel will "Enable Background Refresh" which will allow Excel to move on and continue execution before the query refresh is actually finished. Some people have been able to get it to work by calling .Refresh twice but it's pretty much random/arbitrary timing.
There should be an Excel option to uncheck in the Data Tables properties or you might be able to update the BackgroundQuery = False property from VBA through a reference to it
If you disable background refreshing then your code will sit and wait for the refresh to complete before moving on.

Excel VBA: Update chart button when new row of data is added to datasheet

I'm working on an Excel VBA macro that will create a timeline for a list of projects. I would like the macro to be dynamic and have an update command button that will refresh the chart when new information is added to the data sheet.
However, anytime I add a new row to the data sheet, the chart format goes haywire. Here is my code:
Private Sub CommandButton1_Click()
Application.ScreenUpdating = False
On Error Resume Next
ThisWorkbook.Charts.Delete
On Error GoTo 0
Dim ChartSheet1 As Chart
Set ChartSheet1 = Charts.Add
With ChartSheet1
.SetSourceData Source:=Sheets("Sheet1").Range("A2:A61,F2:F61,I2:I61")
.ChartType = xlBarStacked
End With
Application.ScreenUpdating = True
End Sub
The "ThisWorkbook.Charts.Delete" is to delete the existing chart and have it replaced with the new, updated chart.
Column A contains the project titles. Column F contains the date that the project starts on. Column I contains the number of days that the project will last for.
I have 59 rows of data in my data sheet. I made the range to 61 so that I could add another row or two of data to see if my code works. But it doesn't. I used the record macro function to make most of my code. I understand this isn't the best technique but I have no background in VBA and was looking for a quick solution. I've been trying to learn the basics but can't find a solution for the problem I'm having.
I'm thinking that the problem is with "61" and that I should change that to a variable like "lastRow". Or maybe I'm completely off due to my lack of experience with programming. Thanks for any insight.
You can make the data source dynamic by calculating the last row of the data in VBA like this:
Private Sub CommandButton1_Click()
' declare variables for last row and worksheet
Dim lastRow
Dim sh As Worksheet
Set sh = ActiveWorkbook.Worksheets("Sheet1")
' get last populated cell in column A
lastRow = sh.Cells(Rows.Count, "A").End(xlUp).Row
Application.ScreenUpdating = False
On Error Resume Next
ThisWorkbook.Charts.Delete
On Error GoTo 0
Dim ChartSheet1 As Chart
Set ChartSheet1 = Charts.Add
With ChartSheet1
.SetSourceData Source:=Sheets("Sheet1").Range("A2:A" & lastRow & ",F2:F" & lastRow & ",I2:I" & lastRow)
.ChartType = xlBarStacked
End With
Application.ScreenUpdating = True
End Sub
Your code completely deletes the chart sheet and then creates a new one. I'm not sure what "the chart format goes haywire" means in your world, but without any formatting you'll get the Excel default horizontal bar chart with that code.
Turn the data in the worksheet into a Table (Insert tab > Table). Make the chart using this table.
Now whenever a row is added to or deleted from the table, the chart automatically updates to reflect the current contents of the table.
No need for VBA, no need to delete and recreate the chart, and lose then try to restore its formatting.
Look at the numbers. The Range goes from Row 2 to Row 61, which covers 59 rows of data. If you want to add another 2 more rows of data, you need to change it to "63".

Poor performance updating Excel slicer selection using VBA

I am simulating a click on an Excel Slicer using VBA but have run into serious performance problems.
The user clicks on a column graph with dates on the X-axis. When clicking on a column, the corresponding date is selected in a slicer containing the list of dates. The list will continue to grow with time.
The only way (to my knowledge) to set slicer selection for non-OLAP data sources (my case) is to set selected = true individually for each slicer item. As a recalculation is triggered on each setting this is very slow for slicers with many items.
Small code example showing the problem:
On Error GoTo Err_Handler:
Dim SC As SlicerCache
Set SC = ActiveWorkbook.SlicerCaches("Slicer_DATE")
Dim SI As SlicerItem
Application.EnableEvents = False
Application.Calculation = xlCalculationManual
For Each SI In SC.SlicerItems
SI.Selected = True
Next
Err_Handler:
Application.Calculation = xlCalculationAutomatic
Application.EnableEvents = True
Similar questions have been asked before:
Selecting multiple slicer items at once without a refresh
Pivot Slicer Update To Slow, Can I pause all functions until slicer update is complete?
There the suggestion is either:
Application.EnableEvents = false
or
Application.Calculation = xlCalculationManual
UPDATE: I also notice that despite turning off events and calculation, all pivot tables are in fact recalculating!
For me, neither of these options work and do not improve the performance. Calculation is indeed postponed and no events are triggered. Still, each iteration of the selected=true takes around 1.5 seconds. In total the operation takes around 5 minutes to complete.
My slicer is connected to 23 pivot tables (!) in multiple sheets. The underlying data (MS Access DB connection) is around 60,000 rows with ~20 variables which is not that much.
Any help is appreciated.
PivotTables have a ManualUpdate property which can be set to True. Setting this property for all your pivots might help speed up your code.
Please try adding this right above where your update slicer code is:
Dim PT As PivotTable
Dim wb As Workbook
Dim ws As Worksheet
Set wb = ThisWorkbook
For Each ws In wb.Sheets
For Each PT In ws.PivotTables
PT.ManualUpdate = True
Next PT
Next ws
And then add this after you've updated the slicer:
For Each ws In wb.Sheets
For Each PT In ws.PivotTables
PT.ManualUpdate = False
Next PT
Next ws
For more information:
Speed up pivot table filtering VBA code
Turn Off PT Calc
MSDN: ManulaUpdate
Hope that helps!
What you need to do is Duplicate the Slicer and
Duplicate the field as a slicer AND a report filter
see http://www.powerpivotpro.com/2010/12/another-way-to-get-and-use-slicer-values-in-formulas/
Then use the CurrentPage property to select the item:
Private Sub SelectPivotItem(FieldName As String, Itemname As String)
Dim PT As PivotTable, PTF As PivotField, PTI As PivotItem
Set PT = shtInt.PivotTables("PivotTable1")
Set PTF = PT.PivotFields(FieldName)
PTF.ClearAllFilters
PTF.CurrentPage = Itemname
End Sub
This helps massively with performance. The same approach applies to selecting multiple PivotItems in VBA.
Set the pivot tables to ManualUpdate.
Hide all the sheets that contain pivot tables affected by the Slicer while you're doing the multiple slicer selections.
Don't forget to set the pivot tables to not ManualUpdate when done, and set the sheets visible afterwards if you need to
It's very strange because even with Application.ScreenUpdating = False this still helps enormously.
I got my PivotItems selection macro down from 3 minutes to 3 seconds by doing this. Quite a stark difference.
Unfortunately for Slicers this is still slow, around 30s. I'm looking to switch back to doing this with VBA and PivotItems, and avoiding the use of Slicers completely, unless I can find another way to improve performance.