次の方法で共有


多対多のリレーションシップに関するガイダンス

この記事では、Power BI Desktop を使用するデータ モデリング担当者を対象にしています。 ここでは、3 つの異なる多対多モデリング シナリオについて説明します。 また、モデルで正しく設計する方法についてのガイダンスも提供します。

手記

モデル リレーションシップの概要については、この記事では説明しません。 リレーションシップ、それらのプロパティ、またはそれらを構成する方法について完全に理解していない場合は、最初に Power BI Desktop の モデルのリレーションシップに関する記事を読んでお勧めします。

また、スター スキーマの設計について理解していることも重要です。 詳細については、「スター スキーマと Power BIの重要性」を参照してください

3 つの異なる多対多シナリオがあります。 これらは、次の必要があるときに発生する可能性があります。

多対多のディメンションを関連付ける

従来の多対多シナリオでは、銀行の顧客や銀行口座などの 2 つのエンティティが関連付けられます。 顧客は複数のアカウントを持ち、アカウントには複数の顧客を持つことができます。 1 つのアカウントに複数の顧客がある場合、一般的に共同アカウント所有者 呼ばれます。

これらのエンティティのモデル化は簡単です。 1 つの ディメンション テーブル アカウントが格納され、もう 1 つのディメンション テーブルに顧客が格納されます。 ディメンション テーブルに共通する特性として、各テーブルには一意の識別子 (ID) 列があります。 2 つのテーブル間のリレーションシップをモデル化するには、3 番目のテーブルが必要です。 このテーブルは、一般に、ブリッジ テーブルと呼ばれます。 この例では、顧客とアカウントの関連付けごとに 1 つの行を格納することが目的です。 興味深いことに、このテーブルに識別子列のみが含まれている場合は、ファクトレス ファクト テーブルと呼ばれます。

3 つのモデル テーブルの単純な図を次に示します。

3 つのモデル テーブルを示す図。設計については、次の段落で説明します。

最初のテーブルは Accountという名前で、AccountIDAccountの 2 つの列が含まれています。 2 番目のテーブルは AccountCustomerという名前で、AccountIDCustomerIDの 2 つの列が含まれています。 3 番目のテーブルは Customerという名前で、CustomerIDCustomerの 2 つの列が含まれています。 リレーションシップは、どのテーブルにも存在しません。

テーブルを関連付けるために、2 つの一対多リレーションシップが追加されます。 関連テーブルの更新されたモデル図を次に示します。 Transaction という名前のファクト テーブルが追加されました。 アカウント トランザクションを記録します。 ブリッジ テーブルとすべての識別子列が非表示になっています。

4 つのテーブルを含むモデル図を示す図。すべてのテーブルを関連付けるために、一対多リレーションシップが追加されました。

リレーションシップ フィルターの伝達のしくみを説明するために、モデル ダイアグラムがテーブル行を表示するように変更されました。

モデル テーブルとその行を示す図。4 つのテーブルの行の詳細については、次の段落で説明します。

次の箇条書きには、4 つのテーブルの行の詳細が表示されます。

  • Account テーブルには、次の 2 つの行があります。
    • AccountID1Account-01 用です
    • AccountID2Account-02 用です
  • Customer テーブルには、次の 2 つの行があります。
    • CustomerID91 は、Customer-91 用です
    • CustomerID92 は、Customer-92 用です
  • AccountCustomer テーブルには、次の 3 つの行があります。
    • AccountID1 は、CustomerID91 に関連付けられています。
    • AccountID1CustomerID92 に関連付けられています
    • AccountID2 は、CustomerID92 に関連付けられています。
  • Transaction テーブルには、次の 3 つの行があります。
    • Date2019 年 1 月 1 日, AccountID1, Amount100
    • Date2019 年 2 月 2 日, AccountID2, Amount200
    • Date2019 年 3 月 3 日, AccountID1, Amount-25

モデルへの問い合わせで何が起こるかを見てみましょう。

次の図では、Transaction テーブルの Amount 列を要約した 2 つのテーブル ビジュアルがあります。 最初のビジュアルはアカウントごとにグループ化されるため、Amount 列の合計は、アカウント残高を表します。 2 番目のビジュアルは顧客別にグループ化されるため、 列の合計は顧客の残高を表します。

2 つのテーブル ビジュアルが並べて表示されている図。ビジュアルについては、次の段落で説明します。

最初のテーブル ビジュアル (勘定科目残高) には、AccountAmountの 2 つの列があります。 次の結果が表示されます。

  • 口座-01の 残高金額は75です。
  • 口座-02 残高は 200
  • 合計は 275です。

