【Power BI DAX】将一系列数字转化为数字区间(文本型)

文章出处:http://radacad.com/fun-with-dax-compressing-numeric-sequences-into-text

这篇文章的区间并不能成为一个slicer, 我也没想到这么转了之后有什么用途,但是佩服作者在DAX 中使用变量使用得炉火纯清,

特地转到博客下面以备有兴趣时再研究下。

 

This might be a group of values such as 1, 2, 3, 4, 7, 8, 9, 12, 13:  (note there are gaps) with the expected result grouping the numbers that run in a sequence together to produce text like “1-4, 7-9, 12-13”.  Essentially to identify gaps when creating the text.  

First I created some working data in a clean Power BI Desktop model by creating the following calculated table.

Table = GENERATESERIES(1,50)

1

Table = GENERATESERIES(1,50)

This provides a single column table with 50 rows in a sequence.  I use the ‘Table'[Value] field in a slicer and disabled the single select formatting option so I could click a series of numbers.

The image below shows the slicer with some values selected AND it shows the text generated by the finished measure.

Now let’s take a look at the measure.  For readability, I break the measure into steps using variables.  I use a very unimaginative naming system but will describe the point of each variable as I go.

Measure = VAR A = SELECTCOLUMNS('Table',"N",[Value])

1

2

Measure =

VAR A = SELECTCOLUMNS('Table',"N",[Value])

Variable A will store a table expression that only contains rows from ‘Table’ that have been selected using the slicer.  The table expression stored in A will only have a single column and only as many rows as there are selections on the slicer.  In my example, that means just 9 of the 50 rows.

Measure = VAR A = SELECTCOLUMNS('Table',"N",[Value]) VAR B = ALL('Table'[Value])

1

2

3

Measure =

VAR A = SELECTCOLUMNS('Table',"N",[Value])

VAR B = ALL('Table'[Value])

Variable B will store every value from the original ‘Table’.  This is mainly done for readability when I start to play table A off against table B.  Table B will contain 50 rows (1 through 50).

Measure = VAR A = SELECTCOLUMNS('Table',"N",[Value]) VAR B = ALL('Table'[Value]) VAR GAPS = SELECTCOLUMNS(EXCEPT(B,A),"Gap Value",[Value])

1

2

3

4

Measure =

VAR A = SELECTCOLUMNS('Table',"N",[Value])

VAR B = ALL('Table'[Value])

VAR GAPS = SELECTCOLUMNS(EXCEPT(B,A),"Gap Value",[Value])

Variable GAPS creates a list of values that exist in B, that doesn’t exist in A.  This will create an inverse list of values and should be the numbers from our ‘Table’ that aren’t selected.   The column in this table is renamed.  The number of rows in A + GAPS should always = 50 in this case.

Measure = VAR A = SELECTCOLUMNS('Table',"N",[Value]) VAR B = ALL('Table'[Value]) VAR GAPS = SELECTCOLUMNS(EXCEPT(B,A),"Gap Value",[Value]) VAR C = SELECTCOLUMNS(A,"End Num",[N])

1

2

3

4

5

Measure =

VAR A = SELECTCOLUMNS('Table',"N",[Value])

VAR B = ALL('Table'[Value])

VAR GAPS = SELECTCOLUMNS(EXCEPT(B,A),"Gap Value",[Value])

VAR C = SELECTCOLUMNS(A,"End Num",[N])

Variable C makes a copy of A but renames the only column in the table to be “End Num”.

Measure = VAR A = SELECTCOLUMNS('Table',"N",[Value]) VAR B = ALL('Table'[Value]) VAR GAPS = SELECTCOLUMNS(EXCEPT(B,A),"Gap Value",[Value]) VAR C = SELECTCOLUMNS(A,"End Num",[N]) --- Get a bunch of possible sequences VAR D = GENERATE( A, FILTER( C, [End Num] >= [N]) )

1

2

3

4

5

6

7

8

9

10

11

12

Measure =

VAR A = SELECTCOLUMNS('Table',"N",[Value])

VAR B = ALL('Table'[Value])

