从数据到信息
从信息到洞察

理解 GROUPBY

GROUPBY 函数在语法上类似于 SUMMARIZE,但不提供 SUMMARIZE 生成的筛选上下文,也不提供可转换的行上下文。它的目的是通过 CURRENTGROUP 函数对分组后的每一行应用迭代器。

GROUPBY

GROUPBY ( <Table>, [<GroupBy_ColumnName>, ...], [<Name>, <Expression>], [ … ] )

创建一个包含指定列的分组表。

参数 属性 描述
Table 需要分组的表.
GroupBy_ColumnName 可选
可重复
用于分组的列.
Name 可选
可重复
需要添加的列名.
Expression 可选
可重复
新列使用的表达式

示例用法

例如,你可以这样使用 GROUPBY 重写 SUMMARIZE 的第一个案例:

EVALUATE
GROUPBY (
    Sales,
    'Date'[Calendar Year Number],
    "Quantity", SUMX ( CURRENTGROUP (), Sales[Quantity] )
)

你必须在表最外层的列表达式中使用 CURRENTGROUP,因此它不支持多层迭代函数嵌套。如果需要生成动态 DAX 查询,并且希望对另一个表表达式的结果应用特定的聚合,GROUPBY 函数会非常有用。

GROUPBY 仅用于对其他表函数(通常是 SUMMARIZECOLUMNSADDCOLUMNS)返回的小型结果集做二次计算。GROUPBY 不应该用于聚合数据模型中的物理表,因为它不会将计算发送到存储引擎。虽然名称类似于 SQL 语言的 GROUPBY 条件,它们其实是完全不同的

SUMMARIZE 的局限

除了性能方面的限制,SUMMARIZE 的另一个限制是不能聚合在其内部创建的派生列,例如,观察下面的查询,它看起来应该为每个产品类别返回相关子类别平均价格的最大值。

EVALUATE
ADDCOLUMNS (
    SUMMARIZE (
        ADDCOLUMNS (
            SUMMARIZE (
                Product, 
                'Product Category'[Category], 
                'Product Subcategory'[Subcategory] 
            ),
            "Average Price", CALCULATE ( AVERAGE ( Product[Unit Price] ) )
        ),
        'Product Category'[Category]
    ),
    "Max SubCat Avg Price", CALCULATE ( MAX ( [Average Price] ) )
)

但实际上查询会得到一个错误的 MAX ( [Average Price] )。原因是 DAX 只允许你聚合物理列,在聚合函数的本质一文中,我们介绍过类似的等价表达式:

MAX ( table[expression] ) = MAXX ( table, table[expression] )

如果你尝试聚合在查询中新建的列(例如本例中[Average Price]),引擎将无法找到相应的 MAXX 函数。为了获得正确的结果,你需要移除外层的 SUMMARIZE,直接对新建的列进行操作

EVALUATE
ADDCOLUMNS (
    VALUES ( 'Product Category'[Category] ),
    "Max SubCat Avg Price", CALCULATE (
        MAXX (
            ADDCOLUMNS (
                SUMMARIZE (
                    Product,
                    'Product Category'[Category],
                    'Product Subcategory'[Subcategory]
                ),
                "Average Price", CALCULATE ( AVERAGE ( Product[Unit Price] ) )
            ),
            [Average Price]
        )
    )
)

改写后的查询得到了正确结果

但是这种写法的问题也很明显,代码冗长且计算效率不高。如果希望同时计算最低和最高平均价格,你需要复制两份 SUMMARIZE,这会导致更长的执行时间。

使用 GROUPBY 代替 SUMMARIZE

GROUPBY 可以用于聚合查询内部的派生列,避免 ADDCOLUMNS+SUMMARIZE 的冗长代码。但是,即使是 GROUPBY,我们也不能直接使用聚合函数

本文隐藏内容查看价格为3G币,请先
单独购买的内容长期有效,不受时间限制(购买前先刷新当前页面)。加入VIP会员可享受全站权益,性价比更高。

GROUPBY 移除空值

GROUPBY 会自动从结果中删除 CURRENTGROUP 列中有空白结果的行。大多数情况这种行为都是符合直觉的:从结果中删除那些“空白行”,但是当你将使用 CURRENTGROUP 的计算与其他计算混合使用时,结果可能有些出乎意料。

例如,考虑以下查询,它只考虑平均价格高于 500 的产品,并在另一列中返回产品类别名称的字符串长度。

EVALUATE
GROUPBY (
    GROUPBY (
        Product,
        'Product Category'[Category],
        'Product Subcategory'[Subcategory],
        "Average Price", AVERAGEX ( CURRENTGROUP (), Product[Unit Price] )
    ),
    'Product Category'[Category],
    "Max SubCat Avg Price", MAXX (
        CURRENTGROUP (),
        IF ( [Average Price] > 500, [Average Price], BLANK () )
    ),
    "Len", MAXX ( CURRENTGROUP (), LEN ( 'Product Category'[Category] ) )
)

尽管可以对所有产品类别统计名称的长度,但结果也只包括至少一个子类别平均价格高于 500 的产品类别。

查询结果

值得注意的是,由于 GROUPBY 并非迭代函数,所以它既不生成行上下文,也不生成筛选上下文,所以计算字符串长度的代码不能直接引用当前行,仍然要套用 CURRENTGROUP ()函数。