2 番目のテーブル ビジュアル (Customer Balance) には、CustomerAmountの 2 つの列があります。 次の結果が表示されます。

  • Customer-91 残高金額は 275です。
  • Customer-92 の残高は 275です。
  • 合計は 275です。

テーブルの行とアカウント残高のビジュアルを一目で見ると、各勘定と合計金額の結果が正しいことを確認できます。 これは、アカウントをグループ化するたびに、そのアカウントの Transaction テーブルへのフィルター伝達が行われるためです。

ただし、Customer Balance ビジュアルでは何かが正しく表示されません。 このビジュアルの各顧客は、合計残高と同じ残高を持ちます。 この結果は、すべての顧客がすべてのアカウントの共同アカウント所有者である場合にのみ正しい可能性があります。 この例ではそうではありません。 問題があり、フィルターの伝達に関連しています。 フィルターは、Transaction テーブルまで流れるわけではありません。

Customer テーブルから Transaction テーブルへのリレーションシップ フィルターの方向に従うと、Account テーブルと AccountCustomer テーブル間のリレーションシップが間違った方向に伝達されていることを確認できます。 このリレーションシップのフィルターの方向を Bothに設定する必要があります。

モデルが更新されたことを示す図。これで、双方向でフィルター処理されるようになりました。

同じ 2 つのレポート ビジュアルが並べて表示されている図。最初のビジュアルは変更されていませんが、2 番目のビジュアルには変更はありません。

予想どおり、[アカウント残高] ビジュアルに変更はありません。

ただし、Customer Balance ビジュアルには次の結果が表示されます。

  • Customer-91 の残高は 75 です。
  • Customer-92 の残高は 275 です。
  • 合計は 275です。

Customer Balance ビジュアルに正しい結果が表示されるようになりました。 自分のフィルターの指示に従って、顧客の残高がどのように計算されたかを確認します。 また、視覚化の合計が "すべての顧客" を意味していることを理解してください。

モデルの関係に慣れていない人は、結果が正しくないと結論付けることができます。 Customer-91Customer-92 の合計残高が 350 (75 + 275) に等しくないのはなぜですか。

彼らの質問に対する答えは、多対多の関係を理解することです。 各顧客残高は複数の口座残高の加算を表すことができるため、顧客の残高は加法ではありません

多対多ディメンションの関連付けに関するガイダンス

ディメンション テーブル間に多対多リレーションシップがある場合は、次のガイダンスに従います。

  • 各多対多関連エンティティをモデル テーブルとして追加し、ID 列があることを確認します。
  • 関連付けられたエンティティを格納するブリッジ テーブルを追加します。
  • 3 つのテーブル間に一対多リレーションシップを作成します。
  • "1 つ" の双方向リレーションシップを設定して、フィルターの伝達をファクト テーブルに続行できるようにします。
  • 不足している ID 値を持つことが適切でない場合は、Is Nullable プロパティを無効にします。欠損値が取得されると、データの更新は失敗します。
  • ブリッジ テーブルを非表示にします (レポートに必要な他の列またはメジャーが含まれていない場合)。
  • レポートに適さない ID 列を非表示にします (たとえば、列にサロゲート キー値が格納されている場合)。
  • ID 列を表示したままにするのが理にかなっている場合は、リレーションシップの "一" 側にあることを確認します。"多" 側の列は常に非表示にします。 これは、"1" スライドにフィルターを適用すると、フィルターのパフォーマンスが向上するためです。
  • 混乱や解釈の誤りを回避するには、レポート ユーザーに説明を伝えます。テキスト ボックスや視覚的なヘッダー ツールを使用して説明を追加できます。

多対多ディメンション テーブルを直接関連付けすることはお勧めしません。 この設計アプローチでは、多対多カーディナリティとのリレーションシップを設定する必要があります。 概念的には達成できますが、関連する列に重複する値が含まれている可能性があることを意味します。 ただし、これはよく受け入れられる設計手法ですが、ディメンション テーブルには ID 列があります。 ディメンション テーブルでは、リレーションシップの "一" 側として ID 列を常に使用する必要があります。

多対多ファクトを関連付ける

異なる多対多シナリオの種類には、2 つのファクト テーブルを関連付ける必要があります。 2 つのファクト テーブルを直接関連付けることができます。 この設計手法は、迅速かつ簡単なデータ探索に役立ちます。 ただし、明確にするために、通常、この設計アプローチはお勧めしません。 その理由については、このセクションの後半で説明します。

