NAME
Excel::PowerPivot::Utils - utilities for scripting Power Pivot models within Excel workbooks
SYNOPSIS
use Excel::PowerPivot::Utils;
my $ppu = Excel::PowerPivot::Utils->new; # will connect to the currently active workbook
# operations on the whole model ...
print $ppu->whole_model_as_YAML;
$ppu->inject_whole_model({QUERIES => ...,
RELATIONSHIPS => ...
MEASURES => ...});
# .. or specific operations on queries, relationships or measures
print $ppu->queries_as_YAML;
$ppu->inject_queries ([{Name => 'New_table', Formula => $M_formula_for_new_table}]);
print $ppu->relationships_as_YAML;
$ppu->inject_relationships([ {ForeignKey => 'Album.ArtistId',
PrimaryKey => 'Artist.ArtistId',
Active => 1},
...
]);
print $ppu->measures_as_YAML;
$ppu->inject_measures([{Name => 'Invoice Lines Total Amount',
AssociatedTable => 'InvoiceLine',
Description => 'sum of quantities multiplied by unit price',
FormatInformation => [qw/Currency USD 2/],
Formula => 'SUMX(InvoiceLine, InvoiceLine[UnitPrice] * InvoiceLine[Quantity]'
},
...
]);
DESCRIPTION
This module uses OLE automation to interact with an Excel Power Pivot model. It can be used for example for
documenting existing models
scripting series of updates or inserts on measures or queries as batch operations -- useful for propagating similar changes to a series of models.
use a version control system on textual exports of the model
Obviously, this module only works on a Windows platform with a local installation of Microsoft Office 2016 or greater.
The exposed interface hides details about the interaction with the Excel object model as documented in https://learn.microsoft.com/en-us/office/vba/excel/concepts/about-the-powerpivot-model-object-in-excel; nevertheless, some knowledge of that model and of the Win32::OLE module is recommended to fully understand what is going on.
CONSTRUCTOR
my $ppu = Excel::PowerPivot::Utils->new(%options);
Creates a new instance. Options are :
- workbook
-
An OLE object representing an Excel workbook. If none is supplied, it will default to the currently active workbook.
- log
-
A logger object equipped with
debug
,info
andwarning
methods. If none is supplied, a simple logger is automatically created from Log::Dispatch.
METHODS
Utilities for Power Query
queries
Returns information about queries in the Excel workbook. This is a list of hashrefs containing
- Name
-
the name of the query
- Formula
-
the M formula (Power Query language)
- Description
-
optional description text
queries_as_YAML
Content of the "queries" method as a YAML string, nicely formatted so that it is easily readable by humans.
inject_queries
$ppu->inject_queries($queries, %options);
Takes an arrayref or query specifications. Each specification must be a hashref with keys Name
, Formula
and optionally Description
. For names corresponding to queries already in the workbook, this is an update operation; other queries in the list are added to the workbook.
Options are :
- delete_others
-
If true, queries not mentioned in the list are deleted from the workbook. False by default.
- handle_connections
-
If true, queries are automatically associated with workbook connections. This is equivalent to manually checking "Add this data to the Data Model" in the "Close and Load To" dialog of Power Query. True by default. When adding or deleting queries in the model, Excel recomputes the whole model, so this operation may be quite slow.
- fast_combine
-
Activates the
FastCombine
property -- see https://learn.microsoft.com/en-us/office/vba/api/excel.queries.fastcombine.
Utilities for relationships
relationships
Returns information about relationships in the Excel model. Due to the inner constraints of Power Pivot, all relationships are many-to-one. The returned structure is a list of hashrefs containing :
- ForeignKey
-
A single string of form
$table.$column
, describing the "many" side of the relationship. - PrimaryKey
-
A single string of form
$table.$column
, describing the "one" side of the relationship. - Active
-
A boolean stating if the relationship is active or not.
relationships_as_YAML
Content of the "relationships" method as a YAML string, nicely formatted so that it is easily readable by humans.
inject_relationships
$ppu->inject_relationships($relationships, %options);
Takes an arrayref or relationship specifications. Each specification must be a hashref with keys ForeignKey
, PrimaryKey
and Active
. For pairs (foreign key, primary key) corresponding to relationships already in the model, this is an update operation on the Active
property; otherwise the relationships are added to the model.
Options are :
- delete_others
-
If true, relationships not mentioned in the list are deleted from the model. False by default.
Utilities for DAX measures
measures
Returns information about measures in the Excel model. This is a list of hashrefs containing
- Name
-
the name of the measure
- AssociatedTable
-
the name of the table to which this measure is associated
- Formula
-
the DAX formula
- Description
-
optional description text
measures_as_YAML
Content of the "measures" method as a YAML string, nicely formatted so that it is easily readable by humans.
inject_measures
$ppu->inject_measures($measures, %options);
Takes an arrayref or measure specifications. Each specification must be a hashref with keys Name
, AssociatedTable
, Formula
and optionally Description
. For names corresponding to measures already in the model, this is an update operation; other measures in the list are added to the model.
Options are :
- delete_others
-
If true, measures not mentioned in the list are deleted from the model. False by default.
- dont_refresh_pivots
-
If true, the
EnableRefresh
property in pivot caches is temporarily disabled, which allows for much faster operations on measures in the model. True by default.
Methods on the whole model
whole_model_as_YAML
Returns a single YAML string containing descriptions for queries, relationships and measures.
inject_whole_model
my $model_to_inject = YAML::Load($whole_model_as_YAML);
$ppu->inject_whole_model($model_to_inject, %options);
Takes as input a hashref with keys QUERIES
, RELATIONSHIPS
and MEASURES
, and calls methods "inject_queries", "inject_relationships" and "inject_measures" on the corresponding subtrees.
NOTES
Unfortunately this module cannot add DAX computed columns to a model table ... because there is no available method for this task in the VBA interface for Excel.
In principle the OLE mechanism allows one to open a connection to an Excel workbook through
my $workbook = Win32::OLE->GetObject($pathname);
However, launching Power Query or Power Pivot operations on such connections does not work well -- I experienced several crashes or file corruptions. So the recommended way is to connect to a running Excel instance, and use that connection to open the workbook. The
t/02_chinook.t
file in this distribution contains a full example; here is the excerpt doing the connection :my $xl = Win32::OLE->GetActiveObject("Excel.Application") or skip "can't connect to an active Excel instance"; my $workbook = $xl->Workbooks->Open($fullpath_xl_file) or skip "cannot open OLE connection to Excel file $fullpath_xl_file";
FULL EXAMPLE
File t/02_chinook.t
this distribution is a full example dealing with the Chinook database, an open source dataset. A diagram of the relational schema can be seen at https://schemaspy.org/sample/relationships.html.
The test script performs the following operations :
download the sqlite database
generate an Excel file with an Excel table for each database table (through the companion module Excel::ValueWriter::XLSX).
inject the model :
- a)
-
Power Queries to connect the Excel tables to the Power Pivot model. Here is an example of the YAML description :
#====================================================================== - Name : Album #====================================================================== Description : Formula : |- let Album_Table = Excel.CurrentWorkbook(){[Name="Album"]}[Content], #"Modified type" = Table.TransformColumnTypes(Album_Table,{ {"AlbumId", Int64.Type}, {"Title", type text}, {"ArtistId", Int64.Type}}) in #"Modified type"
- b)
-
Power Pivot relationships between tables loaded into the model. Here is an example of the YAML description :
#====================================================================== - ForeignKey : Album.ArtistId PrimaryKey : Artist.ArtistId Active : 1 #======================================================================
- c)
-
Power Pivot measures.Since this is just for demonstration purposes, only 3 measures are defined. Here is the YAML description :
#====================================================================== - Name : Invoice Lines Total Amount #====================================================================== AssociatedTable : InvoiceLine Description : FormatInformation : [Currency, USD, 2] Formula : |- SUMX(InvoiceLine, InvoiceLine[UnitPrice] * InvoiceLine[Quantity]) #====================================================================== - Name : Invoice Total Amount #====================================================================== AssociatedTable : Invoice Description : FormatInformation : [Currency, USD, 2] Formula : |- SUM(Invoice[Total]) #====================================================================== - Name : Invoice Lines Percentage Sales #====================================================================== AssociatedTable : InvoiceLine Description : FormatInformation : [PercentageNumber, 1, 0] Formula : |- DIVIDE([Invoice Lines Total Amount], [Invoice Total Amount])
- d)
-
Once the Power Pivot model is in place, we can start building pivot tables based on the DAX measures. For this task the Perl module has no added value, it is done through standard OLE automation :
# create a Pivot Table (percentage of sales per genre, for each customer country) my $pcache = $workbook->PivotCaches->Create(xlExternal, $workbook->Connections("ThisWorkbookDataModel")); my $ptable = $pcache->CreatePivotTable("ComputedPivot!R5C1", 'Sales_by_genre_and_country'); $ptable->CubeFields("[Measures].[Invoice Lines Percentage Sales]")->{Orientation} = xlDataField; $ptable->CubeFields("[Genre].[Name]") ->{Orientation} = xlColumnField; $ptable->CubeFields("[Customer].[Country]") ->{Orientation} = xlRowField;
- e)
-
Then it is possible to write Excel formulas that extract values from the pivot cache. So for example here is the formula that retrieves the percentage of sales for the "Classical" genre in Austria :
= CUBEVALUE("ThisWorkbookDataModel", "[Measures].[Invoice Lines Percentage Sales]", "[Genre].[Name].[Classical]", "[Customer].[Country].[Austria]")
AUTHOR
Laurent Dami, <dami at cpan.org>
COPYRIGHT AND LICENSE
Copyright 2023 by Laurent Dami.
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
3 POD Errors
The following errors were encountered while parsing the POD:
- Around line 676:
'=item' outside of any '=over'
- Around line 741:
You forgot a '=back' before '=head3'
You forgot a '=back' before '=head3'
- Around line 830:
Unterminated L<...> sequence