PowerBI DAX:创建接受来自同一个表的部分视觉过滤器的度量

Cod*_*key 3 dax powerbi

我有一个事实表和一个日历表。日历表有一个布尔列,表示某一天是否是假期。

关系

我不想创建一个度量:AllSales,它忽略假日上的任何过滤器,但尊重所有其他日历列上的过滤器。请参阅稍后应用的视觉级别过滤器。

除总金额外,以下措施均有效:

AllSales = CALCULATE(SUM(FactSales[Sales]); ALL('Calendar'[Holiday]); ALL('Calendar'[DateKey]))
Run Code Online (Sandbox Code Playgroud)

结果见下图。单独的几周是可以的,因为这些都可以,但请注意年份的总和是错误的。条形聊天显示正确,但矩阵表中的总计错误。由于视觉级别过滤器Calendar[DateKey]删除了 2019 年第一周,AllSales 的总数应为 700,但它仍计入总数中。

错误结果

总结一下:

  • 视觉水平过滤器打开Calendar[DateKey] > "2019 year 01 week
  • 视觉水平过滤器打开Calendar[Holiday] = False
  • Measure AllSales 应尊重 DateKey 过滤器
  • 衡量所有销售额应忽略所有假日过滤器
  • 视觉效果按列排序Calendar[Year], Calendar[Week]

如果我删除ALL(Calendar[DateKey])它,就像我的ALL(Calendar[Holiday])过滤器被忽略并AllSales变得相当于Sales原始数据列。

如果您不想使用这里的数据,可以使用两个 M 查询来创建 Calendar 和 FactSales 表。只需通过 DateKey 1:* 单向链接它们即可。

创建两个新查询并将以下代码粘贴到高级编辑器(M 语言编辑器)中的每个查询中

日历查询:

let
    Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText("i45WMjIwtNQ1MAQiJR0wB0iBmCFFpalKsTpICoxQFbgl5hSjqTAmqMIEocIIuwpT3CqMDJAcamSA6VCoAiNUBViMMCaowgShAoczTLGqiAUA", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type text) meta [Serialized.Text = true]) in type table [DateKey = _t, year = _t, Week = _t, Holiday = _t]),
    #"Changed Type" = Table.TransformColumnTypes(Source,{{"DateKey", type date}, {"year", Int64.Type}, {"Week", Int64.Type}, {"Holiday", type logical}}),
    #"Renamed Columns" = Table.RenameColumns(#"Changed Type",{{"year", "Year"}})
in
    #"Renamed Columns"
Run Code Online (Sandbox Code Playgroud)

事实销售查询:

let
    Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText("i45WMjQwUNJRMjIwtNQ1MAQipVgdDEEjbILG2ARNsAmaoggaGWCxCCpohE3QGJugCTZBoEWxAA==", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type text) meta [Serialized.Text = true]) in type table [Sales = _t, DateKey = _t]),
    #"Changed Type" = Table.TransformColumnTypes(Source,{{"Sales", type number}, {"DateKey", type date}})
in
    #"Changed Type"
Run Code Online (Sandbox Code Playgroud)

我得出的结论是,DAX/TabularModel 不能很好地与同一个表中的多个切片器/过滤器配合使用,我应该将它们分开。我仍然想知道是否有人有 DAX 解决方案来解决这个问题。

gre*_*gyb 5

So, what happens when you build a visual is that a DAX query is created to return a resultset that is then plotted. You can see an excerpt of the query below. There is more after this, but we have everything we need to understand the behavior you're seeing.


DEFINE
  // This is a table-valued variable, which holds all dates which will be in context.
  // This is based on your visual filter.
  VAR __DS0FilterTable = 
    FILTER(
      KEEPFILTERS(VALUES('Calendar'[DateKey])),
      'Calendar'[DateKey] >= (DATE(2019, 1, 3) + TIME(0, 0, 1))
    )

  // This is a table-valued variable which holds the 1-row, 1-column table based on
  // 'Calendar'[Holiday] holding the value FALSE. This is from your slicer
  VAR __DS0FilterTable2 = 
    TREATAS({FALSE,
      BLANK()}, 'Calendar'[Holiday])

  // This builds up your resultset
  VAR __DS0Core = 
    // SUMMARIZECOLUMNS lists your grouping columns, applies filters, and projects
    // measures onto your resultset
    SUMMARIZECOLUMNS(
      // This rollup defines the grouping columns, whose values are shown on your visual.
      // The rollup defines which columns get subtotals, which means we calculate an
      // additional row in the resultset with filter context cleared from the column
      // which is rolling up. In this case, we'll have a row with no Year context,
      // but with Week context, a row with no Week context, but with Year context, and
      // a row with no context on either.
      ROLLUPADDISSUBTOTAL('Calendar'[Year], "IsGrandTotalRowTotal", 'Calendar'[Week], "IsDM1Total"),

      // I'm separating out the filter args here. These are evaluated in a logical
      // AND. The first includes all dates that pass your filter on the visual.
      // The second includes only 'Calendar'[Holiday]=FALSE. The logical AND of these
      // includes:
      //   {2019-01-04, 2019-01-05, 2020-01-02, 2020-01-03, 2020-01-04, 2020-01-05}
      // Note that we're missing 2020-01-01, which passes the date test, but is a holiday.
      // This is the context for evaluating our measures.
      __DS0FilterTable,
      __DS0FilterTable2,
      // end of filter args

      "Sales_Amount", 'FactSales'[Sales Amount],
      "All_Sales", 'FactSales'[All Sales],
    )