VAR GAPS = SELECTCOLUMNS(EXCEPT(B,A),"Gap Value",[Value])

VAR C = SELECTCOLUMNS(A,"End Num",[N])

--- Get a bunch of possible sequences

VAR D = GENERATE(

            A,

            FILTER(

               C,

                [End Num] >= [N])

                )

Variable D now creates a Cartesian join for every row in variable A with every row in variable C, but only where the value in C is greater than the value in A and at this point will look something like this:

Measure = VAR A = SELECTCOLUMNS('Table',"N",[Value]) VAR B = ALL('Table'[Value]) VAR GAPS = SELECTCOLUMNS(EXCEPT(B,A),"Gap Value",[Value]) VAR C = SELECTCOLUMNS(A,"End Num",[N]) --- Get a bunch of possible sequences VAR D = GENERATE( A, FILTER( C, [End Num] >= [N]) ) -- Make a list of sequences WTIH a gap in them VAR E = SELECTCOLUMNS( GENERATE( D , FILTER( GAPS, [Gap Value]>=[N] && [Gap Value] <= [End Num]) ), "N1",[N] , "N2",[End Num] +0 )

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

Measure =

VAR A = SELECTCOLUMNS('Table',"N",[Value])

VAR B = ALL('Table'[Value])

VAR GAPS = SELECTCOLUMNS(EXCEPT(B,A),"Gap Value",[Value])

VAR C = SELECTCOLUMNS(A,"End Num",[N])

--- Get a bunch of possible sequences

VAR D = GENERATE(

            A,

            FILTER(

               C,

                [End Num] >= [N])

                )

-- Make a list of sequences WTIH a gap in them

VAR E = SELECTCOLUMNS(

            GENERATE(

                D ,

                FILTER(

                        GAPS,

                        [Gap Value]>=[N] && [Gap Value] <= [End Num])

                        ),

            "N1",[N]  ,

            "N2",[End Num] +0 )

Variable E now makes a list of the number ranges (start/finish) generated in variable D, but keeps sequences that DO contain a value stored in the GAPS variable.  This is looking for bad sequences that we want to discard as we only want to keep sequences without gaps.

Measure = VAR A = SELECTCOLUMNS('Table',"N",[Value]) VAR B = ALL('Table'[Value]) VAR GAPS = SELECTCOLUMNS(EXCEPT(B,A),"Gap Value",[Value]) VAR C = SELECTCOLUMNS(A,"End Num",[N]) --- Get a bunch of possible sequences VAR D = GENERATE( A, FILTER( C, [End Num] >= [N]) ) -- Make a list of sequences WTIH a gap in them VAR E = SELECTCOLUMNS( GENERATE( D , FILTER( Gaps, [Gap Value]>=[N] && [Gap Value] <= [End Num]) ), "N1",[N] , "N2",[End Num] +0 ) VAR F = EXCEPT(D,E)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

Measure =

VAR A = SELECTCOLUMNS('Table',"N",[Value])

VAR B = ALL('Table'[Value])

VAR GAPS = SELECTCOLUMNS(EXCEPT(B,A),"Gap Value",[Value])

VAR C = SELECTCOLUMNS(A,"End Num",[N])

--- Get a bunch of possible sequences

VAR D = GENERATE(

            A,

            FILTER(

               C,

                [End Num] >= [N])

                )

-- Make a list of sequences WTIH a gap in them

VAR E = SELECTCOLUMNS(

            GENERATE(

                D ,

                FILTER(

                        Gaps,

                        [Gap Value]>=[N] && [Gap Value] <= [End Num])

                        ),

            "N1",[N]  ,

            "N2",[End Num] +0 )  

VAR F = EXCEPT(D,E)

Variable F takes our list of every data range pair that we stored in variable D, but now remove the bad sequences identified using variable E.  None of the pairs now stored in variable F will have gaps, but there will be overlaps.

