J'ai deux tables, chacune avec un user_id
et des colonnes group_name
.
Par exemple
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
J'essaie de créer une répartition des décomptes entre le nombre de fois qu'un utilisateur de la table2 apparaît dans la table1, mais faites cela au sein d'un groupe.
Pour l'exemple ci-dessus, j'obtiendrais
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
Pour expliquer certaines lignes comme exemple supplémentaire:
Pour la ligne 1, l'utilisateur avec user_id = 3 dans le groupeL apparaît 0 fois dans le groupeA. Pour la ligne 2, l'utilisateur avec user_id = 2 dans groupL apparaît une fois dans groupA. Et pour la ligne 3, l'utilisateur avec user_id = 1 dans groupL apparaît deux fois dans groupA.
Bien que dans cet exemple, une personne apparaisse jusqu'à un maximum de 2 fois, dans les données réelles, ce nombre est arbitrairement grand nombre que je ne connais pas à l'avance.
De même pour les autres groupes si j'ai rempli tout cela correctement.
J'ai trouvé une requête qui peut faites tout cela sauf en comptant les 0, qui ressemble à ceci:
| 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 | ----------------------------------------------------------------
Malheureusement, cela ne renverra pas les nombres de 0 dans la colonne times_show_up
, et je n'ai pas trouvé de solution qui puisse accomplir cela sans beaucoup de sous-requêtes. Une méthode possible consiste simplement à exécuter des sous-requêtes pour récupérer tous les 0 pour toutes les combinaisons de tous les groupes, puis simplement UNION
ces lignes au reste de la table. Mais je veux éviter les approches qui incluent des sous-requêtes pour chaque combinaison group1, group2 possible car le nombre de groupes est très grand.
Certaines limitations incluent que la partition par
sur cet ensemble de données a tendance à manquer de mémoire, je veux donc l'éviter. Exigence mise à jour: De plus, l'utilisation de CROSS JOIN au niveau de l'utilisateur individuel (donc la jonction croisée de la table1 directement à la table2 sans regrouper les lignes au préalable) ne fonctionne pas car chaque table comporte des dizaines de millions de lignes.
Enfin, les lignes avec des 0 dans la colonne number_of_users
ne doivent pas apparaître (c'est bien si elles le font car elles peuvent être supprimées avec un simple WHERE code >, mais ne sont pas nécessaires, si cela aide avec la requête)
MISE À JOUR:
J'ai pu proposer une requête qui peut générer les zéros tout en ne nécessitant qu'une seule requête pour chaque nom_groupe1, plutôt qu'une seule requête pour chaque combinaison nom_groupe1, nom_groupe2. Je l'ajoute à la question au cas où cela aiderait à trouver des réponses avec encore moins de requêtes, car il est toujours vrai que le nombre de groupes dans le tableau 1 pourrait être supérieur à 20+, ce qui signifie plus de 20 requêtes ajoutées via UNION ALL
.
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' | ------------------------
3 Réponses :
Voici la stratégie.
jointure croisée
pour générer les lignes. count (distinct)
. times_show_up
à l'aide d'une table dérivée. table1
et table2
Voici la requête:
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;
Vous voudrez peut-être count (distinct user_id)
au lieu de count (* )
dans la sous-requête, si vos données ont vraiment des doublons.
Je ne pense pas que cela fonctionnera car il semble coder en dur le 0, 1, 2, alors que cela pourrait être n'importe quoi dans les données réelles. Une personne peut apparaître 1000 fois ou un autre numéro.
Ci-dessous est pour BigQuery Standard SQL et a fini par être relativement simple
#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
S'il faut appliquer aux exemples de données de votre question - le résultat est
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
... les lignes avec 0 dans la colonne number_of_users ne doivent pas apparaître
Remarque: je suis cette règle car il semble que vous prévoyez de les éliminer de toute façon au cas où le résultat en aurait
Mise à jour pour ... chaque table contient des dizaines de millions de lignes.
Essayez ci-dessous la version "optimisée"
#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
Je n'ai pas de données pertinentes pour tester si cela aidera ou non pour vos données spécifiques p>
C'est intéressant. J'avais peur d'utiliser la jointure croisée sur les données utilisateur brutes car il y a des dizaines de millions par table. Je lui ai quand même essayé et jusqu'à présent, la requête ne s'est pas plantée, mais elle fonctionne depuis plus de 15 minutes maintenant et je soupçonne sournoisement qu'elle épuisera les ressources de calcul avant de se terminer. Si cela fonctionne et qu'il n'y a pas de moyen simple de créer une requête plus rapide (par exemple, si toutes les autres versions nécessitent une sous-requête pour chaque combinaison group1, group2), j'accepterai cette réponse car c'est une solution soignée. Mais les requêtes plus bâclées s'exécutent au moins en ~ 1 minute.
voyons si cela fera une fin sans erreur: o) Je vais revoir cette requête dans la matinée pour voir si cela peut être optimisé. mais honnêtement je pense qu'il devrait réussir tel quel même pour 10M +
Je n'ai toujours pas planté mais après 83 minutes je l'ai arrêté: D C'était sur un ensemble de données plus petit, donc je ne peux qu'imaginer ce qui se passera sur un ensemble de données plus grand. J'ajouterai le 'pas de jointures croisées au niveau de l'utilisateur individuel' aux exigences de la question :)
Le délai d'expiration de la requête par défaut est de 6 heures. mais il est peut-être trop long d'attendre votre cas o (je viens de voir votre mise à jour - oui, je pense que c'est la bonne direction pour l'optimisation - pré-agrégez d'abord les deux tables.
Bien, même si cela se termine, 83 minutes pour une requête plus petite ne vaut pas la peine car je peux l'obtenir en ~ 1-3 minutes en utilisant simplement des sous-requêtes pour chacun des zéros group1, group2. Par exemple, ma requête initiale qui calcule correctement tout sauf que les zéros se termine en ~ 1 minute
J'ai exécuté votre requête initiale sur des exemples de données et je n'ai pas obtenu le résultat attendu. mais je peux me tromper car je n'ai pas passé beaucoup de temps. de toute façon - je vérifierai celui-ci à nouveau dans la matinée. il est minuit en ce moment à Los Angeles et j'ai encore du travail à faire: o)
Voici un violon: sqlfiddle.com/#!9/78bf1f/4 Ignorer le lignes avec null dans group_name1, je peux facilement les faire disparaître en remplaçant Right join par Inner join (ou une instruction where). Si vous le faites, vous verrez que cette requête génère toutes les lignes où num_users> 0 (comme vous le souhaitez), sauf celles où times_show_up = 0 (le problème que j'essaie de résoudre)
@CHP - ajout de la version "optimisée": o)
Cela prend encore très longtemps, jusqu'à présent pendant 30 minutes et cela ne s'est pas terminé, et je soupçonne que cela ne fonctionnera pas bien avec des ensembles de données plus volumineux non plus (ne se nourrissant que de 3 groupes pour le test, au lieu de 20) :) La requête mise à jour que j'ai postée par contraste se termine en 20 secondes pour 3 groupes. Peut-être étais-je trop naïf pour penser que cela pouvait être fait rapidement sans questions supplémentaires. Il semble juste étrange que seuls les 0 posent des problèmes, j'ai l'impression qu'il doit y avoir une logique qui me manque pour les inclure avec une seule requête qui ne croise pas au niveau user_id.
J'ai fini par l'annuler car cela a pris plus d'une heure, mais j'accepterai la réponse car elle répond à la question d'origine, et rien de plus efficace n'est venu. Je publierai ma requête en tant que réponse distincte pour les personnes ayant des données de taille similaire.
La réponse de @Mikhail Berlyant répond aux exigences initiales de ma question. Malheureusement, parce qu'il repose sur une jointure croisée au niveau user_id et qu'il existe des dizaines de millions d'ID utilisateur, cela prend du temps pour mon cas d'utilisation spécifique. Je donne donc la réponse suivante, qui est plus rapide, mais nécessite une requête supplémentaire pour chaque groupe du tableau 1 (mais pas pour chaque combinaison de groupe1 et groupe2), ce qui rend la requête moins concise qui peuvent potentiellement dépasser les limites de la taille de la requête BigQuery si le nombre de groupes est très très important.
Cette approche est préférable si vous pouvez générer par programme les requêtes pour chaque groupe et avoir moins de groupes avec des millions d'utilisateurs, alors que la réponse de @Mikhail Berlyant devrait bien fonctionner dans les situations où il y a beaucoup plus de groupes avec un petit nombre d'utilisateurs chacun, et dans les cas où la génération de requête n'est pas effectuée par programme et que vous devez écrire chacune pour chaque groupe.
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```
Pourquoi l'utilisateur
1
fait-il deux fois partie dugroupA
?@CHP. . . Marquez la question avec la base de données que vous utilisez.
@The Empaler - C'est prévu. Un peu difficile à expliquer sans entrer dans la signification des groupes, mais en général, le groupe A ne regroupe pas des utilisateurs, mais des ensembles d'utilisateurs. Et ici, nous voulons compter le nombre d'ensembles dont un utilisateur du groupe A fait partie.
@GordonLinoff - Merci d'avoir fait cela dans BigQuery et ajouté une balise selon votre suggestion, bien que je devrais pouvoir convertir en BQ si j'obtiens la logique générale, qui, espérons-le, ne repose pas sur des fonctionnalités spécialisées.