OrderFulfillmentの 2 つのファクト テーブルを含む例を考えてみましょう。 Order テーブルには注文明細行ごとに 1 行が含まれており、Fulfillment テーブルには注文明細行ごとに 0 行以上の行を含めることができます。 Order テーブルの行は販売注文を表します。 Fulfillment テーブルの行は、出荷された注文品目を表します。 多対多リレーションシップは、各テーブルの OrderID 列と、Order テーブルからのみフィルター伝達を関連付けます (つまり、Order テーブルは Fulfillment テーブルをフィルター処理します)。

注文とフルフィルメントの 2 つのテーブルを含むモデルを示す図。

リレーションシップのカーディナリティが Many-to-many に設定され、両方のテーブルに重複する OrderID 列の値を格納できます。 Order テーブルには、注文に複数の行が含まれる可能性があるため、重複する ID 値が存在する可能性があります。 Fulfillment テーブルでは、注文に複数の行を含めることができるため、重複する ID 値が存在する可能性があり、注文明細行は多数の出荷で満たすことができます。

次は、テーブル行を見てみましょう。 Fulfillment テーブルでは、注文行が複数の出荷のフルフィルメントの対象となることがあることに注意してください。 (注文明細行がない場合は、注文がまだ満たされていないことを意味します)。

モデル テーブルの行を示す図。2 つのテーブルの行の詳細については、次の段落で説明します。

2 つのテーブルの行の詳細については、次の箇条書きで説明します。

  • Order テーブルには、次の 5 つの行があります。
    • OrderDate2019 年 1 月 1 日, OrderID1, OrderLine1, ProductIDProd-A, OrderQuantity5, Sales50
    • OrderDate2019 年 1 月 1 日, OrderID1, OrderLine2, ProductIDProd-B, OrderQuantity10, Sales80
    • OrderDate2019 年 2 月 2 日, OrderID2, OrderLine1, ProductIDProd-B, OrderQuantity5, Sales40
    • OrderDate2019 年 2 月 2 日, OrderID2, OrderLine2, ProductIDProd-C, OrderQuantity1, Sales20
    • OrderDate2019 年 3 月 3 日, OrderID3, OrderLine1, ProductIDProd-C, OrderQuantity5, Sales100
  • Fulfillment テーブルには、次の 4 つの行があります。
    • FulfillmentDate2019 年 1 月 1 日, FulfillmentID50, OrderID1, OrderLine1, FulfillmentQuantity2
    • FulfillmentDate2019 年 2 月 2 日, FulfillmentID51, OrderID2, OrderLine1, FulfillmentQuantity5
    • FulfillmentDate2019 年 2 月 2 日, FulfillmentID52, OrderID1, OrderLine1, FulfillmentQuantity3
    • FulfillmentDate2019 年 1 月 1 日, FulfillmentID53, OrderID1, OrderLine2, FulfillmentQuantity10

モデルにクエリを実行するとどうなるかを見てみましょう。 次に示すのは、Order テーブルの OrderID 列ごとに注文とフルフィルメントの数量を比較するテーブルの視覚エフェクトです。

3 つの列を持つテーブル ビジュアルを示す図: OrderID、OrderQuantity、FulfillmentQuantity。

ビジュアルは正確な結果を示します。 ただし、Order テーブル OrderID 列によってのみフィルター処理またはグループ化できるため、モデルの有用性は制限されます。

多対多ファクトの関連付けに関するガイダンス

一般に、多対多カーディナリティを使用して 2 つのファクト テーブルを直接関連付けすることはお勧めしません。 主な理由は、レポート ビジュアルのフィルター処理やグループ化の方法がモデルによって柔軟に提供されないためです。 この例では、ビジュアルが Order テーブル OrderID 列でフィルター処理またはグループ化することだけが可能です。 もう 1 つの理由は、データの品質に関連しています。 データに整合性の問題がある場合は、多対多のカーディナリティと制限付きリレーションシップの特質によりクエリ中に一部の行が省略される可能性があります。

ファクト テーブルを直接関連付けるのではなく、スター スキーマ 設計を実装することをお勧めします。 つまり、ディメンション テーブルを追加します。 次に、これらのディメンション テーブルは、一対多リレーションシップを使用してファクト テーブルに関連付けます。 この設計アプローチは、柔軟なレポート オプションを効率的に提供するために堅牢です。 ディメンション テーブル列を使用してフィルター処理またはグループ化したり、関連するファクト テーブルの列を集計したりできます。

より良い解決策を考えてみましょう。

6 つのテーブル (OrderLine、OrderDate、Order、Fulfillment、Product、FulfillmentDate) で構成されるモデルを示す図。