Measure = VAR A = SELECTCOLUMNS('Table',"N",[Value]) VAR B = ALL('Table'[Value]) VAR GAPS = SELECTCOLUMNS(EXCEPT(B,A),"Gap Value",[Value]) VAR C = SELECTCOLUMNS(A,"End Num",[N]) --- Get a bunch of possible sequences VAR D = GENERATE( A, FILTER( C, [End Num] >= [N]) ) -- Make a list of sequences WTIH a gap in them VAR E = SELECTCOLUMNS( GENERATE( D , FILTER( Gaps, [Gap Value]>=[N] && [Gap Value] <= [End Num]) ), "N1",[N] , "N2",[End Num] +0 ) VAR F = EXCEPT(D,E) VAR G = SELECTCOLUMNS( GENERATE( F, FILTER( SELECTCOLUMNS( f, "D",[N] , "E",[End Num] +0 ) , ([D]>[N] && [D]<=[End Num]) || ([E]>=[N] && [E]<[End Num]) ) ) ,"D",[D],"E",[E])

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

Measure =

VAR A = SELECTCOLUMNS('Table',"N",[Value])

VAR B = ALL('Table'[Value])

VAR GAPS = SELECTCOLUMNS(EXCEPT(B,A),"Gap Value",[Value])

VAR C = SELECTCOLUMNS(A,"End Num",[N])

--- Get a bunch of possible sequences

VAR D = GENERATE(

            A,

            FILTER(

               C,

                [End Num] >= [N])

                )

-- Make a list of sequences WTIH a gap in them

VAR E = SELECTCOLUMNS(

            GENERATE(

                D ,

                FILTER(

                        Gaps,

                        [Gap Value]>=[N] && [Gap Value] <= [End Num])

                        ),

            "N1",[N]  ,

            "N2",[End Num] +0 )  

VAR F = EXCEPT(D,E)

VAR G =

    SELECTCOLUMNS(

        GENERATE(

            F,

            FILTER(

                SELECTCOLUMNS(

                    f,

                    "D",[N] ,

                    "E",[End Num] +0

                    ) ,

                ([D]>[N] && [D]<=[End Num])

                ||    

                ([E]>=[N] && [E]<[End Num])

                )

           )

           ,"D",[D],"E",[E])

Variable G now joins variable F back to itself with some filter criteria to help look for pairs in F that are overlapped by another pair in the list, either by the pair starting before, or ending after.  Pairs in F that are NOT overlapped by another pair do not make this list.  It is these pairs that we eventually want to keep, but if we identify the “bad” pairs first, it makes the next step easier.

Measure = VAR A = SELECTCOLUMNS('Table',"N",[Value]) VAR B = ALL('Table'[Value]) VAR GAPS = SELECTCOLUMNS(EXCEPT(B,A),"Gap Value",[Value]) VAR C = SELECTCOLUMNS(A,"End Num",[N]) --- Get a bunch of possible sequences VAR D = GENERATE( A, FILTER( C, [End Num] >= [N]) ) -- Make a list of sequences WTIH a gap in them VAR E = SELECTCOLUMNS( GENERATE( D , FILTER( Gaps, [Gap Value]>=[N] && [Gap Value] <= [End Num]) ), "N1",[N] , "N2",[End Num] +0 ) VAR F = EXCEPT(D,E) VAR G = SELECTCOLUMNS( GENERATE( F, FILTER( SELECTCOLUMNS( f, "D",[N] , "E",[End Num] +0 ) , ([D]>[N] && [D]<=[End Num]) || ([E]>=[N] && [E]<[End Num]) ) ) ,"D",[D],"E",[E]) VAR H = SELECTCOLUMNS( EXCEPT(F,G), "Output", IF( [N] = [End Num], [N], [N] & "-" & [End Num] ) ) RETURN CONCATENATEX(H,[Output], ", ")

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

Measure =

VAR A = SELECTCOLUMNS('Table',"N",[Value])

VAR B = ALL('Table'[Value])

VAR GAPS = SELECTCOLUMNS(EXCEPT(B,A),"Gap Value",[Value])

VAR C = SELECTCOLUMNS(A,"End Num",[N])

--- Get a bunch of possible sequences

VAR D = GENERATE(

            A,

            FILTER(

               C,

                [End Num] >= [N])

                )

