美文网首页BI
PowerBI中构建日期区间表的终极方法

PowerBI中构建日期区间表的终极方法

作者: PowerBI战友联盟 | 来源:发表于2018-02-02 03:08 被阅读2536次

    什么是日期区间表

    只有日期表的叫法,参考:日期表,实际上并没有对日期区间表的约定速成的叫法,但日期区间表却是在PowerBI数据建模分析中有非常重要的运用,下面先看一个例子:

    可以看出,当选择年份后,这里希望迅速选择对比的粒度是月,周,日其中的一个,并迅速展开对比及可视化观察。所以要求可以直接点击选择来进行,选择周的效果如下:

    以及选择日的效果如下:

    这里的优势在于:不需要离开纸面,直接选择对比粒度展开对比。这里称可以动态变化的对比粒度为日期区间

    这种日期区间表和日期表显然是不同的,它可以这样理解:

    如果从左到右,依次可称为:YearMonthPeriod,YearWeekPeriod以及YearDatePeriod,观察它们的PeriodName字段,名称相同但内容不同,所以CalendarPeriod应该是这三个表的纵向追加合并结果,下面给出构造方法。

    用M构建日期区间表

    可以自行理解,或者不必理解细节,直接复制粘贴用即可。在PowerBI查询编辑中,新建空查询,然后粘贴如下内容:

    
    let
    
        calendar_period_type =  type function (
            
                optional CalendarYearStart as (type number meta [
                    Documentation.FieldCaption = "开始年份,日期区间表从开始年份1月1日起。",
                    Documentation.FieldDescription = "日期区间表从开始年份1月1日起",
                    Documentation.SampleValues = { Date.Year( DateTime.LocalNow( ) ) - 1 } // Current Year
                ]),
                
                optional CalendarYearEnd as (type number meta [
                    Documentation.FieldCaption = "结束年份,日期区间表至结束年份12月31日止。",
                    Documentation.FieldDescription = "日期区间表至结束年份12月31日止",
                    Documentation.SampleValues = { Date.Year( DateTime.LocalNow( ) ) } // Previous Year
                ]),
    
                optional CalendarFirstDayOfWeek as (type text meta [
                    Documentation.FieldCaption = "定义一周开始日,从 Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday中选择一个,缺省默认为Monday。",
                    Documentation.FieldDescription = "从 Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday中选择一个,缺省默认为Monday。",
                    Documentation.SampleValues = { "Monday" }
                ]),
    
                optional CalendarCulture as (type text meta [
                    Documentation.FieldCaption = "指定日期区间表显示月以及星期几的名称是中文或英文,en 表示英文,zh 表示中文,缺省默认与系统一致。",
                    Documentation.FieldDescription = " en 表示英文,zh 表示中文,缺省默认与系统一致。",
                    Documentation.SampleValues = { "zh" }
                ])
    
            ) 
            as table meta [
                Documentation.Name = "构建日期区间表",
                Documentation.LongDescription = "创建指定年份之间的日期区间表。并可进行各种设置。",
                Documentation.Examples = {
                [
                    Description = "返回当前年份日期区间表",
                    Code = "CreateCalendarPeriod()",
                    Result = "当前年份日期区间表。"
                ],
                [
                    Description = "返回指定年份的日期区间表",
                    Code = "CreateCalendarPeriod( 2017 )",
                    Result = "返回2017/01/01至2017/12/31之间的日期区间表。"
                ],
                [
                    Description = "返回起止年份之间的日期区间表",
                    Code = "CreateCalendarPeriod( 2015 , 2017 )",
                    Result = "返回2015/01/01至2017/12/31之间的日期区间表。"
                ],
                [
                    Description = "返回起止年份之间的日期区间表,并指定周二为每周的第一天",
                    Code = "CreateCalendarPeriod( 2015 , 2017 , ""Tuesday"" )",
                    Result = "2015/01/01至2017/12/31之间的日期区间表,且周二是每周的第一天。"
                ],
                [
                    Description = "返回起止年份之间的日期区间表,并指定周二为每周的第一天,并使用英文显示名称。",
                    Code = "CreateCalendarPeriod( 2015 , 2017 , ""Tuesday"", ""en"" )",
                    Result = "2015/01/01至2017/12/31之间的日期区间表,且周二是每周的第一天,并使用英文显示月名称及星期几的名称。"
                ]
                }
            ],
    
        f_create_calendar_period = ( 
            optional CalendarYearStart as number, 
            optional CalendarYearEnd as number, 
            optional CalendarFirstDayOfWeek as text, 
            optional  CalendarCulture as text) =>
        
        let
    
            begin_year = CalendarYearStart ,
            end_year = CalendarYearEnd ,
            first_day_of_week = if Text.Lower( CalendarFirstDayOfWeek ) = "monday" then Day.Monday
                                else if Text.Lower( CalendarFirstDayOfWeek ) = "tuesday" then Day.Tuesday
                                else if Text.Lower( CalendarFirstDayOfWeek ) = "wednesday" then Day.Wednesday
                                else if Text.Lower( CalendarFirstDayOfWeek ) = "thursday" then Day.Thursday
                                else if Text.Lower( CalendarFirstDayOfWeek ) = "friday" then Day.Friday
                                else if Text.Lower( CalendarFirstDayOfWeek ) = "saturday" then Day.Saturday
                                else if Text.Lower( CalendarFirstDayOfWeek ) = "sunday" then Day.Sunday
                                else if CalendarFirstDayOfWeek <> null then error "参数错误:参数CalendarFirstDayOfWeek必须是Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday中的一个。"
                                else Day.Monday ,
            culture = if CalendarCulture <> null then CalendarCulture else null , // "en" , "zh"
            y1 = if begin_year <> null then begin_year else if end_year <> null then end_year else Date.Year( DateTime.LocalNow() ) ,
            y2 = if end_year <> null then end_year else if begin_year <> null then begin_year else Date.Year( DateTime.LocalNow() ) ,
            
            // calendar list is here:
            
            calendar_list = { Number.From ( #date( Number.From( 2016 ) , 1 , 1 ) ) .. Number.From( #date( Number.From( 2017 ) , 12, 31 ) ) },
            calendar_list_table = Table.FromList(calendar_list, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
            #"Changed Type" = Table.TransformColumnTypes( calendar_list_table ,{{"Column1", type date}}),
            #"Renamed Columns" = Table.RenameColumns(#"Changed Type",{{"Column1", "Date"}}),
            #"Inserted Year" = Table.AddColumn(#"Renamed Columns", "Year", each Date.Year([Date]), Int64.Type) ,
    
            // build the period for year-month
    
            period_year_month = 
            let
                #"Inserted PeriodType" = Table.AddColumn(#"Inserted Year", "PeriodType", each "Month" , type text),
                #"Inserted Month" = Table.AddColumn(#"Inserted PeriodType", "PeriodNameOrderBy", each Date.Month([Date]), Int64.Type),
                #"Inserted Month Name" = Table.AddColumn(#"Inserted Month", "PeriodName", each Date.MonthName([Date]), type text)
            in
                #"Inserted Month Name",
    
            // build the period for year-week
    
            period_year_week = 
            let
                #"Inserted PeriodType" = Table.AddColumn(#"Inserted Year", "PeriodType", each "Week" , type text),
                #"Inserted Week" = Table.AddColumn(#"Inserted PeriodType", "PeriodNameOrderBy", each Date.WeekOfYear([Date]), Int64.Type),
                #"Inserted Week Name" = Table.AddColumn(#"Inserted Week", "PeriodName", each "W" & Text.From( [PeriodNameOrderBy] ) , type text)
            in
                #"Inserted Week Name",
    
            // build the period for year-date
    
            period_year_date = 
            let
                #"Inserted PeriodType" = Table.AddColumn(#"Inserted Year", "PeriodType", each "Day" , type text),
                #"Inserted Date" = Table.AddColumn(#"Inserted PeriodType", "PeriodNameOrderBy", each Number.From([Date]) , Int64.Type),
                #"Inserted Date Name" = Table.AddColumn(#"Inserted Date", "PeriodName", each Text.From( [Date] ) , type text)
            in
                #"Inserted Date Name",
    
            combined_month_week_date_period = Table.Combine( { period_year_month , period_year_week , period_year_date } ),
            #"Inserted PeriodTypeOderby" = Table.AddColumn( combined_month_week_date_period , "PeriodTypeOrderBy", 
                each if [PeriodType] = "Month" then 1 else if [PeriodType] = "Week" then 2 else 3  , Int64.Type )
    
        in
            #"Inserted PeriodTypeOderby"
    in
        Value.ReplaceType( f_create_calendar_period , calendar_period_type )
    
    

    这种构造方式与日期表如出一辙,保留了灵活性可以应对复杂的需求。

    这里的M构造的编写使用Visual Studio Code进行,它对编写M提供了实时的提示和着色,有兴趣自己编写M的伙伴可以尝试,如下:

    日期表与日期区间表的联动

    细心的话可以发现,日期区间表在每一个子表,前述的YearMonthPeriod,YearWeekPeriod以及YearDatePeriod中都存在同样的日期字段,也就是例如2016/01/01会出现3次,那么就无法使用日趋区间表的日期字段与业务事实表来关联,因为业务事实表也会存在多个相同日期,关联将导致直接多对多关系,这是不允许的。

    这里可以使用日期表作为桥表,将日期区间表与业务事实表隔离,如下所示:

    除了进行隔离,为了使日期区间表可以对业务事实表起到筛选作用,需要启动PowerBI提供的双向筛选器。如此一来,就可以顺利地完成两类任务:

    1. 使用日期表进行静态日期筛选。
    2. 使用日期区间表进行动态日期区间筛选。

    总结

    这里给出了日期表的伴侣日期区间表,它们可以在一起完美地配合工作,为从日期维度进行对比分析起到重要支持。如果发现BUG或更好方法,欢迎一起探讨。

    相关文章

      网友评论

        本文标题:PowerBI中构建日期区间表的终极方法

        本文链接:https://www.haomeiwen.com/subject/kaqszxtx.html