Table of Contents

配置增量刷新

脚本用途

如果你想基于某个特定的日期字段为导入表配置增量刷新。 此脚本适用于你希望配置增量刷新的 datetimedateinteger 日期列。

要使用此脚本,请在要配置增量刷新的表中选中日期列,然后运行脚本。 仅当你尚未创建 #"RangeStart" 和 #"RangeEnd" 参数,并且所选表尚未配置刷新策略时,脚本才会运行。

Note

此脚本会自动修改该表的 M 分区,以添加筛选步骤。 请务必检查是否已正确完成。

如果你有很多步骤,务必将此步骤移动到可以折叠到数据源的位置。 请确保在 Power Query 中调整所有 `#"Step References"

Note

此脚本会使用用户输入来生成刷新策略。 请确保在用户输入对话框中输入正确的值。

脚本

为所选列实现增量刷新

// This script will automatically generate an Incremental Refresh policy for a selected table
// It is generated based on the selected column
// It requires input from the user with a dialogue pop-up box.
// This script will automatically generate an Incremental Refresh policy for a selected table
// It is generated based on the selected column
// It requires input from the user with a dialogue pop-up box.
using System.Drawing;
using System.Windows.Forms;

// Hide the 'Running Macro' spinbox
ScriptHelper.WaitFormVisible = false;

// Initialize Variables
Table _Table                = Model.Tables[0];
string _MExpression         = "";
Column _Column              = Model.AllColumns.ToList()[0];
string _ColumnName          = "";
DataType _ColumnDataType    = DataType.DateTime;

try
    {   
        // Select a Table for which you will configure Incremental Refresh.
        // The Refresh Policy will be enabled and configured for this table.
        _Table = 
            Model.Tables.Where(

                // Exclude tables that already have a refresh policy
                t => 
                t.EnableRefreshPolicy != true && 

                // Include only 'Table' objects
                t.ObjectType == ObjectType.Table && 

                // Exclude Calculated Tables
                t.Columns[0].Type != ColumnType.CalculatedTableColumn && 

                // Only include tables that have a column on the "From" side of the relationship
                (Model.Relationships.Count() > 0 ?
                t.Columns.Any(c =>  Model.Relationships.Any(r => r.FromColumn == c) ) : true) && 

                // Exclude tables that don't have a DateTime or Integer column
                (
                    t.Columns.Any(c => c.DataType == DataType.DateTime) || 
                    t.Columns.Any(c => c.DataType == DataType.Int64)
                )
            ).SelectTable(null,"Select a Table for which you will configure Incremental Refresh:");
        
        _MExpression = _Table.Partitions[0].Expression;
    
    try
    {
        // Select the column to apply the Refresh Policy. 
        // The M Expression will be modified using the name of this column.
        _Column = 
            _Table.Columns.Where(

                // Include only DateTime or Int columns
                c => 
                c.DataType == DataType.DateTime || 
                c.DataType == DataType.Int64

            ).SelectColumn(null, "Select a DateTime or DateKey (Int) Column to apply the Refresh Policy.");
        
        _ColumnName = _Column.DaxObjectName;
        _ColumnDataType = _Column.DataType;
    
        try 
        {   // Test if 'RangeStart' exists
            Model.Expressions.Contains(Model.Expressions["RangeStart"]);
            Info ("RangeStart already exists!");
        }
        catch
        {
            // Add RangeStart parameter
            Model.AddExpression( 
                "RangeStart", 
                @"
            #datetime(2023, 01, 01, 0, 0, 0) meta
            [
                IsParameterQuery = true,
                IsParameterQueryRequired = true,
                Type = type datetime
            ]"
            );
        
            // Success message for adding 'RangeStart'
            Info ( "Created 'RangeStart' M Parameter!" );
        }
        
        // Test if the RangeEnd parameter exists
        try 
        {   // Test if 'RangeEnd' exists
            Model.Expressions.Contains(Model.Expressions["RangeEnd"]);
            Info ("RangeEnd already exists!");
        }
        catch
        {
            // Add RangeEnd parameter
            Model.AddExpression( 
                "RangeEnd", 
                @"
            #datetime(2023, 31, 01, 0, 0, 0) meta
            [
                IsParameterQuery = true,
                IsParameterQueryRequired = true,
                Type = type datetime
            ]"
            );
        
            // Success message for adding 'RangeEnd'
            Info ( "Created 'RangeEnd' M Parameter!" );
        
        }
        
        // Incremental Refresh Configuration
        // Input box config
        Font _fontConfig = new Font("Segoe UI", 11);
        
        // Label for how long data should be stored
        var storeDataLabel = new Label();
        storeDataLabel.Text = "Store data in the last:";
        storeDataLabel.Location = new Point(20, 20);
        storeDataLabel.AutoSize = true;
        storeDataLabel.Font = _fontConfig;
        
        // User input for how long data should be stored
        var storeDataTextBox = new TextBox();
        storeDataTextBox.Location = new Point(storeDataLabel.Location.X + TextRenderer.MeasureText(storeDataLabel.Text, storeDataLabel.Font).Width + 20, storeDataLabel.Location.Y);
        storeDataTextBox.Size = new Size(100, 20);
        storeDataTextBox.Text = "3";
        storeDataTextBox.Font = _fontConfig;
        
        // User selection for how long data should be stored (granularity)
        var storeDataComboBox = new ComboBox();
        storeDataComboBox.Location = new Point(storeDataTextBox.Location.X + storeDataTextBox.Width + 20, storeDataLabel.Location.Y);
        storeDataComboBox.Size = new Size(100, 20);
        storeDataComboBox.DropDownStyle = ComboBoxStyle.DropDownList;
        storeDataComboBox.Items.AddRange(new object[] { "days", "months", "quarters", "years" });
        storeDataComboBox.SelectedIndex = 3;
        storeDataComboBox.Font = _fontConfig;
        
        // Label for how much data should be refreshed
        var refreshDataLabel = new Label();
        refreshDataLabel.Text = "Refresh data in the last:";
        refreshDataLabel.Location = new Point(20, storeDataLabel.Location.Y + storeDataLabel.Height + 15);
        refreshDataLabel.AutoSize = true;
        refreshDataLabel.Font = _fontConfig;
        
        // User input for how much data should be refreshed
        var refreshDataTextBox = new TextBox();
        refreshDataTextBox.Location = new Point(storeDataTextBox.Location.X, refreshDataLabel.Location.Y);
        refreshDataTextBox.Size = new Size(100, 20);
        refreshDataTextBox.Text = "30";
        refreshDataTextBox.Font = _fontConfig;
        
        // User selection for how much data should be refreshed (Period)
        var refreshDataComboBox = new ComboBox();
        refreshDataComboBox.Location = new Point(storeDataComboBox.Location.X, refreshDataLabel.Location.Y);
        refreshDataComboBox.Size = new Size(100, 20);
        refreshDataComboBox.DropDownStyle = ComboBoxStyle.DropDownList;
        refreshDataComboBox.Items.AddRange(new object[] { "days", "months", "quarters", "years" });
        refreshDataComboBox.SelectedIndex = 0;
        refreshDataComboBox.Font = _fontConfig;
        
        // User input to refresh full periods or not
        var fullPeriodsCheckBox = new CheckBox();
        fullPeriodsCheckBox.Text = "Refresh only full periods";
        fullPeriodsCheckBox.Location = new Point(storeDataLabel.Location.X + 3, refreshDataLabel.Location.Y + refreshDataLabel.Height + 15);
        fullPeriodsCheckBox.AutoSize = true;
        fullPeriodsCheckBox.Font = _fontConfig;
        
        // Form OK button
        var okButton = new Button();
        okButton.Text = "OK";
        okButton.Location = new Point(storeDataLabel.Location.X, fullPeriodsCheckBox.Location.Y + fullPeriodsCheckBox.Height + 15);
        okButton.MinimumSize = new Size(80, 25);
        okButton.AutoSize = true;
        okButton.DialogResult = DialogResult.OK;
        okButton.Font = _fontConfig;
        
        // Form cancel button
        var cancelButton = new Button();
        cancelButton.Text = "Cancel";
        cancelButton.Location = new Point(okButton.Location.X + okButton.Width + 10, okButton.Location.Y);
        cancelButton.MinimumSize = new Size(80, 25);
        cancelButton.AutoSize = true;
        cancelButton.DialogResult = DialogResult.Cancel;
        cancelButton.Font = _fontConfig;
        
        // Adjust the Location of the storeDataLabel to align with the storeDataTextBox
        storeDataLabel.Location = new Point(storeDataLabel.Location.X, storeDataLabel.Location.Y + 4);
        refreshDataLabel.Location = new Point(refreshDataLabel.Location.X, refreshDataLabel.Location.Y + 4);
        
        // Form config
        var form = new Form();
        form.Text = "Incremental Refresh configuration:";
        form.AutoSize = true;
        form.MinimumSize = new Size(450, 0);
        form.FormBorderStyle = FormBorderStyle.FixedDialog;
        form.MaximizeBox = false;
        form.MinimizeBox = false;
        
        // Open the dialogue in the center of the screen
        form.StartPosition = FormStartPosition.CenterScreen;
        
        // Set the AutoScaleMode property to Dpi
        form.AutoScaleMode = AutoScaleMode.Dpi;
        
        // Add controls to form specified above
        form.Controls.Add(storeDataLabel);
        form.Controls.Add(storeDataTextBox);
        form.Controls.Add(storeDataComboBox);
        form.Controls.Add(refreshDataLabel);
        form.Controls.Add(refreshDataTextBox);
        form.Controls.Add(refreshDataComboBox);
        form.Controls.Add(fullPeriodsCheckBox);
        form.Controls.Add(okButton);
        form.Controls.Add(cancelButton);
        
        // Draw the form
        var result = form.ShowDialog();
        
        // Get the values of the user input if entered
        if (result == DialogResult.OK)
        {
            // Enables the refresh policy
            _Table.EnableRefreshPolicy = true;
            
            var storeDataValue = storeDataTextBox.Text;
            var storeDataComboBoxValue = storeDataComboBox.SelectedItem.ToString();
            var refreshDataValue = refreshDataTextBox.Text;
            var refreshDataComboBoxValue = refreshDataComboBox.SelectedItem.ToString();
            var fullPeriodsChecked = fullPeriodsCheckBox.Checked;
        
            // Display the input values in a message box
            var message = string.Format(
                "Store data in the last: {0} {1}" + 
                "\nRefresh data in the last: {2} {3}" + 
                "\nRefresh only full periods: {4}",
                storeDataTextBox.Text,
                storeDataComboBox.SelectedItem.ToString(),
                refreshDataTextBox.Text,
                refreshDataComboBox.SelectedItem.ToString(),
                fullPeriodsCheckBox.Checked);
        
            Info(message);
        
            // Convert StoreDataGranularity to correct TOM Property
            RefreshGranularityType StoreDataGranularity = RefreshGranularityType.Day;
            switch (storeDataComboBox.SelectedItem.ToString())
            {
                case "years":
                    StoreDataGranularity = RefreshGranularityType.Year;
                    break;
            
                case "quarters":
                    StoreDataGranularity = RefreshGranularityType.Quarter;
                    break;
            
                case "months":
                    StoreDataGranularity = RefreshGranularityType.Month;
                    break;
            
                case "days":
                    StoreDataGranularity = RefreshGranularityType.Day;
                    break; 
            
                default:
                    Error("Bad selection for Incremental Granularity.");
                    break;
            }

        
            // Convert IncrementalGranularity to correct TOM Property
            RefreshGranularityType IncrementalPeriodGranularity = RefreshGranularityType.Year;
            switch (refreshDataComboBox.SelectedItem.ToString())
            {
                case "years":
                    IncrementalPeriodGranularity = RefreshGranularityType.Year;
                    break;
        
                case "quarters":
                    IncrementalPeriodGranularity = RefreshGranularityType.Quarter;
                    break;
        
                case "months":
                    IncrementalPeriodGranularity = RefreshGranularityType.Month;
                    break;
        
                case "days":
                    IncrementalPeriodGranularity = RefreshGranularityType.Day;
                    break; 
        
                default:
                    Error ( "Bad selection for Incremental Granularity." );
                    break;
            }
        
            // Convert RefreshCompletePeriods checkbox to correct TOM property
            int RefreshCompletePeriods;
            if ( fullPeriodsCheckBox.Checked == true )
            { 
            RefreshCompletePeriods = -1;
            }
            else
            {
            RefreshCompletePeriods = 0;
            }
        
            // Set incremental window: period to be refreshed
            _Table.IncrementalGranularity = IncrementalPeriodGranularity;
        
            // Default: 30 days - change # if you want
            _Table.IncrementalPeriods = Convert.ToInt16(refreshDataTextBox.Text);
        
            // Only refresh complete days. Change to 0 if you don't want.
            _Table.IncrementalPeriodsOffset = RefreshCompletePeriods;
            
            // Set rolling window: period to be archived
            // Granularity = day, can change to month, quarter, year...
            _Table.RollingWindowGranularity = StoreDataGranularity;
        
            // Keep data for 1 year. Includes 1 full year and current partial year 
            //    i.e. if it is Nov 2023, keeps data from Jan 1, 2022. 
            //    On Jan 1, 2024, it will drop 2022 automatically.
            _Table.RollingWindowPeriods = Convert.ToInt16(storeDataTextBox.Text);
            
            // If the selected date column is an integer of type YYYYMMDD...
            if ( _ColumnDataType == DataType.Int64 )
            {
                // Add DateTimeToInt Function
                var _DateTimeToInt = 
                    Model.AddExpression( 
                        "fxDateTimeToInt", 
                        @"(x as datetime) => Date.Year(x) * 10000 + Date.Month(x) * 100 + Date.Day(x)"
            );
        
            _DateTimeToInt.SetAnnotation("PBI_ResultType", "Function");
            _DateTimeToInt.Kind = ExpressionKind.M;
        
            // Source expression obtained from the original M partition
            _Table.SourceExpression = 
        
                // Gets expression before final "in" keyword
                _MExpression.Split("\nin")[0].TrimEnd() +
        
                // Adds comma and newline
                ",\n" +
        
                // Adds step called "Incremental Refresh" for filtering
                @"    #""Incremental Refresh"" = Table.SelectRows( " +
        
                // Gets name of last step (after "in" keyword)
                _MExpression.Split("\nin")[1].TrimStart() +
        
                // Adds 'each' keyword
                @", each " +
        
                // Bases incremental refresh on current column name
                _ColumnName +
        
                // Greater than or equal to RangeStart
                @" >= fxDateTimeToInt ( #""RangeStart"" ) and " +
        
                // and
                _ColumnName +
        
                // Less than RangeEnd
                @" < fxDateTimeToInt ( #""RangeEnd"" ) )" +
        
                // re-add 'in' keyword
                "\nin\n" +
        
                // Reference final step just added
                @"    #""Incremental Refresh""";
            }
        
        
            // Otherwise treat it like a normal date/datetime column
            else
            {
                // Source expression obtained from the original M partition
                _Table.SourceExpression = 
                    // Gets expression before final "in" keyword
                    _MExpression.Split("\nin")[0].TrimEnd() +
        
                    // Adds comma and newline
                    ",\n" +
                    
                    // Adds step called "Incremental Refresh" for filtering
                    @"    #""Incremental Refresh"" = Table.SelectRows( " +
                    
                    // Gets name of last step (after "in" keyword)
                    _MExpression.Split("\nin")[1].TrimStart() +
                    
                    // Adds 'each' keyword
                    @", each " +
                    
                    // Bases incremental refresh on current column name
                    _ColumnName +
                    
                    // Greater than or equal to RangeStart
                    @" >= Date.From ( #""RangeStart"" ) and " +
                    
                    // and
                    _ColumnName +
                    
                    // Less than RangeEnd
                    @" < Date.From ( #""RangeEnd"" ) )" +
                    
                    // re-add 'in' keyword
                    "\nin\n" +
                    
                    // Reference final step just added
                    @"    #""Incremental Refresh""";
            }
        
            // Success message for Refresh Policy configuration
            Info ( 
                "Successfully configured the Incremental Refresh policy.\n" + 
                "\nSelect the table and right-click on 'Apply Refresh Policy...'" + 
                "\nSelect & peform a 'Full Refresh' of all new policy partitons that are created." 
            );
            }
        else if (result == DialogResult.Cancel)
        {
            // if the user clicks the Cancel button, close the form and exit the script
            form.Close();
            Error ( "Cancelled configuration! Ending script without changes." );
            return;
        }
    }
    catch
    {
        Error( "No valid column selected! Ending script without changes." );
    }
    
}
catch
{
    Error( "No valid table selected! Ending script without changes." );
}

说明

此代码片段会基于所选日期列,在所选表中配置增量刷新。

示例输出

The prompt that helps you configure incremental refresh based on selected columns
图 1:运行脚本时,系统会提示你选择要配置的表,以及要为其配置策略的 DateTime 或 Int 列。 然后,将出现此图所示的对话框,让你输入刷新策略参数。
A confirmation dialog that acknowledges you have configured the refresh policy
图 2:确认对话框会告知你刷新策略配置已成功,并用通俗易懂的文字再次说明你所设置的内容。