Run Code Online (Sandbox Code Playgroud)

So based on how that is being evaluated, by the time it gets around to your measure, the filter context for dates is:

<whatever is on your visual at that level>

INTERSECT

{2019-01-04, 2019-01-05, 2020-01-02, 2020-01-03, 2020-01-04, 2020-01-05}
Run Code Online (Sandbox Code Playgroud)

That's the filter context in which we evaluate:

AllSales =
CALCULATE (
  SUM ( FactSales[Sales] );
  ALL ( 'Calendar'[Holiday] );
  ALL ( 'Calendar'[DateKey] )
)
Run Code Online (Sandbox Code Playgroud)

So without your ALL ( 'Calendar'[DateKey] ) the filter context for your sum does not include 2020-01-01. Unfortunately, your filter for 'Calendar'[DateKey] > 2019-01-03 also gets cleared by this ALL, which explains your grand total issue. The grand total is no Year context (based on the rollup in SUMMARIZECOLUMNS), no Week context (based on the same), and then based on your measure no Holiday context and no DateKey context.

The reason it works for your column chart is that there is no grand total to show.

Now, a solution that will likely be unsatisfying, because it will seem so very similar, is to define a new column, YearWeek and filter on that, instead of filtering on 'Calendar'[DateKey].

So:

//column
'Calendar'[YearWeek] = 'Calendar'[Year] * 100 + 'Calendar'[Week]

//measure:
All Sales =
CALCULATE (
  SUM ( 'FactSales'[Sales] );
  ALL ( 'Calendar'[Holiday] )
)
Run Code Online (Sandbox Code Playgroud)

Then you can define a filter on 'Calendar'[YearWeek] > 201901. This is logically equivalent, but not DAX equivalent. Your query looks like this now:

DEFINE
  // This is now on 'Calendar'[YearWeek], instead of 'Calendar'[DateKey]
  VAR __DS0FilterTable = 
    FILTER(
      KEEPFILTERS(VALUES('Calendar'[YearWeek])),
      'Calendar'[YearWeek] > 201901
    )

  // same as before
  VAR __DS0FilterTable2 = 
    TREATAS({FALSE,
      BLANK()}, 'Calendar'[Holiday])

  // Textually same as before, but different context
  VAR __DS0Core = 
    SUMMARIZECOLUMNS(
      ROLLUPADDISSUBTOTAL('Calendar'[Year], "IsGrandTotalRowTotal", 'Calendar'[Week], "IsDM1Total"),

      // Here are the filters again:
      // Our intersection of 'Calendar'[Holiday]=FALSE and 'Calendar'[YearWeek]>201901.
      // The 'Calendar'[YearWeek] filter is: {201902, 202001, 202002}
      // so now our filter context doesn't explicitly exclude the date 2020-01-01, which
      // was the problem above.
      __DS0FilterTable,
      __DS0FilterTable2,
      // end of filters

      "Sales_Amount", 'FactSales'[Sales Amount],
      "All_Sales", 'FactSales'[All Sales]
    )

...
Run Code Online (Sandbox Code Playgroud)

Based on the filter context above, the measure works just fine.

So, what's the difference between the following two scenarios:

  1. Filter on 'Calendar'[Holiday] and 'Calendar'[DateKey]
  2. Filter on 'Calendar'[Holiday] and 'Calendar'[YearWeek]

它们是同一张桌子上的两组过滤器。不同之处在于“日历”[假日] 和“日历”[日期键] 具有相同的粒度。它们的交集明确排除了您想要的日期。所以你需要一个额外的东西ALL来覆盖它。ALL不过,范围太宽泛,这会扰乱您的总计。

“Calendar”[Holiday] 和“Calendar”[YearWeek] 的粒度不同。如果您在单个 YearWeek 中有一整周的天数都为 'Calendar'[Holiday]=TRUE,那么您会遇到与上面相同的问题,因为您对 'Calendar'[Holiday] 的过滤器会无意中删除您想要包含的整个“日历”[YearWeek]。

所有这一切的关键点是 DAX 过滤器上下文只是列中文字值的列表。强调的重点很重要。在回答这个问题时,我绞尽脑汁,但将其分解为字面值表让我回到了正轨。

这是一个非常好的问题,感谢您提出这个问题。它让我深入研究了 DAX 的一些细微差别,而我最近不必考虑太多。你的直觉准确地知道事情应该如何表现。