【DAX学习】1、DAX的一些事儿

image.png

DAX是专门为在数据模型上计算业务公式而设计的。 大家可能已经知道什么是数据模型。 如果没有,我们将从数据模型和关系的描述开始,为建立DAX知识奠定基础。


1、什么是数据模型

数据模型是一组由关系链接的表

我们都知道表是什么:一组包含数据的行,每行分为几列。 每列都有一种数据类型,并且包含一条信息。 我们通常将表中的一行称为记录。 表是组织数据的便捷方法。 表虽然本身是最简单的形式,但它本身就是数据模型。 因此,当我们在Excel工作簿中编写名称和数字时,我们正在创建一个数据模型。

如果数据模型包含许多表,则它们很可能通过关系链接在一起。 关系是两个表之间的链接。 当两个表被关联在一起时,我们说它们是相关的。 在图形上,关系由连接两个表的线表示。 图1-1显示了数据模型的示例。

(图1-1)

以下是关系的一些重要方面:

  • 关系中的两个表没有相同的角色。 它们被称为关系的单边和多边,分别用1和*表示。 在图1-1中,重点关注产品和产品子类别之间的关系。 一个子类别包含许多产品,而一个产品只有一个子类别。 因此,产品子类别和产品的关系是1对多的关系

  • 特殊类型的关系是1:1和弱关系。 在1:1关系中,两个表都是单边的,而在弱关系中,两个表都可以是多边的。 这些特殊类型的关系并不常见。后续会讨论这些关系

  • 用于创建关系的列在两个表中通常具有相同的名称,称为关系的键。在关系的单边,该列的每一行都必须具有唯一的值,并且不能包含空格。在很多情况下,相同的值可以在许多不同的行中重复。当列的每一行都有唯一值时,该列称为表的键。

  • 关系可以形成一条链。每个产品都有一个子类别,每个子类别都有一个类别。因此,每个产品都有一个类别。要检索产品的类别,必须遍历两个关系的链。图1-1包含一个由三个关系组成的链示例,从销售开始,一直到产品类别。

  • 在每种关系中,一个或两个小箭头可以确定交叉过滤的方向。图1-1在“销售”和“产品”之间的关系中显示了两个箭头,而所有其他关系只有一个箭头。箭头指示自动筛选关系的方向(交叉筛选),通常是不鼓励使用这种双向过滤器的


2、理解关系的方向

每个关系可以具有一个单向或双向交叉过滤器。 过滤总是从关系的单边到多边进行。 如果交叉过滤器是双向的,也就是说,如果交叉过滤器上有两个箭头,则过滤也会从多边到一侧发生。

  • 比如我们通过图1-2中的以下条件去筛选,会得到图1-3的结果
图1-2

图1-3

这其中的原理是因为:Calendar Year是属于Date表的列。 由于Date是Sales的单边关系,因此引擎会根据年份过滤Sales。 这就是为什么显示的数量按年份进行过滤的原因。而 由于Sales和Product表之间的关系是双向的,因此发生筛选。 当我们在报表中放置产品名称进行计数时,由于每年的过滤器通过Sales表传递到Product表,因此我们获得了每年销售的产品数量。 如果销售和产品之间的关系是单向的,则结果将有所不同。

  • 如果我们通过在行上放置“颜色”并在值区域中添加“日期计数”来修改报告,结果将有所不同,如图1-4所示。
图1-4

行中的过滤器是Product表中的Color列。 由于Product处于Sales关系的一侧,因此正确地过滤了数量。 产品名称计数被过滤,因为它是根据行中的表(即Sales)计算值。 而“日期计数”对于所有行,它始终显示相同的值,即,Date表中的总行数。来自Color列的过滤器不会传播到Date表,因为Date和Sales之间的关系是单向的。 因此,尽管Sales上面有活动的筛选器,但筛选器无法传播到Date,因为关系方向阻止了它。

  • 如果我们更改Date和Sales之间的关系以启用双向交叉过滤,则结果如图1-5所示。
image
图1-5

用图1-5和图1-4对比,可以发现Date的计数随着Color的不同而不同

乍一看,似乎所有的关系都应定义为双向的,以便使过滤器沿任何方向传播并始终返回有意义的结果。 但是现在就可以告诉你用这种方法设计数据模型几乎是不合适的,原因后面会讨论


3、DAX for Excel用户