次の設計変更に注意してください。

  • モデルには、OrderLineOrderDateProductFulfillmentDateの 4 つのテーブルが追加されました。
  • 4 つの追加テーブルは、一対多リレーションシップがファクト テーブルに関連付けられているすべてのディメンション テーブルです。
  • OrderLine テーブルには、OrderLineID 列が含まれています。この列には、OrderID 値に 100 を乗算した値と、OrderLine 列の値 (各注文明細行の ID) が格納されます。
  • Order テーブルと Fulfillment テーブルには、それぞれ OrderLineID 列が含まれるようになり、OrderID 列と OrderLine 列は含めなくなりました。
  • Fulfillment テーブルに OrderDate 列と ProductID 列が含まれるようになりました。
  • FulfillmentDate テーブルには、Fulfillment テーブルにのみリレーションシップがあります。
  • すべての ID 列は非表示になります。

スター スキーマ設計の採用に時間がかかると、次の利点があります。

  • レポート ビジュアルは、ディメンション テーブルから表示される任意の列で "フィルター処理またはグループ化" できます。
  • レポート ビジュアルは、ファクト テーブルから表示される列を "集計" できます。
  • OrderLineOrderDate、または Product テーブルに適用されるフィルターは、両方のファクト テーブルに反映されます。
  • すべてのリレーションシップは一対多であり、各リレーションシップは通常のリレーションシップです。 データ整合性の問題はマスクされません。 リレーションシップの評価の詳細については、「Power BI Desktopでのモデルリレーションシップ」を参照してください。

より高い粒度のファクトを関連付ける

この多対多シナリオは、この記事で既に説明されている他の 2 つのシナリオとは大きく異なります。

DateSalesProductTargetの 4 つのテーブルを含む例を考えてみましょう。 Date テーブルと Product テーブルはディメンション テーブルであり、一対多リレーションシップはそれぞれ Sales ファクト テーブルに関連付けられます。 今のところ、適切なスター スキーマ設計が示されています。 ただし、Target テーブルは、他のテーブルとまだ関連していません。

日付、売上、製品、ターゲットの 4 つのテーブルで構成されるモデルを示す図。

Target テーブルには、CategoryTargetQuantity、および TargetYearの 3 つの列があります。 テーブルの行には、年と製品カテゴリの詳細が表示されます。 つまり、販売実績の測定に使用されるターゲットは、製品カテゴリごとに毎年設定されます。

Sales ファクト テーブルと Target ファクト テーブルを示す図。Target ファクト テーブルには、TargetYear、Category、TargetQuantity の 3 つの列があります。

Target テーブルにはディメンション テーブルよりも高いレベルでデータが格納されるため、一対多リレーションシップを作成することはできません。 さて、それは関係の1つだけに当てはまります。 Target テーブルをディメンション テーブルに関連付ける方法を見てみましょう。

より高い粒度の期間を関連付ける

Date テーブルと Target テーブル間のリレーションシップは、一対多リレーションシップである必要があります。 これは、TargetYear 列の値が日付であるためです。 この例では、各 TargetYear 列にターゲット年の最初の日付が格納されます。

ヒント

日よりも高い時間粒度でファクトを格納する場合は、列のデータ型を Date (または日付キーを使用している場合は整数 ) に設定します。 列に、期間の最初の日を表す値を格納します。 たとえば、年の期間は年の 1 月 1 日として記録され、月の期間はその月の最初の日として記録されます。

ただし、月または日付レベルのフィルターによって意味のある結果が生成されるように注意する必要があります。 特別な計算ロジックがない場合、レポート ビジュアルでは、ターゲットの日付が実際には各年の最初の日であると報告される場合があります。 それ以外のすべての日 (および 1 月を除くすべての月) は、目標数量を空白として集計します。

