[英]Count rows that don't match between two tables within a group
我有兩個表,每個表都有一個user_id
和group_name
列。
例如
table1:
| user_id | group_name1|
------------------------
| 1 | 'groupA' |
| 1 | 'groupB' |
| 2 | 'groupA' |
| 1 | 'groupA' |
------------------------
table2:
| user_id | group_name2|
------------------------
| 1 | 'groupL' |
| 2 | 'groupL' |
| 3 | 'groupL' |
| 4 | 'groupN' |
| 1 | 'groupN' |
| 3 | 'groupN' |
------------------------
我正在嘗試在 table2 中的用戶出現在 table1 中的次數之間創建計數分布,但在組內執行此操作。
對於上面的例子,我會得到
| times_show_up | number_of_users | group_name1 | group_name2 |
---------------------------------------------------------------
| 0 | 1 | groupA | groupL |
| 1 | 1 | groupA | groupL |
| 2 | 1 | groupA | groupL |
| 0 | 2 | groupB | groupL |
| 1 | 1 | groupB | groupL |
| 2 | 0 | groupB | groupL |
| 0 | 2 | groupA | groupN |
| 1 | 0 | groupA | groupN |
| 2 | 1 | groupA | groupN |
| 0 | 2 | groupB | groupN |
| 1 | 1 | groupB | groupN |
| 2 | 0 | groupB | groupN |
----------------------------------------------------------------
解釋一些行作為進一步的例子:
對於第 1 行,groupL 中 user_id = 3 的用戶在 groupA 中出現了 0 次。 對於第 2 行,groupL 中 user_id = 2 的用戶在 groupA 中出現一次。 對於第 3 行,groupL 中 user_id = 1 的用戶在 groupA 中出現了兩次。
雖然在這個例子中一個人最多出現 2 次,但在實際數據中,這個數字是一個我事先不知道的任意大的數字。
如果我正確填寫了所有內容,其他組也類似。
我想出了一個查詢,除了計算 0 之外,它可以完成所有這些操作,如下所示:
SELECT
COUNT(user_id) AS num_users,
times_show_up,
group_name1,
group_name2
FROM
(
SELECT
user_id,
COUNT(*) AS times_show_up,
group_name1,
group_name2
FROM
table1
RIGHT JOIN
(SELECT DISTINCT user_id, group_name2 FROM table2)
USING(user_id)
GROUP BY user_id, group_name1, group_name2
)
GROUP BY times_show_up, group_name1, group_name2
不幸的是,這不會返回times_show_up
列中的 0 計數,而且我還沒有想出一個無需大量子查詢即可完成此操作的解決方案。 一種可能的方法是運行子查詢以獲取所有組的所有組合的所有 0,然后將這些行UNION
到表的其余部分。 但是我想避免包含每個可能的 group1、group2 組合的子查詢的方法,因為組的數量非常大。
一些限制包括此數據集上的partition by
往往會耗盡內存,因此我想避免它。 更新的要求:此外,在個人用戶級別使用 CROSS JOIN(因此將 table1 直接交叉連接到 table2 而不先將行分組)不起作用,因為每個表都有數百萬行。
最后, number_of_users
列中帶有 0 的行不必顯示(如果顯示也沒關系,因為可以使用簡單的WHERE
刪除它們,但如果對查詢有幫助,則不是必需的)
更新:
我能夠提出一個可以生成零的查詢,同時只需要對每個 group_name1 進行單個查詢,而不是對每個 group_name1、group_name2 組合進行單個查詢。 我將它添加到問題中,以防它有助於提出更少查詢的答案,因為表 1 中的組數仍然可能超過 20+,這意味着通過UNION ALL
添加了 20+ 查詢。
SELECT * FROM
(SELECT
times_show_up,
COUNT(user_id) AS num_users,
group_name1,
group_name2
FROM
(
SELECT
user_id,
COUNT(*) AS times_show_up,
group_name1,
group_name2
FROM
table1
INNER JOIN
(SELECT DISTINCT user_id, group_name2 FROM table2) t2
USING(user_id)
GROUP BY user_id, group_name1, group_name2
) t1
GROUP BY times_show_up, group_name1, group_name2) t9
UNION ALL
(SELECT
0 AS times_show_up,
SUM(CASE WHEN t1.user_id IS NULL
THEN 1 ELSE 0 END) AS num_users,
'groupA' AS group_name1,
group_name2
FROM
table2
LEFT JOIN
(SELECT user_id FROM table1 WHERE group_name1 = 'groupA') t1
USING(user_id)
GROUP BY group_name2)
UNION ALL
(SELECT
0 AS times_show_up,
SUM(CASE WHEN t1.user_id IS NULL
THEN 1 ELSE 0 END) AS num_users,
'groupB' AS group_name1,
group_name2
FROM
table2
LEFT JOIN
(SELECT user_id FROM table1 WHERE group_name1 = 'groupB') t1
USING(user_id)
GROUP BY group_name2)
--- ORDER BY group_name1, group_name2, times_show_up
下面是 BigQuery 標准 SQL,結果相對簡單
#standardSQL
SELECT times_show_up,
COUNT(DISTINCT user_id) number_of_users,
group_name1, group_name2
FROM (
SELECT COUNTIF(a.user_id = b.user_id) times_show_up,
b.user_id,
group_name1, group_name2
FROM table1 a
CROSS JOIN table2 b
GROUP BY user_id, group_name1, group_name2
)
GROUP BY times_show_up, group_name1, group_name2
-- ORDER BY group_name2, group_name1, times_show_up
如果適用於您問題中的樣本數據 - 結果是
Row times_show_up number_of_users group_name1 group_name2
1 0 1 groupA groupL
2 1 1 groupA groupL
3 2 1 groupA groupL
4 0 2 groupB groupL
5 1 1 groupB groupL
6 0 2 groupA groupN
7 2 1 groupA groupN
8 0 2 groupB groupN
9 1 1 groupB groupN
... number_of_users 列中帶有 0 的行不必顯示
注意:我遵循此規則,因為您打算無論如何都要消除它們,以防萬一結果出現這種情況
更新...每個表都有數百萬行。
試試下面的“優化”版本
#standardSQL
SELECT times_show_up,
COUNT(DISTINCT user_id) number_of_users,
group_name1, group_name2
FROM (
SELECT SUM(IF(a.user_id = b.user_id, cnt, 0)) times_show_up,
b.user_id,
group_name1, group_name2
FROM (SELECT user_id, group_name1, COUNT(1) cnt FROM table1 GROUP BY user_id, group_name1) a
CROSS JOIN (SELECT DISTINCT user_id, group_name2 FROM table2) b
GROUP BY user_id, group_name1, group_name2
)
GROUP BY times_show_up, group_name1, group_name2
我沒有相關數據可以測試,但這對您的特定數據是否有幫助
這是策略。
cross join
生成行。count(distinct)
獲取組。times_show_up
。table1
和table2
這是查詢:
select g1.group_name1, g2.group_name2, tsu.times_show_up,
coalesce(t12.cnt, 0) as num_users
from (select distinct group_name1 from table1) g1 cross join
(select distinct group_name2 from table2) t2 cross join
(select 0 as times_show_up union all
select 1 union all
select 2
) tsu left join
(select t1.group_name1, t2.group_name2, count(*) as cnt
from table1 t1 join
table2 t2
on t2.user_id = t1.user_id
group by t1.group_name1, t2.group_name2
) t12
on t12.group_name1 = g1.group_name1 and
t12.group_name2 = g2.group_name2 and
t12.cnt = tsu.times_show_up;
如果您的數據確實有重復項,您可能需要在子查詢中使用count(distinct user_id)
而不是count(*)
。
@Mikhail Berlyant 的回答符合我問題的原始要求。 不幸的是,因為它依賴於 user_id 級別的交叉聯接,並且有數百萬個用戶 ID,所以對於我的特定用例需要很長時間才能完成。 所以我提供了以下答案,它更快,但確實需要對表 1 中的每個組進行額外查詢(但不是針對 group1 和 group2 的每個組合),從而使查詢不太簡潔,可能會超出限制如果組數非常非常大,則 BigQuery 查詢大小的大小。
如果您可以以編程方式為每個組生成查詢,並且擁有數百萬用戶的組較少,則這種方法是首選,而@Mikhail Berlyant 的答案應該適用於每個組有少量用戶的更多組的情況,並且在查詢生成不是以編程方式完成的情況下,您必須為每個組編寫一個。
SELECT * FROM
(SELECT
times_show_up,
COUNT(user_id) AS num_users,
group_name1,
group_name2
FROM
(
SELECT
user_id,
COUNT(*) AS times_show_up,
group_name1,
group_name2
FROM
table1
INNER JOIN
(SELECT DISTINCT user_id, group_name2 FROM table2) t2
USING(user_id)
GROUP BY user_id, group_name1, group_name2
) t1
GROUP BY times_show_up, group_name1, group_name2) t9
# Each subsequent query being UNIONed corresponds to a group in table 1
UNION ALL
(SELECT
0 AS times_show_up,
SUM(CASE WHEN t1.user_id IS NULL
THEN 1 ELSE 0 END) AS num_users,
'groupA' AS group_name1,
group_name2
FROM
table2
LEFT JOIN
(SELECT user_id FROM table1 WHERE group_name1 = 'groupA') t1
USING(user_id)
GROUP BY group_name2)
UNION ALL
(SELECT
0 AS times_show_up,
SUM(CASE WHEN t1.user_id IS NULL
THEN 1 ELSE 0 END) AS num_users,
'groupB' AS group_name1,
group_name2
FROM
table2
LEFT JOIN
(SELECT user_id FROM table1 WHERE group_name1 = 'groupB') t1
USING(user_id)
GROUP BY group_name2)
--- ORDER BY group_name1, group_name2, times_show_up```
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.