您可能已经知道DAX有点像的Excel公式语言。 毕竟,DAX的根源是Power Pivot for Excel,开发团队试图使这两种语言保持相似。 这种相似性使向这种新语言的过渡更加容易。 但是,有一些重要的区别。

单元格VS表格

Excel对单元格执行计算。 使用其坐标引用一个单元格。 因此,我们可以编写如下公式:

= (A1 * 1.25) - B2

在DAX中,不存在单元格的概念。 DAX适用于表和列,而不适用于单元格。 因此,DAX表达式引用表和列,这意味着编写代码的方式有所不同。 表和列的概念在Excel中不是新的。 实际上,如果通过使用“格式为表格”功能将Excel范围定义为表格,则可以在Excel中编写引用表格和列的公式。 在图1-6中,SalesAmount列评估一个表达式,该表达式引用同一表中的列而不是工作簿中的单元格。

图1-6

而在DAX中的写法如下:

Sales[SalesAmount] = Sales[ProductPrice] * Sales[ProductQuantity]

如上所见,每列均以其表名为前缀。 在Excel中,我们不提供表名,因为Excel公式在单个表中起作用。 但是,DAX在包含许多表的数据模型上工作。 我们必须指定表名,因为不同表中的两列可能具有相同的名称。

DAX中的许多功能与Excel等效的功能以相同的方式工作。 例如,IF函数在DAX和Excel中的读取方式相同:

Excel IF ( [@SalesAmount] > 10, 1, 0) 
DAX IF ( Sales[SalesAmount] > 10, 1, 0)

Excel和DAX的语法不同的一个重要方面是引用整个列的方式。对于使用过R和Python的人来说再熟悉不过了。 实际上,在[@ProductQuantity]中,@表示“当前行中的值”。 在DAX中,无需指定值必须来自当前行,因为这是该语言的默认行为。 在Excel中,我们可以通过删除@符号来引用整个列(即该列中的所有行)。 如图1-7所示

图1-7

AllSales列的值在所有行中都相同,因为它是SalesAmount列的总计。 换句话说,在当前行中的列的值与整个列的值之间在语法上存在差异。DAX是不同的。 在DAX中,这是编写图1-7的AllSales表达式的方式:

AllSales := SUM ( Sales[SalesAmount] )

检索特定行的列的值与整体使用列之间在语法上没有区别。 DAX理解我们要对列的所有值求和,因为我们在聚合函数(在本例中为SUM函数)中使用列名,这需要将列名作为参数传递。 因此,尽管Excel需要明确的语法来区分要检索的两种类型的数据,但DAX会自动进行歧义消除。


4、Excel and DAX:两种函数式语言

两种语言相似的一个方面是Excel和DAX都是函数式语言。 功能语言基本上是由函数调用的表达式组成。 在Excel和DAX中,尽管语句,循环和跳转的概念在许多编程语言中都是通用的,但它们并不存在。 在DAX中,一切都是表达式。

DAX中的迭代器

在Excel中工作时,一次只能执行一次计算。 前面的示例表明,要计算销售总额,我们创建一个包含价格乘以数量的列。 然后,作为第二步,我们对其求和以计算总销售额。 例如,该数字可用作计算每个产品的销售百分比的分母。

使用DAX,您可以使用迭代器在单个步骤中执行相同的操作。 迭代器完全按照其名称的含义进行操作:它遍历一个表并在表的每一行上执行计算,将结果汇总以产生所请求的单个值。使用前面的示例,我们现在可以使用SUMX迭代器计算所有销售额的总和:

AllSales := SUMX (
Sales, Sales[ProductQuantity] * Sales[ProductPrice]
)

这种方法既有优点又有缺点。

优点是我们可以在一个步骤中执行许多复杂的计算,而不必担心添加只会对特定公式有用的列。缺点是,与使用Excel进行编程相比,使用DAX进行编程的视觉效果更差。 确实,我们没有看到计算价格乘以数量的列; 它仅是整个计算的一部分。

其实我们可以创建一个计算列来计算价格乘以数量的乘积。但是,除非使用DirectQuery和Aggregations,否则它会占用内存,并且会减慢计算速度

5、学习DAX的理论

事实上,DAX要求首先学习理论而并不是编程语言之间的区别。在编写DAX代码之前,我们需要学习DAX理论并彻底理解evaluation contexts是如何工作的,不然我们会一脸懵逼。所以在我们对evaluation contexts没有了解之前,最好不要深入学习下去。不过对于这些概念后面都要介绍