-- Make a list of sequences WTIH a gap in them

VAR E = SELECTCOLUMNS(

            GENERATE(

                D ,

                FILTER(

                        Gaps,

                        [Gap Value]>=[N] && [Gap Value] <= [End Num])

                        ),

            "N1",[N]  ,

            "N2",[End Num] +0 )  

VAR F = EXCEPT(D,E)

VAR G =

    SELECTCOLUMNS(

        GENERATE(

            F,

            FILTER(

                SELECTCOLUMNS(

                    f,

                    "D",[N] ,

                    "E",[End Num] +0

                    ) ,

                ([D]>[N] && [D]<=[End Num])

                ||    

                ([E]>=[N] && [E]<[End Num])

                )

           )

                ,"D",[D],"E",[E])  

VAR H =  

    SELECTCOLUMNS(

        EXCEPT(F,G),

        "Output",

            IF(

                [N] = [End Num],

                [N],

                [N] & "-" & [End Num]

                )

           )

                                            

RETURN CONCATENATEX(H,[Output], ", ")

The last variable H, uses the EXCEPT function to only return pairs from variable F that were not identified as bad (variable G).  The IF function is used to decide if a hyphen is needed, or if the pair starts and stops at the same value.

The CONCATENATEX function finally pivots the list stored in variable H that looks like :


Into

The PBIX file can be downloaded here, and allows you to play with a slicer and see the effect it has on the calculated measure on the same canvas.

This approach could be tweaked to use dates in place of numbers so you can create a text-based description of your date ranges.  This might be useful in a report title especially given that it can update based on user interaction with slicers.

The code can also simply run over a column of numbers (or dates) without the need for a slicer.   I found the question interesting and enjoyed coming up with a solution that I’m sure might be helpful to someone after something similar.

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
PowerBI系列之Power Query专题1.  获取数据 数据源种类介绍和获取Excel数据源输入数据和拷贝数据:创建辅助表解析Json/XML数据格式获取Web网页数据和URL添加动态参数连接数据的四种模式:Import、DirectQuery、Live Connection、Dual双 属于混合模式连接数据库:Sql server、 Mysql(直连但是必须先安装一个mysql插件)DirectQuery直连查询:Sql serverODBC方式获取数据表关联或多个Sql或调用存储过程获取数据SQL中动态传参和自定义函数: sql中使用参数或数据库名称使用参数连接Sharepoint和OneDrive数据源连接Dataset和Dataflow 替换本地数据源为Sharepoint数据源并保留数据处理操作 终止当前数据刷新Loading:Cancel Query数据源设置-重置数据连接凭证PBIDS连接数据源创建和使用报表模块(输入或值列表)利用报表模板和参数控制线下报表数据权限DirectQuery启用自动页面刷新和更改检测管理聚合表提高DirectQuery查询性能动态M查询参数提高DirectQuery查询性能添加数据刷新时间 DateTime.LocalNow()和Getdate()2.  数据清洗和M语言M语言和官方文档介绍PowerQuery中查阅M函数:=#shared, Ctrl+Space提示数据清洗之常用技能:提升标题、更改数据类、保留删除错误或空行,删除重复项、选择列和删除列、填充单元格、合并列、拆分、提取、替换、条件替换、添加自定义列,添加条件列、添加索引列、分组、添加年月日列、追加和合并查询透视和逆透视以及转置合并单元格的Excel文件处理导入文件夹中多Excel文件并合并解决多文件合并中列顺序不一致使用参数和函数批量导入文件 文本中提取中文、英文、数字等处理双引号转义 列拆分详解解决列名改变错误解决列丢失错误动态显示、排序和重命名列为所有列名添加前缀列名字母大写和分隔符调整Trim标题列中的多余空格如何处理load数据错误为什么load的Excel数据有null空行为什么load的Excel数据标题在第二行灵活添加占位符规范同类相似数据数据按多列排序为分组添加Index序号分组内值合并诊断工具分析数据处理过程PowerQuery小技巧分享 新冠病例活动轨迹地图标识 

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值