如果需要显示价格列为空的值,只需将 MAXX 函数中的 BLANK()替换为 0

EVALUATE
GROUPBY (
    GROUPBY (
        Product,
        'Product Category'[Category],
        'Product Subcategory'[Subcategory],
        "Average Price", AVERAGEX ( CURRENTGROUP (), Product[Unit Price] )
    ),
    'Product Category'[Category],
    "Max SubCat Avg Price",
        MAXX (
            CURRENTGROUP (),
            IF ( [Average Price] > 500, [Average Price], 0)
        ),
    "Len", MAXX ( CURRENTGROUP (), LEN ( 'Product Category'[Category] ) )
)

完整结果

16
说点什么

1000
 
鼓掌微笑开心憧憬爱你色并不觉得吃瓜doge二哈喵喵思考笑哭捂脸悲伤大哭抓狂汗偷笑打脸捂眼黑线问号晕拜拜闭嘴衰咒骂ok作揖
6 评论数
11 被回复的评论
5 订阅评论的人数
 
查看最近回复
查看最热评论
  订阅本文评论  
最新 最旧 得票最多
提醒
成员
卫东

高老师,我测了下GROUPBY,现在是不是也返回CURRENTGROUP()中的空白行了?

1666235960398.jpg
成员
139****3194

EVALUATE
GROUPBY (
Sales,
‘Date'[Calendar Year Number],
“Quantity”, SUMX ( CURRENTGROUP (), Sales[Quantity] )
)

老师,这里的sumx第一参数currentgroup(),实际是一张年份列的多行表吗? 还是指迭代年份列的行形成的单行单列表,谢谢

成员
lyliuyouyang

高飞老师你好,关于多次聚合的这个问题,我反复理解后还是有疑惑,麻烦你看看我的理解到底那里出了问题。
我上次提问以后,你告诉我说,其实三次聚合也可以,不过当时不太会写,也就不了了之了。现在感觉自己多嵌套一层也没有问题了,我就尝试写了下面的例子,三个表函数
table1和table2的区别是得到最内层的劳务队名称的不重复列的时候,一个是summarize,一个是values。table3和前两个的区别是summarize生成不重复列的时候,有3个列(包含了外面2层的聚合的两列)。我的疑问在于,在table1和table2中,最内层使用addcolumns+summarize(values)的时候得到了一个按照劳务队名称的最大值的虚拟表(此时的表只有2列,劳务队名称和对应的计价金额的最大值),为什么可以被第二层的备注和第三层的年份筛选(虚拟表已经没有这两列了)?我知道有行上下文的转化,不过因为虚拟表已经不是物理表了,按道理说已经无法继续按照其他条目聚合了才对,但是又可以返回正确结果,这个地方太绕了,我自己理解有限,想不通。table3的写法感觉更标准一些,最内层生成虚拟表的时候就考虑了外面2层的聚合条目,更好理解一些,外层的行上下文转化为筛选上下文时,虚拟表中有这一列,可以继续聚合很好理解,table1和table2就不知道怎么能够解释的通。
望老师解答。这个问题我提的次数实在太多,占用老师时间真是非常抱歉!

screencapture-www-daxformatter-com-1588609305472.png
成员
lyliuyouyang

你好,我又有了新的疑惑。在了解到了groupby的等价形式以后,觉得这个等价形式好像还有优化的空间。我也是自己尝试出来的,我写了table1,table2,table3。table1是仿照文章里的写法,第一次聚合采用summarize,summarize参数后面的列需要把两次聚合的都写进去(年份和劳务队名称),但是我在table2中,我就在想为什么第一次聚合为什么要考虑第二次聚合的维度,在第一次的聚合里只写了第一次聚合的列(劳务队名称),外层不变,结果还是可以得到和table1一样的结果,table3直接把第一次聚合的summarize也替换成了values,也可以得到相同的结果。我就在想,这个示例中的规避两次使用summarize的写法是否可以等价成我table2里的写法,起码看起来更自然,第一次聚合不必考虑第二次聚合的维度?甚至为了简化,也可以写成两次都用values的写法?我只是测试了自己的一个表,并不知道是否能够完全替代,对于二次聚合我甚至还尝试了generate,generate的一个好处就是第一次聚合不用考虑第二次聚合的维度,结果其实table2这样的写法也不用?望解惑。

批注 2020-04-03 160624.jpg
成员
lyliuyouyang

我在学习这个函数以后,有个疑惑,就是concatenatex属不属于带x的迭代函数这一类的,因为我尝试以下代码,发现报错,因为这个函数本来大家使用的不多,参考的资料也很少,所以我不明白是不是不能这么写,还是说我的用法出问题了:
VAR table4
GROUPBY (
table1,
[工班],[籍贯],
“平均年龄”, AVERAGEX ( CURRENTGROUP (), [年龄] ),
“籍贯”, CONCATENATEX ( CURRENTGROUP (), [籍贯],[籍贯], “,” )
)
但是像其他的平均、求和、计数,因为只有两个参数,就可以,特此提问。

DAX 圣经

导读

初识 DAX

DAX 基础知识

DAX 原理

DAX 高级原理

基础函数类型

迭代函数

CALCULATE 函数

CALCULATE 调节器

基础表函数

条件判断函数

查找匹配函数

时间智能函数

统计类函数

投影函数

分组/连接函数

集合函数

其他函数