6、DAX for SQL使用者

如果习惯使用SQL语言,那么你已经使用过许多表并创建了列之间的连接来设置关系。

实际上,DAX中的计算就是查询一组由关系连接的表和值。

关系的处理

SQL和DAX之间的第一个区别是模型中关系的工作方式。

在SQL中,我们可以在表之间设置外键来声明关系,但是引擎永远不会在查询中使用这些外键,除非我们显式地使用它们。例如,如果我们有一个Customers表和一个Sales表,其中CustomerKey是Customers中的主键和Sales中的外键,我们可以编写以下查询:

SELECT Customers.CustomerName,  SUM  ( Sales.SalesAmount )  AS SumOfSales 
FROM Sales
 INNER  JOIN Customers 
ON Sales.CustomerKey = Customers.CustomerKey 
GROUP  BY Customers.CustomerName

尽管我们使用外键在表之间声明关系,但仍然需要显式地在查询中声明连接条件。虽然这种方法使查询更加冗长,但它很有用,因为可以在不同的查询中使用不同的连接条件,从而在表达查询的方式上有很大的自由。

在DAX中,关系是模型的一部分,它们都属于外连接。当它们在模型中定义时,我们不再需要在查询中指定连接类型,只要使用与主表相关的列,DAX就会在查询中使用自动左外连接。因此,在DAX中,您将按照如下方式编写之前的SQL查询:


EVALUATE 
SUMMARIZECOLUMNS ( 
       Customers[CustomerName], "SumOfSales", SUM ( Sales[SalesAmount] )
)

因为DAX知道销售和客户之间的现有关系,所以它会按照模型自动进行加入。

DAX是一种函数式语言

SQL是一种声明性语言。我们可以通过使用SELECT语句声明要检索的数据集来定义所需的内容,而不必担心引擎如何实际检索信息。

而DAX是一种函数式语言。在DAX中,每个表达式都是一个函数调用,而且函数参数也可以是其他函数执行出来的结果。参数的计算可能导致DAX执行复杂的查询计划来计算结果。

例如,如果我们只想检索住在欧洲的客户,我们可以编写这个查询

在SQL:


SELECT 
  Customers.CustomerName, 
  SUM ( Sales.SalesAmount ) AS SumOfSales 
FROM 
  Sales 
  INNER JOIN Customers 
  ON Sales.CustomerKey = Customers.CustomerKey 
WHERE 
  Customers.Continent = 'Europe' 
GROUP BY
  Customers.CustomerName

使用DAX,我们不会在查询中声明WHERE条件。相反,我们使用一个特定的函数(FILTER)来获取结果:


EVALUATE 
SUMMARIZECOLUMNS (
      Customers[CustomerName], FILTER(
        Customers, Customers[Continent] = "Europe" ), 
     "SumOfSales", SUM ( Sales[SalesAmount] )
)

DAX是一种编程和查询语言

在SQL中,查询语言和编程语言之间存在明显的区别——即用于在数据库中创建存储过程、视图和其他代码的指令集。

每种SQL语言都有自己的语句,以便程序员使用代码丰富数据模型。然而,DAX实际上没有区分查询和编程。一组丰富的函数可以操作表,并反过来返回表。

DAX和SQL中的子查询和条件

作为查询语言的SQL最强大的特性之一是可以选择使用子查询。

DAX具有类似的概念。然而,对于DAX子查询,它们源于语言的函数特性。

例如,要检索购买价值超过100美元的客户的客户和总销售额,我们可以用SQL编写这个查询:

SELECT CustomerName, SumOfSales
FROM ( SELECT Customers.CustomerName,
SUM ( Sales.SalesAmount ) AS SumOfSales FROM
Sales INNER JOIN Customers
ON Sales.CustomerKey = Customers.CustomerKey GROUP BY
Customers.CustomerName ) AS SubQuery
WHERE
SubQuery.SumOfSales > 100

我们可以在DAX中通过嵌套函数调用得到相同的结果:

EVALUATE 
FILTER (
  SUMMARIZECOLUMNS ( 
      Customers[CustomerName], 
     "SumOfSales", SUM ( Sales[SalesAmount] ) 
), 
  [SumOfSales] > 100
)

此系列是对The_Definitive_Guide_to_DAX第2版的学习笔记

微信公众号:纸上躬行君

你可能感兴趣的:(【DAX学习】1、DAX的一些事儿)