OmniではOne to Manyの結合時のfan outをどのように解消しているか確認してみた

OmniではOne to Manyの結合時のfan outをどのように解消しているか確認してみた

2025.11.14

さがらです。

BIツールのよくある悩みの1つとして、「One to ManyのリレーションシップでJOINした場合にレコードが重複してしまうfan out現象が起きてしまう」という問題があると思います。

この問題をOmniはどのように解消しているか確認してみたので、内容をまとめてみます。

参考記事

以下のOmniのコミュニティノートに記載があるため、こちらを参考に試していきます。

https://community.omni.co/t/how-do-i-prevent-my-aggregations-from-counting-duplicates-when-my-query-fans-out/172

試してみた

One to ManyのJOIN設定

まず検証のためにOne to ManyのJOIN設定が必要なのですが、下図のようにJOINを設定しました。

2025-11-14_14h27_50

fan outが発生するクエリをOmniから実行してみる

One側のメジャーとMany側のメジャーを同時に選択してみると、下図のようにエラーが表示されます。

2025-11-14_14h30_29

このエラーについてはOmniのコミュニティノートでも言及があり、対策としてはプライマリキーを各viewに対して設定する必要があります。

https://community.omni.co/t/how-do-i-prevent-my-aggregations-from-counting-duplicates-when-my-query-fans-out/172

プライマリキーの設定

Omniではプライマリキーの設定をGUIベースで行うことが出来ます。

今回はJOINしている以下のviewについて、プライマリキーを設定しました。

  • CustomersviewのCustomer ID

2025-11-14_14h31_20

  • OrdersviewのOrder ID

2025-11-14_14h32_49

fan outが発生するクエリをOmniから実行してみる(プライマリキー設定後)

改めて先程エラーが発生した、One側のメジャーとMany側のメジャーを同時に選択してみると、今度はエラーがなく解消されました!

2025-11-14_14h33_46

どのようなSQLが作成されているかを確認してみると、OMNI_SUMという関数で集計されていることがわかります。

2025-11-14_11h38_16

2025-11-14_14h35_10

実際にDWHに発行されているSQLはInspectorから確認ができます。Inspectorから見ると、下図のようなSQLが実行されていました。

2025-11-14_11h55_29

2025-11-14_14h36_19

実際にDWHに発行されていたクエリを書き出したものが下記となります。生成AIに解説してもらった内容も添付しておきます。

このクエリは、customersordersのOne to Many関係でJOINした際に発生するfan out問題を、UNION ALLとフラグを使った手法で解決しています。通常、顧客テーブルと注文テーブルをJOINすると、1人の顧客に対して複数の注文レコードが結合されるため、customers側のlifetime_spendのようなメジャーが注文件数分だけ重複してカウントされてしまいます。
この問題を避けるため、クエリはUNION ALLで2つの異なる集計パターンを作成しています。1つ目のパターン($f4=0)では、CUSTOMER_IDGROUP BY句に含めることで、顧客単位での正確な集計を実現しています。2つ目のパターン($f4=1)では、CUSTOMER_IDを除外してGROUP BYを行い、orders側の集計を行います。
最終的に外側のクエリで、このフラグ値を使ってCASE文で振り分けを行います。$f4=1の場合はorder_total_sumMINで取得し、$f4=0の場合はlifetime_spend_sumSUMで集計します。これにより、それぞれのメジャーが適切な粒度で計算された値から取得されるため、重複計算を防ぎながら異なる粒度のメジャーを同時に表示できます。

WITH "$with_t_0" AS (SELECT *
        FROM "MART"."CUSTOMERS" AS "omni_dbt_mart__customers"
            LEFT JOIN (SELECT "CUSTOMER_ID" AS "CUSTOMER_ID0",
                    "ORDER_TOTAL"
                FROM "MART"."ORDERS" AS "omni_dbt_mart__orders") AS "t" ON "omni_dbt_mart__customers"."CUSTOMER_ID" = "t"."CUSTOMER_ID0")

SELECT "omni_dbt_mart__customers.customer_name",
        MIN(CASE
            WHEN "$f4" = 1 THEN "$f2"
            ELSE NULL
        END) AS "omni_dbt_mart__orders.order_total_sum",
        COALESCE(SUM(CASE
            WHEN "$f4" = 0 THEN "$f3"
            ELSE NULL
        END), 0) AS "omni_dbt_mart__customers.lifetime_spend_sum"
    FROM (SELECT "CUSTOMER_NAME" AS "omni_dbt_mart__customers.customer_name",
                "CUSTOMER_ID" AS "omni_dbt_mart__customers__pk__0",
                COALESCE(SUM("ORDER_TOTAL"), 0) AS "$f2",
                MIN("LIFETIME_SPEND") AS "$f3",
                0 AS "$f4"
            FROM "$with_t_0"
            GROUP BY 1, 2
            UNION ALL
            SELECT "CUSTOMER_NAME" AS "omni_dbt_mart__customers.customer_name",
                NULL AS "omni_dbt_mart__customers__pk__0",
                COALESCE(SUM("ORDER_TOTAL"), 0) AS "$f2",
                MIN("LIFETIME_SPEND") AS "$f3",
                1 AS "$f4"
            FROM "$with_t_0"
            GROUP BY 1) AS "t5"
    GROUP BY "omni_dbt_mart__customers.customer_name"
    ORDER BY 3 DESC NULLS LAST
    LIMIT 1000

最後に

OmniではOne to Manyの結合時のfan outをどのように解消しているか確認してみました。

基本的には各viewに対してプライマリキーを設定するだけで、One to Manyの結合もOmniは問題なく対処できます!

この記事をシェアする

FacebookHatena blogX

関連記事