簡體   English   中英

計算組內兩個表之間不匹配的行

[英]Count rows that don't match between two tables within a group

我有兩個表,每個表都有一個user_idgroup_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

我沒有相關數據可以測試,但這對您的特定數據是否有幫助

這是策略。

  1. 使用cross join生成行。
  2. 為此,使用count(distinct)獲取組。
  3. 使用派生表生成times_show_up
  4. 聚合table1table2
  5. 一起加入這一切。

這是查詢:

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM