web-dev-qa-db-ja.com

redshift:ウィンドウパーティションで個別の顧客をカウントする

Redshiftは、ウィンドウ関数でDISTINCT集計をサポートしていません。 COUNTのAWSドキュメントでは、これを示しています 、およびdistinctはどのウィンドウ関数でもサポートされていません。

私の使用例:さまざまな時間間隔とトラフィックチャネルで顧客をカウントする

私は毎月およびYTDユニークの顧客数を今年度分だけ希望し、トラフィックチャネルおよびすべてのチャネルの合計で分割します。顧客は複数回訪問できるため、個別の顧客のみをカウントする必要があるため、Redshiftウィンドウ集計は役に立ちません。

  • count(distinct customer_id)...group byを使用して個別の顧客をカウントできますが、これにより、必要な4つの結果のうち1つしか得られません。
  • 私はしないでくださいunion allの束の間に積み上げられた希望の各カウントに対して完全なクエリを実行する習慣を身につけたいと思います。これが唯一の解決策ではないことを願っています。

これは私がpostgres(またはOracleに関して言えば)で書くものです:

select order_month
       , traffic_channel
       , count(distinct customer_id) over(partition by order_month, traffic_channel) as customers_by_channel_and_month
       , count(distinct customer_id) over(partition by traffic_channel) as ytd_customers_by_channel
       , count(distinct customer_id) over(partition by order_month) as monthly_customers_all_channels
       , count(distinct customer_id) over() as ytd_total_customers

from orders_traffic_channels
/* otc is a table of dated transactions of customers, channels, and month of order */

where to_char(order_month, 'YYYY') = '2017'

これをRedshiftで解決するにはどうすればよいですか?

結果はredshiftクラスターで機能する必要があります。さらに、これは単純化された問題であり、実際の望ましい結果には、必要なパーティションの数を乗算する製品カテゴリーと顧客タイプがあります。したがって、union allロールアップのスタックは適切なソリューションではありません。

6
Merlin

A 2016年のブログ投稿 はこの問題を指摘し、初歩的な回避策を提供するので、Mark D. Adamsに感謝します。不思議なことに私がすべてのWebで見つけることができるものはほとんどないので、(テストされた)ソリューションを共有しています。

重要な洞察は、問題のアイテムによって順序付けされたdense_rank()は、同一のアイテムに同じランクを提供するため、最高ランクは一意のアイテムの数でもあります。私が望むパーティションごとに次のものを交換しようとすると、これは恐ろしい混乱です:

_dense_rank() over(partition by order_month, traffic_channel order by customer_id)
_

最も高いランクが必要なため、すべてをサブクエリして、取得した各ランキングから最大値を選択する必要があります。 外部クエリのパーティションをサブクエリの対応するパーティションに一致させることが重要です

_/* multigrain windowed distinct count, additional grains are one dense_rank and one max over() */
select distinct
       order_month
       , traffic_channel
       , max(tc_mth_rnk) over(partition by order_month, traffic_channel) customers_by_channel_and_month
       , max(tc_rnk) over(partition by traffic_channel)  ytd_customers_by_channel
       , max(mth_rnk) over(partition by order_month)  monthly_customers_all_channels
       , max(cust_rnk) over()  ytd_total_customers

from (
       select order_month
              , traffic_channel
              , dense_rank() over(partition by order_month, traffic_channel order by customer_id)  tc_mth_rnk
              , dense_rank() over(partition by traffic_channel order by customer_id)  tc_rnk
              , dense_rank() over(partition by order_month order by customer_id)  mth_rnk
              , dense_rank() over(order by customer_id)  cust_rnk

       from orders_traffic_channels

       where to_char(order_month, 'YYYY') = '2017'
     )

order by order_month, traffic_channel
;
_

ノート

  • max()およびdense_rank()のパーティションは一致する必要があります
  • dense_rank()はnull値をランク付けします(すべて同じランク、最大値)。 null値をカウントしない場合は、case when customer_id is not null then dense_rank() ...etc...が必要です。または、nullがあることがわかっている場合は、max()から1を減算できます。
10
Merlin