2016-03-30 12 views
0

存在する可能性のあるすべての異なる商品ペアのうち、どれだけの商品を購入したかを判断しようとしています。例えば。私はA、B、Cの3つの製品を持っています。どちらの製品も国ごとに所有している顧客数のうち、AとB、BとC、AとCを購入した顧客の割合は何パーセントですか?クロス商品購入の割合

私のテーブルは以下のようになります。

Customer | Country | Product 
1  | US | A 
1  | US | B 
2  | CA | A 
2  | CA | C 
3  | US | A 
3  | US | C 
4  | US | B 
5  | US | A 

お客様は、1つの国にのみ所属することができます。

私の所望の出力は次のようになります。

Country | Pair | % 
US  | A_B | 25%  
US  | B_C | 0% 
US  | A_C | 33% 
CA  | A_B | 0%  
CA  | B_C | 0% 
CA  | A_C | 100% 

%は、本質的に比

国別
(# of unique customers who bought Product1 and Product2)/ 
(# of unique customers who bought Product1 or Product2) 

です。

だから、たとえば、米国でA_Bのために、私たちはAまたはBを買っただけで、これらの1がそう比は1/4あるABの両方を買って4人の顧客を持っています。

私は大きな数、任意の数のペアを持っていた場合、スケールするこの素晴らしいソリューションがありますか?

+0

US A_Cの値は25%ではなく33%にする必要がありますか? 3人の顧客(1、3、5)がAまたはCのいずれかを購入し、そのうちの1つ(3)が両方を購入しました。 – APH

+0

@APHあなたは正しいです。それは33%でなければなりません – Black

答えて

2

反復クエリの開発...

あなたがproductテーブルを持っている、とだけcustomer_country_productテーブルを持っていない場合、あなたはそれぞれの国のための製品の明確なリストを作成するために、インライン・ビューを使用することができます。

SELECT ccp.product_id 
     , ccp.country_id 
    FROM customer_country_product ccp 
    GROUP 
     BY ccp.product_id 
     , ccp.country_id 

我々はそれインライン・ビューすることにより、値集合ソースとしてそのクエリを使用することができます...国によって製品を得るために。そのクエリを括弧で囲み、エイリアスを割り当て、別のクエリのFROM句で参照します。製品のペアを取得するには、同じ製品(A_A)のペアを返さずに、「重複」ペアを返さないようにする(A_CC_Aのいずれかを返す)ことなくインラインビューを結合することができます。

SELECT a.country_id 
     , a.product_id AS a_product_id 
     , b.product_id AS b_product_id 
    FROM (SELECT ccpa.product_id 
       , ccpa.country_id 
      FROM customer_country_product ccpa 
      GROUP 
       BY ccpa.product_id 
       , ccpa.country_id 
     ) a 
    JOIN (SELECT ccpb.product_id 
       , ccpb.country_id 
      FROM customer_country_product ccpb 
      GROUP 
       BY ccpb.product_id 
       , ccpb.country_id 
     ) b 
     ON b.country_id = a.country_id 
    AND b.product_id > a.product_id 
    ORDER 
     BY a.country_id 
     , a.product_id 
     , b.product_id 

各国のすべての商品の「ペア」を取得する必要があります。注:これは、製品を持つ顧客がいない製品を省略します。我々はすべての可能な製品のペアをしたい場合は、それぞれの国のために、我々はあなたがproductcountryテーブルを持っている場合は、上記のクエリでインライン・ビューを置き換えることができ少し違った...

SELECT c.country_id 
     , a.product_id AS a_product_id 
     , b.product_id AS b_product_id 
    FROM (SELECT ccpa.product_id 
      FROM customer_country_product ccpa 
      GROUP BY ccpa.product_id 
     ) a 
    JOIN (SELECT ccpb.product_id 
      FROM customer_country_product ccpb 
      GROUP BY ccpb.product_id 
     ) b 
     ON b.product_id > a.product_id 
    CROSS 
    JOIN (SELECT ccpc.country_id 
      FROM customer_country_product ccpc 
      GROUP BY ccpc.country_id 
     ) c 
    ORDER 
     BY c.country_id 
     , a.product_id 
     , b.product_id 

ことを書く必要があると思いますこれらのテーブルへの参照が含まれています。

顧客の「カウント」を取得するには、SELECTリストに相関サブクエリを使用するか、SELECTリストで結合操作と集計を実行できます。 (私たちが注意しないなら合流して、生成し、「重複」をカウントする可能性があります。)

は、特定の製品を持っている特定の国に明確な顧客の数を取得するには

SELECT COUNT(DISTINCT ccp.customer_id) AS cnt_cust 
    FROM customer_country_product ccp 
WHERE ccp.country_id = ? 
    AND ccp.product_id = ? 

は、2つの特定の製品を持っている特定の国に顧客の数を取得するには2つの特定の製品

SELECT COUNT(DISTINCT ccp.customer_id) AS cnt_cust_have_either 
    FROM customer_country_product ccp 
WHERE ccp.country_id = ? 
    AND ccp.product_id IN (? , ?) 

の少なくとも一つを有する特定の国からの明確な顧客の数を取得するには:

SELECT COUNT(DISTINCT ccp1.customer_id) AS cnt_cust_have_both 
    FROM customer_country_product ccp1 
    JOIN customer_country_product ccp2 
    ON ccp2.country_id = ccp1.country_id 
    AND ccp2.customer_id = ccp1.customer_id 
WHERE ccp1.country_id = ? 
    AND ccp1.product_id = ? 
    AND ccp2.product_id = ? 

これらのクエリは、単一の列を含む単一の行を返すため、これらを別のクエリのSELECTリストの式として使用できます。 「プロダクトペア」クエリから始め、SELECTリストに追加します。私たちは除算演算を行うために必要な「割合」を算出するために、今すぐ

SELECT c.country_id 
     , a.product_id AS a_product_id 
     , b.product_id AS b_product_id 
     , (SELECT COUNT(DISTINCT ccp1.customer_id) 
      FROM customer_country_product ccp1 
      JOIN customer_country_product ccp2 
       ON ccp2.country_id = ccp1.country_id 
       AND ccp2.customer_id = ccp1.customer_id 
      WHERE ccp1.country_id = c.country_id 
       AND ccp1.product_id = a.product_id 
       AND ccp2.product_id = b.product_id 
     ) AS cnt_cust_have_both 
     , (SELECT COUNT(DISTINCT ccp.customer_id) 
      FROM customer_country_product ccp 
      WHERE ccp.country_id = c.country_id 
       AND ccp.product_id IN (a.product_id,b.product_id) 
     ) AS cnt_cust_have_either 
    FROM (SELECT ccpa.product_id 
      FROM customer_country_product ccpa 
      GROUP BY ccpa.product_id 
     ) a 
    JOIN (SELECT ccpb.product_id 
      FROM customer_country_product ccpb 
      GROUP BY ccpb.product_id 
     ) b 
     ON b.product_id > a.product_id 
    CROSS 
    JOIN (SELECT ccpc.country_id 
      FROM customer_country_product ccpc 
      GROUP BY ccpc.country_id 
     ) c 
    ORDER 
     BY c.country_id 
     , a.product_id 
     , b.product_id 

:私たちは、外側のクエリからの列を参照して、それらの疑問符プレースホルダを交換してください。 MySQLでは、「ゼロによる除算」はNULLを返します。私たちの外側のクエリが行のみを返した場合、我々は国からの顧客を知っている(私たちは、それと気にする必要はありません持っている製品の1 ...最初のクエリ

SELECT c.country_id 
     , a.product_id AS a_product_id 
     , b.product_id AS b_product_id 
     , (SELECT COUNT(DISTINCT ccp1.customer_id) 
      FROM customer_country_product ccp1 
      JOIN customer_country_product ccp2 
       ON ccp2.country_id = ccp1.country_id 
       AND ccp2.customer_id = ccp1.customer_id 
      WHERE ccp1.country_id = c.country_id 
       AND ccp1.product_id = a.product_id 
       AND ccp2.product_id = b.product_id 
     ) 
    /(SELECT COUNT(DISTINCT ccp.customer_id) 
      FROM customer_country_product ccp 
      WHERE ccp.country_id = c.country_id 
       AND ccp.product_id IN (a.product_id,b.product_id) 
     ) 
     * 100.00 AS percent_cust_have_both 
    FROM (SELECT ccpa.product_id 
      FROM customer_country_product ccpa 
      GROUP BY ccpa.product_id 
     ) a 
    JOIN (SELECT ccpb.product_id 
      FROM customer_country_product ccpb 
      GROUP BY ccpb.product_id 
     ) b 
     ON b.product_id > a.product_id 
    CROSS 
    JOIN (SELECT ccpc.country_id 
      FROM customer_country_product ccpc 
      GROUP BY ccpc.country_id 
     ) c 
    ORDER 
     BY c.country_id 
     , a.product_id 
     , b.product_id 

によって返される結果すなわち私たちは適切なインデックスを用意しておく必要があります。特に相関サブクエリの場合は、になります。の行がすべて外側のクエリによって返されます。

最後のクエリは、分母にゼロのカウントがあるときにNULLを返す可能性があります。condで指定した除算をラップすることでゼロを代入することができますitionalテスト

IFNULL(<expr> , 0) * 100.00 AS 

(多分エラーがどこかにこれらのクエリであります、行方不明括弧、無効な参照、間違った修飾子など、これらのクエリがテストされていません。私は強くあなたが各1テストし、ちょうどその最後の1をつかんいないお勧めします。)


フォロー

テスト用のテーブル...

CREATE TABLE customer_country_product 
(customer_id INT 
, country_id VARCHAR(2) 
, product_id VARCHAR(2) 
) 
; 
INSERT INTO customer_country_product (customer_id, country_id, product_id) VALUES 
('1','US','A') 
,('1','US','B') 
,('2','CA','A') 
,('2','CA','C') 
,('3','US','A') 
,('3','US','C') 
,('4','US','B') 
,('5','US','A') 
; 

最終クエリが返す:

country_id a_product_id b_product_id percent_cust_have_both 
---------- ------------ ------------ ---------------------- 
CA   A    B    0.000000 
CA   A    C    100.000000 
CA   B    C    0.000000 
US   A    B    25.000000 
US   A    C    33.333333 
US   B    C    0.000000 

これは些細な変化は、単一の列にa.product_idb.product_idを連結することになります。 SELECTリストの2番目と3番目の列は、CONCAT(a.product_id,'_',b.product_id) AS a_bのようなものに置き換えることができます。

+0

SQL Serverではなく、この回答を書いたときに私はMySQLを念頭に置いていました。私の悪い。構文の中には、MySQL固有のものがあります。 – spencer7593

+0

ありがとうございます。これはまさに私が必要としたものであり、本当に明白です。 – Black

+1

@Black:それはかなり長いクエリであることが判明しました。私は、そのクエリを構築し、その途中でテストする段階的で段階的なアプローチを実証しようとしました。 SQL Serverの場合、「除算ゼロ」の場合、その除算操作でエラーがスローされることがあります(SQL Serverの場合は、最後のクエリを実行する方法はありません)操作。私はゼロに評価するときにNULLを返す関数で分母式をラップすることをお勧めします。 – spencer7593

1

国と一緒にすべての製品のペアを生成する必要があります。次に、購入した一致する顧客数と両方を購入した顧客数を計算する必要があります。

商品テーブルと国別テーブルがあるとします。次に、サブクエリが最も簡単な解決策になると思います。

select p1.product as product1, p2.product as p2, 
     (select count(*) 
     from (select cp.customer 
       from customerproducts cp 
       where cp.product in (p1.product, p2.product) and 
        cp.country = c.country 
       group by cp.customer 
       having count(distinct product) = 2 
      ) cp 
     ) as numWithBoth, 
     (select count(*) 
     from (select cp.customer 
       from customerproducts cp 
       where cp.product in (p1.product, p2.product) and 
        cp.country = c.country 
       group by cp.customer 
      ) cp 
     ) as numWithEither 
from countries c cross join 
    products p1 cross join 
    products p2 ; 

最終的な答えは、2つの値の比です。

+0

これはかなり良いですね。しかし、 'cp'が' GROUP BY'節を必要とすることをインラインで見ていませんか? 'HAVING'節の集約(' COUNT')はそれを1行に崩壊させず、1人の顧客だけを返しますか? (または、sql_modeに 'ONLY_FULL_GROUP_BY'が含まれているとエラーが発生しますか?)MySQLは相関サブクエリが外部クエリの列を参照できる深さを制限していますか? (おそらくそれは古いバージョンのものか、私が考えている別のデータベースにあるのでしょうか?)これは重複したペアを返しません。 'A_C'と' C_A'だけでなく 'A_A'も使えますか? – spencer7593

+0

残念ながら私はこの1つのテーブルしか持っていません。 – Black

0

CTEを使用し、投稿したテーブルしかないと仮定すると、代わりに国/製品の組み合わせリストを抽出するための作業が追加されます。おそらくこれをより少ないステップで実行することは可能ですが、解決策を説明して、何が起きているのかをより簡単に確認することができました。

drop table #test 

create table #test (customer int, country varchar(2), product char(1)) 
insert into #test values (1, 'US','A') 
insert into #test values (1, 'US','B') 
insert into #test values (2, 'CA','A') 
insert into #test values (2, 'CA','C') 
insert into #test values (3, 'US','A') 
insert into #test values (3, 'US','C') 
insert into #test values (4, 'US','B') 
insert into #test values (5, 'US','A') 

; with CTE as (--Count the number of customers ordering each item 
    select country, product, count(distinct customer) as TotalOrders 
    from #test 
    group by country, product 
    ) 
, CTE2 as (--Join the order counts back to the original data set (can do this in CTE as a windowed function if you don't have customers ordering the same product more than once) 
    select a.*, b.TotalOrders from #test a 
    left join cte b 
    on a.country = b.country and a.product = b.product 
    ) 
, combinations as (--Generate all possible country/product combinations 
    Select * from 
     (Select distinct Country from #test) a 
    cross join 
     (Select distinct a.product + '_' + b.product as ProductCombination from #test a 
     left join #test b 
     on a.product < b.product) b 
    where b.ProductCombination is not null 
    ) 
, calculations as (--count purchasers of combinations, and use this combined with the earlier purchaser counts to generate an unduplicated total purchasers count 
    select a.country, a.product + '_' + b.product as ProductCombination 
     , cast(count(distinct a.customer)*100.0/(a.totalorders + b.totalorders - count(distinct a.customer)) as decimal(5,0)) as PctOfTotal 
    from cte2 a 
    inner join cte2 b 
    on a.country = b.country 
     and a.customer = b.customer 
     and a.product < b.product 
    group by a.country, a.product, b.product, a.totalorders + b.totalorders) 

select a.*, isnull(b.PctOfTotal, 0) as PercentOfTotal from combinations a 
left join calculations b 
on a.country = b.country 
    and a.ProductCombination = b.ProductCombination 
order by a.country, a.ProductCombination 
+0

ご協力いただきありがとうございます。私はこれを実行しようとしましたが、残念ながら、まだクエリが実行されているので、結果を見ることができません。それはかなり遅いようです。 – Black

関連する問題