优化Power BI中的Power 优化Power BI中的Power Query合并查询效率,Part 1:通过删除列来实现

2020-06-17 15:09:05 浏览数 (1)

本篇文章主体部分为翻译Chris Webb的一篇文章。

合并查询在Power Query中是很成熟的应用,相当于SQL中的各种JOIN(抽时间会写几篇SQL的join,算是SQL的小核心)。但同时,在Power Query中合并查询是一个常见的影响刷新效率的因素。在我的工作中,经常会遇到对一些非文件夹性质的数据源进行合并查询操作,所以我一直在想,有没有办法可以对其进行优化。最近我正好做了一些测试,希望这些结果能够帮助到大家。

以下是我的测试数据源,只有一个CSV格式的文件,100万行7列数字格式的数据A, B C, D, E, F 和G:

在本次测试当中,我使用了SQL Server 事件探查器去计算刷新的时间。以下两个事件的持续时间是我们关注的重点:

  • Progress Report End/25 Execute SQL
  • Progress Report End/17 Read Data

第一个我想搞清楚的问题是:

表中列的数量是否影响合并查询时的效率?

首先,我对这个CSV文件创建了两个连接,按照惯例,将第一行转为标题,将7列数字全都定义为整数格式。M代码也是非常简洁明了:

代码语言:javascript复制
let
  Source = Csv.Document(
    File.Contents("C:NumbersMoreColumns.csv"), 
    [Delimiter = ",", Columns = 7, 
     Encoding = 65001, QuoteStyle = QuoteStyle.None]
  ),
  #"Promoted Headers" = 
   Table.PromoteHeaders(Source, [PromoteAllScalars = true]),
  #"Changed Type" = Table.TransformColumnTypes(
    #"Promoted Headers", 
    {
      {"A", Int64.Type}, 
      {"B", Int64.Type}, 
      {"C", Int64.Type}, 
      {"D", Int64.Type}, 
      {"E", Int64.Type}, 
      {"F", Int64.Type}, 
      {"G", Int64.Type}
    }
  )
in
  #"Changed Type"

将两个查询都取消加载,然后建立第三个查询,使用Table.NestedJoin函数将两个查询进行内联结:

代码语言:javascript复制
let
  Source = Table.NestedJoin(
   First, {"A"}, Second, {"A"}, 
   "Second", JoinKind.Inner),
  #"Expanded Second" = Table.ExpandTableColumn(
    Source, 
    "Second", 
    {"A", "B", "C", "D", "E", "F", "G"}, 
    {"Second.A", "Second.B", "Second.C", 
     "Second.D", "Second.E", "Second.F", "Second.G"}
  )
in
  #"Expanded Second"

当我刷新这个查询时,在SQL Server 事件探查器中可以看到两个过程的持续时间:

  • Progress Report End/25 Execute SQL – 40 秒
  • Progress Report End/17 Read Data – 56 秒

相当慢。

但是如果我们合并的表都只有一列呢,还会像合并7列那么慢吗?

为了这样测试,我在两个查询中又添加了一个步骤,删除B-G列,只剩下A列:

代码语言:javascript复制
let
  Source = Csv.Document(
    File.Contents("C:NumbersMoreColumns.csv"), 
    [Delimiter = ",", Columns = 7, 
     Encoding = 65001, QuoteStyle = QuoteStyle.None]
  ),
  #"Promoted Headers" = 
   Table.PromoteHeaders(Source, [PromoteAllScalars = true]),
  #"Changed Type" = Table.TransformColumnTypes(
    #"Promoted Headers", 
    {
      {"A", Int64.Type}, 
      {"B", Int64.Type}, 
      {"C", Int64.Type}, 
      {"D", Int64.Type}, 
      {"E", Int64.Type}, 
      {"F", Int64.Type}, 
      {"G", Int64.Type}
    }
  ),
  #"Removed Other Columns" = 
   Table.SelectColumns(#"Changed Type", {"A"})
in
  #"Removed Other Columns"

接着,我又重新建了一个查询,只合并两个表的一列:

代码语言:javascript复制
let
  Source = Table.NestedJoin(
   First, {"A"}, Second, {"A"}, 
   "Second", JoinKind.Inner),
  #"Expanded Second" = 
   Table.ExpandTableColumn(Source, "Second", {"A"}, {"Second.A"})
in
  #"Expanded Second"

查询结束后,SQL Server 事件探查器显示:

  • Progress Report End/25 Execute SQL – 9 秒
  • Progress Report End/17 Read Data – 1 秒

很明显,快多了。

不过我转念一想:如果是直接查询的表的大小影响了性能,而不是由于合并查询呢?所以,我在合并查询的最后一步又添加了一步:

代码语言:javascript复制
let
  Source = Table.NestedJoin(
   First, {"A"}, Second, {"A"}, 
   "Second", JoinKind.Inner),
  #"Expanded Second" = 
   Table.ExpandTableColumn(Source, "Second", {"A"}, {"Second.A"}),
  #"Counted Rows" = Table.RowCount(#"Expanded Second")
in
  #"Counted Rows"

接下来我又重新运行了一遍上面的查询。我的想法是,合并查询最终只返回一个单独的值,也就是数据量大小,所以不会成为增加查询时间的因素。

以下是运行以上查询时使用7列的表进行查询的时间:

  • Progress Report End/25 Execute SQL – 56 秒
  • Progress Report End/17 Read Data – 0 秒

以下是运行以上查询时使用1列的表进行查询的时间:

  • Progress Report End/25 Execute SQL – 14 秒
  • Progress Report End/17 Read Data – 0 秒

以上的确能够得出结论:合并查询时,列数的多少的确会影响效率,

以上还揭示了:在以上两个查询中,读取数据是立刻发生的,几乎不占用时间,相比之下,最开始的两次查询中读取数据的时间甚至要比执行SQL语句花费的时间更长。

为什么表的列数会影响合并查询的性能的?因为合并查询是发生在内存当中,所以合并查询的表越大,内存需求就越大,也就需要更多的分页,因为超过了分页文件256MB的限制。当每个表中含有两列时合并查询会提交584MB数据,而如果时合并查询两个7列的表,最大会提交3GB的数据。

所以最后,我们可以从容地得出结论:

在合并查询前,去掉不必要的列,的确可以提升刷新效率。

其实合并查询删掉不必要的列,可以有两种方式,一种是如今天说的,在合并查询之前删掉;另外,我们也可以在合并查询后对不需要的列进行删除。

从逻辑上来看,合并查询后再删除列,很明显要比今天说的浪费时间。

然而事实真的是如此吗?

还记得我们之前说过的微软的产品无处不在的节省算力吗?

节省算力:提前知晓同一字段的所有筛选器,先进行合并,再进行计算,避免对同一字段重复计算。

这就是下一篇的内容了。

参考文档:

Chris Webb

https://blog.crossjoin.co.uk/2020/05/31/optimising-the-performance-of-power-query-merges-in-power-bi-part-1

0 人点赞