次のマトリックス ビジュアルは、レポート ユーザーが年から月にドリルダウンした場合の動作を示しています。 ビジュアルは、TargetQuantity 列を要約します。 (マトリックス行に対して[データ のない項目を表示する]オプションが有効になっています。

2020 年の目標数量を 270 として示すマトリックス ビジュアルを示す図。日付によって正しくない値が生成されます。

この動作を避けるには、指標を使用してファクトデータの集計を制御することをお勧めします。 集計を制御する 1 つの方法は、下位レベルの期間が照会されたときに BLANK を返す方法です。 高度な DAX で定義されるもう 1 つの方法は、低レベルの期間にわたって値を割り当てすることです。

ISFILTERED DAX 関数を使用する次のメジャー定義について考慮します。 Date 列と Month 列がフィルター処理されていない場合にのみ、値が返されます。

Target Quantity =
IF(
    NOT ISFILTERED('Date'[Date])
        && NOT ISFILTERED('Date'[Month]),
    SUM(Target[TargetQuantity])
)

次の行列ビジュアルでは、Target Quantity 指標を使用します。 すべての月次目標数量が空白であることを示します。

2 つのマトリックス ビジュアルを示す図。1 つ目は 2020 年の最初の月の目標を 270 として明らかにし、2 番目の月は空白です。

より高い粒度を関連付ける (日付以外)

ディメンション テーブルからファクト テーブルに日付以外の列を関連付ける場合は、別の設計方法が必要です (ディメンション テーブルよりも高い粒度です)。

(Product テーブルと Target テーブルの両方の) Category 列に重複する値が含まれています。 そのため、一対多のリレーションシップに対して「その一方」の側はありません。 この場合は、多対多リレーションシップを作成する必要があります。 リレーションシップは、ディメンション テーブルからファクト テーブルに対して、フィルターを 1 方向に伝達する必要があります。

ターゲット テーブルと Product テーブルのモデルを示す図。多対多リレーションシップは、2 つのテーブルを関連付けます。

次は、テーブル行を見てみましょう。

ターゲットと製品の 2 つのテーブルを含むモデルを示す図。多対多リレーションシップは、2 つの Category 列を関連付けます。

Target テーブルには、4 つの行があります。目標年ごとに 2 行 (2019 年と 2020 年)、2 つのカテゴリ (衣料品とアクセサリ) です。 Product テーブルには、3 つの製品があります。 2 つは衣料品カテゴリに属し、1 つはアクセサリ カテゴリに属します。 服の色の 1 つは緑色で、残りの 2 つは青です。

Product テーブルの Category 列でグループ化されたテーブルビジュアルでは、次の結果が生成されます。 ただし、このビジュアルでは正しい結果が生成されます。 次に、Product テーブルの Color 列を使用してターゲット数量をグループ化するとどうなるかを考えてみましょう。

2 つのテーブル ビジュアルを示す図。最初のグループは Category、2 番目のグループは色でグループ化されます。2 番目のビジュアルでは、正しくない結果が生成されます。

ビジュアルによって、データが誤って表示されます。 ここで何が起こっていますか?

Product テーブルの Color 列に対するフィルターは、2 つの行になります。 行の 1 つは [服] カテゴリ用で、もう 1 つは [アクセサリ] カテゴリ用です。 これら 2 つのカテゴリ値は、フィルターとして Target テーブルに反映されます。 つまり、青色は 2 つのカテゴリの製品で使用されるため、ターゲットのフィルター処理には "それらのカテゴリ" が使用されます。

前述のように、この現象を回避するには、指標を使用してファクトデータの集約を制御することをお勧めします。

次の測定定義について考えてみましょう。 カテゴリ レベルの下にあるすべてのテーブル列 Product フィルターがテストされていることに注意してください。

Target Quantity =
IF(
    NOT ISFILTERED('Product'[ProductID])
        && NOT ISFILTERED('Product'[Product])
        && NOT ISFILTERED('Product'[Color]),
    SUM(Target[TargetQuantity])
)

次の表の視覚エフェクトでは、Target Quantity メジャーを使用します。 すべての色ターゲット数量が空白であることを示します。

2 つのテーブル ビジュアルを示す図。最初のグループは Category、2 番目のグループは色でグループ化されます。2 番目のビジュアルでは、空白の正しい結果が生成されます。

最終的なモデル設計は次のようになります。

一対多リレーションシップに関連する日付テーブルとターゲット テーブルを持つモデルを示す図。

より高い粒度のファクトの関連付けに関するガイダンス

ディメンション テーブルをファクト テーブルに関連付ける必要があり、ファクト テーブルがディメンション テーブルの行よりも高い粒度で行を格納する場合は、次のガイダンスに従います。

  • より粒度の高いファクト日付の場合
    • ファクト テーブルに、期間の最初の日付を格納します。
    • 日付テーブルとファクト テーブルの間に一対多リレーションシップを作成します。
  • その他のより粒度の高いファクトの場合
    • ディメンション テーブルとファクト テーブルの間に多対多リレーションシップを作成します。
  • 両方の種類の場合
    • メジャー ロジックを使用して集計を制御します。下位レベルのディメンション列を使用してフィルターまたはグループ化する場合は、BLANK が返されます。
    • 集計可能なファクト テーブル列を非表示にします。これにより、メジャーのみを使用してファクト テーブルを集計できます。

この記事に関連する詳細については、次のリソースを参照してください。