テクテク日記

テクテク=テクノロジー&一歩ずつ(テクテク)

【参照整合性】Power BIにおける不明項目の特定

Power BIを使っていくと、場合によっては不明な空白が突然出現し、思わぬところで悩んでしますことがあります。参照整合性(RI: Referential Integrity)の違反と言われるものですが、以下簡単な例で説明します。

参照整合性の例

参照整合性とは、リレーションナルデータベースにおいて、テーブル間のレコードの整合性を保つ仕組みであり、Power BIのデータモデルに置き換えて考えると、ファクトテーブルの全てのレコード(商品Key, 店舗Key, 顧客key等)がディメンションテーブルに存在することを意味します。ファクトに存在し、ディメンションに存在しない場合、可視化を行うと参照整合性(RI)の違反を引き起こします。

例えば、以下のようなデータモデルを元に可視化したテーブルがあるとします。

ファクトテーブルはSales(売上)とInv(在庫)の2つ、ディメンションテーブルはdProduct(商品マスタ)、dCalendar(日付マスタ)、dWarehouse(倉庫マスタ)の3つで構築されるスタースキーマとなっています。売上と在庫の2つのファクトに対して、dProductはリレーションシップを持っていますので、1つのテーブルで同時に結果を見ることができるわけです。

上記テーブルはdProductのGroupという列フィルターされていますが、ここで例えばdProductテーブルからいくつかの品番を削除してみます(5行だけ削除を想定)。

すると、テーブルは以下のように変わります。

興味深いことに、この結果は以下3つについて考察していくことになります。

  1. 結果への影響
  2. スライサーへの影響
  3. パフォーマンスへの影響

1の「結果への影響」ですが、上図①の通り、Groupが空白となっているもの(全売上高)が出現しています。この現象は

ファクトテーブルにあるレコードがディメンションテーブルに存在しないがゆえに起こる参照整合性(RI)の違反

となります。言い換えれば、フィルターできるはずのレコード(複数の商品アイテム)がないので、Power BIはこれらをひとまとまり(空白)として集計してしまっている、ことになります。

ここで重要なことは、空白の行が出現したことではなく、RIエラーが発生した場合、

気づかずに結果が間違ってしまう可能性がある

ことです。その典型例として、上図②のように、レポート作成者がRIという概念を認識せず、Groupというスライサーにある空白を除外してレポートを表示させた場合、以下のようになります。

ご覧の通り、テーブルにある空白こそ消えたものの、全売上高の合計値が一致しなくなりました。全売上高というメジャーは12ヵ月以上の売上高を含みますが、通常であればこのような指標は余程のニーズがない限り、テーブルに表示させたりしません。
※ 実務上、指標として見るのは年度累積、あるいは直近12ヵ月といった売上数値であり、今回はデモのデータ量が少ない、年間売上に影響がなかったこともあり、分かりやすくするため、敢えて全売上高を表示させています

データ量がそこそこあり、実務で想定される起こりやすいケースとして、

ディメンションテーブル(○○マスタ)の不備

が挙げられます。Power BIを活用する場合、殆どのケースにおいてスタースキーマが構築されます。商品マスタがタイムリーに更新されていればよいですが、そうでない場合に今回のような事例が発生してしまいます。

また、コンサル等の業種のように、顧客からデータを受け取ってデータ分析を行う場合、往々にしてマスタが提供されないことも多く、その場合には自分たちでマスタを構築することになります。更新漏れといったヒューマンエラーが発生しやすい状態になりますので、RI違反の発生及びその要因をいち早く特定することが重要となります。

最後に3番目の「パフォーマンスへの影響」ですが、DirectQueryを活用する場合には下記公式ドキュメントに記載されたことを実施することでパフォーマンスの改善が可能となります。

DAXで参照整合性(RI)の違反を特定

RIの違反を特定する方法はいくつかあります。最もシンプルなやり方は計算列を作って調べることです。なお、RIの違反についてもう一回おさらいしておきますと、

① ファクトにあって、ディメンションにない場合

がこれに当たりますが、

② ファクトになく、ディメンションにある場合ではない

ことに留意する必要があります。後者(②)の場合、全ての項目をディメンションテーブルが全てカバーできていることになりますので、Power BIでビジュアルを作っても何らエラーは起こりません。

これ以降はRI違反の有無の特定方法について紹介していきます。

DAX Studioによる特定

DAX Studioを使ったことがある人は、RI違反の有無を特定することができます。持っていない人は下記よりダウンロードしてインストールすることができます。

daxstudio.org

過去記事もありますので、ご参考までに。

Power BIを立ち上げた状態で、DAX Studioを外部ツールで立ち上げます。

Advancedタブより、View Metricsをクリックすると、右下のウィンドウにてRI Violationsという項目で違反の有無を見ることができます。今回の場合、2という赤い数字が入っていますが、RIの違反があることになります。

DAX Studioを使う方法はRI違反の有無を調べるのにお手軽で良いですが、具体的に何が起こっているのかまでは教えてくれませんので、次の計算列による特定方法を紹介していきたいと思います。

計算列による特定

最もお手軽にチェックが可能な方法が計算列を作ることです。以下①~⑥の順番で計算列を追加していきます。

ここで留意が必要なのは、

  • Salesテーブルに対するRI違反のチェック
  • リレーションシップが1対多

  • データ量に留意

の3つです。

Salesテーブルに対するチェックになりますので、複数のファクトテーブルがある場合、複数の計算列を追加することになります。また、1対多というリレーションシップがあることで、RELATEDというDAX関数を使って、ディメンションテーブルから値(今回の場合、dProduct[Group]の列から、Group名)を抽出することができるのです。最後に、実務データでデータ量が多い場合、この手法ではモデルサイズが増加したり、そもそもデータの更新が遅くなってしまう可能性があるため、現実的ではないかもしれません。

とはいえ、お手軽にチェックができますので、結果は以下のようになります。

計算列として追加した列(RI_Check_Sales)で空白となっている行がRIに違反したものであり、Product列からその品番を確認することができます。しかしながら、データ量が限定的である場合は良いですが、計算列が追加されてしまうこと、複数のファクトテーブルで同じことを行う必要がある、対象品番をカウントしにくい(上図では重複する品番がある)等の制約がありますので、計算列による方法は一時しのぎとして考えたほうが良いでしょう。

DAXによる特定

そこで最後の手法として、DAXで対象品番を抽出し、それをテーブルに表示させるやり方を紹介します。ウォームアップとして、まずは何個の不明品番があるかを調べるメジャーを作ってみます。

  • # MissingItemsAll(不明アイテム数)
# MissingItemsAll =
VAR _missing_list_sales =
    CALCULATETABLE ( VALUES ( Sales[Product] ), ISBLANK ( dProduct[Product] ) )
VAR _missing_list_inv =
    CALCULATETABLE ( VALUES ( Inv[Product] ), ISBLANK ( dProduct[Product] ) )
VAR _missing_list_overall =
    DISTINCT ( UNION ( _missing_list_inv, _missing_list_sales ) )
VAR _result =
    COUNTROWS ( _missing_list_overall )
RETURN
    COALESCE ( _result, 0 )
    

このメジャーはSalesとInvのそれぞれのテーブルで不明な品番(Product)をリスト化し、重複を削除してカウントしたものとなります。最初にISBLANKでdProduct[Product]が空白となっているものを特定(リレーションシップが聞いているため、このような書き方が可能)し、その条件に合う商品をそれぞれのテーブル(Sales / Inv)からVALUESでリスト化しています。なお、COALESCE関数は空白(=RIの違反がなく、不明品番がなかった場合)に備えて、空白ではなくゼロを返すようにしています。

このメジャーを上記テーブルと単一のテーブルにそれぞれドラッグ&ドロップした結果は以下のようになります。

冒頭で5つの品番を削除していますので、5という数字は正しく反映されています。同じ結果で違う書き方として、以下のものがあります。

  • # MissingItemsAll_02(不明アイテム数_02)
# MissingItemsAll_02 =
VAR _missing_sales =
    EXCEPT ( ALL ( Sales[Product] ), ALL ( dProduct[Product] ) )
VAR _missing_inv =
    EXCEPT ( ALL ( Inv[Product] ), ALL ( dProduct[Product] ) )
VAR _missing_all =
    DISTINCT ( UNION ( _missing_sales, _missing_inv ) )
VAR _missing_all_count =
    COUNTROWS ( _missing_all )
RETURN
    COALESCE ( _missing_all_count, 0 )

//ALLはVALUESでも良い

EXCEPTは下記DAX Guideから詳細が分かりますが、EXCEPT( A, B )とある場合、Aテーブルにあって、Bテーブルにないもの、を抽出してくることになります。

イメージとして、Power Queryの左反(Left Anti)の結果になります。

どちらのメジャーでも同じ結果を算出できますので、お好みに合わせてということで良いかと思います。

ここまでは不明品目数について確認してきましたが、最終的にはどのような商品が何個不明であり、それぞれのテーブルに何個あるか、といったニーズがあると思いますので、これら全てに対応できるメジャーを作っていきます。

  • MissingItems_All(最終メジャー)
MissingItems_All =
VAR _missing_list_sales =
    CALCULATETABLE ( VALUES ( Sales[Product] ), ISBLANK ( dProduct[Product] ) )
VAR _missing_list_inv =
    CALCULATETABLE ( VALUES ( Inv[Product] ), ISBLANK ( dProduct[Product] ) )
VAR _missing_list_overall =
    DISTINCT ( UNION ( _missing_list_inv, _missing_list_sales ) )
VAR _count_sales =
    COUNTROWS ( _missing_list_sales )
VAR _count_inv =
    COUNTROWS ( _missing_list_inv )
VAR _count_all_distinct =
    COUNTROWS ( _missing_list_overall )
VAR _count_duplicate =
    COUNTROWS ( _missing_list_inv ) + COUNTROWS ( _missing_list_sales ) - _count_all_distinct
VAR _result =
    "# missing all: " & COALESCE ( _count_all_distinct, 0 )
        & UNICHAR ( 10 ) & "# missing sales: "
        & COALESCE ( _count_sales, 0 )
        & UNICHAR ( 10 ) & "# missing inv: "
        & COALESCE ( _count_inv, 0 )
        & UNICHAR ( 10 ) & "# duplicates: "
        & COALESCE ( _count_duplicate, 0 )
        & UNICHAR ( 10 ) & "missing SKUs: "
        & UNICHAR ( 10 )
        & CONCATENATEX ( _missing_list_overall, [Product], " |" & UNICHAR ( 10 ) )
RETURN
    _result

このメジャーをテーブルビジュアルに入れると、以下の結果が返ってきます。

# missing all: 全ての不明SKU(商品)数

# missing sales: 売上における不明SKU数

# missing inv: 在庫における不明SKU数

# missing duplicates: 売上と在庫で重複する不明SKU数

Missing SKUs: 不明SKUの詳細リスト

最後のメジャーは非常に長ったらしいのですが、このメジャーが1つあれば、今後はデータモデルが更新される度に、RIチェックが自動的に行われますので、非常に便利です。もしRI違反がなければ、下表のように、全ての項目は0となります。

まとめ

  • Power BIでRIの違反が発生することがあり、特定方法を知ることはデバッグに役立つ
  • RI違反が起こると、データ精度・スライサー・クエリパフォーマンス等に影響が生じてしまう可能性がある
  • RIの違反はDAXで容易に特定が可能である