3

J'ai besoin d'aide pour écrire/optimiser une requête pour récupérer la dernière version de chaque ligne par type et effectuer quelques calculs en fonction du type. Je pense que ce serait mieux si je l'illustre avec un exemple.Comment obtenir la ligne la plus récente par type et effectuer des calculs, en fonction du type de ligne?

Compte tenu de l'ensemble de données suivantes:

+-------+-------------------+---------------------+-------------+---------------------+--------+----------+ 
| id | event_type  | event_timestamp  | message_id | sent_at    | status | rate  | 
+-------+-------------------+---------------------+-------------+---------------------+--------+----------+ 
| 1  | create   | 2016-11-25 09:17:48 | 1   | 2016-11-25 09:17:48 | 0  | 0.500000 | 
| 2  | status_update  | 2016-11-25 09:24:38 | 1   | 2016-11-25 09:28:49 | 1  | 0.500000 | 
| 3  | create   | 2016-11-25 09:47:48 | 2   | 2016-11-25 09:47:48 | 0  | 0.500000 | 
| 4  | status_update  | 2016-11-25 09:54:38 | 2   | 2016-11-25 09:48:49 | 1  | 0.500000 | 
| 5  | rate_update  | 2016-11-25 09:55:07 | 2   | 2016-11-25 09:50:07 | 0  | 1.000000 | 
| 6  | create   | 2016-11-26 09:17:48 | 3   | 2016-11-26 09:17:48 | 0  | 0.500000 | 
| 7  | create   | 2016-11-27 09:17:48 | 4   | 2016-11-27 09:17:48 | 0  | 0.500000 | 
| 8  | rate_update  | 2016-11-27 09:55:07 | 4   | 2016-11-27 09:50:07 | 0  | 2.000000 | 
| 9  | rate_update  | 2016-11-27 09:55:07 | 2   | 2016-11-25 09:55:07 | 0  | 2.000000 | 
+-------+-------------------+---------------------+-------------+---------------------+--------+----------+ 

Le résultat attendu devrait être:

+------------+--------------------+--------------------+-----------------------+ 
| sent_at | sum(submitted_msg) | sum(delivered_msg) | sum(rate_total)  | 
+------------+--------------------+--------------------+-----------------------+ 
| 2016-11-25 |     2 |     2 |    2.500000 | 
| 2016-11-26 |     1 |     0 |    0.500000 | 
| 2016-11-27 |     1 |     0 |    2.000000 | 
+------------+--------------------+--------------------+-----------------------+ 

A la fin du poste est la requête qui est utilisée pour obtenir ce résultat. Je suis prêt à parier qu'il devrait y avoir un moyen de l'optimiser, car il utilise des sous-requêtes avec des jointures, et d'après ce que j'ai lu sur BigQuery, les jointures devraient être évitées. Mais d'abord un peu d'arrière-plan:

En résumé, l'ensemble de données représente une table d'ajout uniquement à laquelle sont écrits des événements multipaires. La taille des données est dans les centaines de millions et va atteindre des milliards +. Puisque les mises à jour dans BigQuery ne sont pas pratiques, et que les données sont transmises à BQ, j'ai besoin d'un moyen de récupérer le plus récent de chaque événement, d'effectuer des calculs basés sur certaines conditions et de retourner un résultat précis. La requête est générée dynamiquement, en fonction de l'entrée de l'utilisateur, ce qui permet d'inclure davantage de champs/calculs, mais a été omis pour plus de simplicité.

  • Il n'y a qu'un seul événement create, mais n de tout autre type
  • Pour chaque groupe d'événements, seul le dernier devrait être pris en compte lorsque vous faites les calculs.
    • status_update - met à jour l'état
    • rate_update - met à jour le taux
    • créer - explicite
  • Chaque événement qui ne create peut pas porter le reste des informations de mai/original ne pas être précis (sauf pour message_id et le champ sur lequel l'événement fonctionne) (l'ensemble de données est simplifié, mais imaginez qu'il y ait beaucoup plus de colonnes, et plus d'événements seront ajoutés plus tard)
    • E.g. un rate_update peut ou peut ne pas avoir l'ensemble du champ d'état, ou une valeur qui n'est pas la finale, donc pas de calcul peut être effectué sur le champ d'état d'un événement rate_update et va de même pour status_update
  • Il peut Supposons que la table est partitionnée par date et que chaque requête utilisera les partions. Ces conditions ont été omises en faveur de la simplicité pour le moment.

Je suppose que j'ai quelques questions:

  • Comment cette requête être optimisée? Sera-t-il préférable de placer les événements autres que create dans leurs propres tables, où les seuls champs disponibles seront ceux pertinents pour les événements, et nécessaires pour les jointures (message_id, event_timestamp)? Est-ce que cela réduira la quantité de données traitées?
  • Quelle serait la manière la plus optimale d'ajouter plus d'événements dans le futur, qui auront leurs propres conditions et calculs?

En fait, tout conseil sur la façon d'interroger ce jeu de données efficacement et amical est plus que bienvenu! Je vous remercie! :)

La monstruosité que j'ai trouvée est la suivante. Les INNER JOINS sont utilisés pour récupérer la dernière version de chaque ligne, selon cette resource

select 
    sent_at as sent_at, 
    sum(submitted_msg) as submitted, 
    sum(delivered_msg) as delivered, 
    sum(sales_rate_total) as sales_rate_total 
    FROM (

     #DELIVERED 
     SELECT 
      d.message_id, 
      FORMAT_TIMESTAMP('%Y-%m-%d 00:00:00', sent_at) AS sent_at, 
      0 as submitted_msg, 
      sum(if(status=1,1,0)) as delivered_msg, 
      0 as sales_rate_total 
     FROM `events` d 
     INNER JOIN 
       (
        select message_id, max(event_timestamp) as ts 
        from `events` 
        where event_type = "status_update" 
        group by 1 
        ) g on d.message_id = g.message_id and d.event_timestamp = g.ts 
     GROUP BY 1,2 

     UNION ALL 

     #SALES RATE 
     SELECT 
      s.message_id, 
      FORMAT_TIMESTAMP('%Y-%m-%d 00:00:00', sent_at) AS sent_at, 
      0 as submitted_msg, 
      0 as delivered_msg, 
      sum(sales_rate) as sales_rate_total 
     FROM `events` s 
     INNER JOIN 
        (
        select message_id, max(event_timestamp) as ts 
        from `events` 
        where event_type in ("rate_update", "create") 
        group by 1 
        ) f on s.message_id = f.message_id and s.event_timestamp = f.ts 
     GROUP BY 1,2 

     UNION ALL 

     #SUBMITTED & REST 
     SELECT 
      r.message_id, 
      FORMAT_TIMESTAMP('%Y-%m-%d 00:00:00', sent_at) AS sent_at, 
      sum(if(status=0,1,0)) as submitted_msg, 
      0 as delivered_msg, 
      0 as sales_rate_total 
     FROM `events` r 
     INNER JOIN 
       (
        select message_id, max(event_timestamp) as ts 
        from `events` 
        where event_type = "create" 
        group by 1 
        ) e on r.message_id = e.message_id and r.event_timestamp = e.ts 
     GROUP BY 1, 2 

    ) k 
    group by 1 
+1

Vous pouvez également être intéressé par une demande de fonctionnalité similaire (https://code.google.com/p/google-bigquery/issues/detail?id=706). –

Répondre

4

Comment cette requête peut-elle être optimisée?

Essayez ci-dessous la version

#standardSQL 
WITH types AS (
    SELECT 
    FORMAT_TIMESTAMP('%Y-%m-%d', sent_at) AS sent_at, 
    message_id, 
    FIRST_VALUE(status) OVER(PARTITION BY message_id ORDER BY (event_type = "create") DESC, event_timestamp DESC) AS submitted_status, 
    FIRST_VALUE(status) OVER(PARTITION BY message_id ORDER BY (event_type = "status_update") DESC, event_timestamp DESC) AS delivered_status, 
    FIRST_VALUE(rate) OVER(PARTITION BY message_id ORDER BY (event_type IN ("rate_update", "create")) DESC, event_timestamp DESC) AS sales_rate 
    FROM events 
), latest AS (
    SELECT 
    sent_at, 
    message_id, 
    ANY_VALUE(IF(submitted_status=0,1,0)) AS submitted, 
    ANY_VALUE(IF(delivered_status=1,1,0)) AS delivered, 
    ANY_VALUE(sales_rate) AS sales_rate 
    FROM types 
    GROUP BY 1, 2 
) 
SELECT 
    sent_at, 
    SUM(submitted) AS submitted, 
    SUM(delivered) AS delivered, 
    SUM(sales_rate) AS sales_rate_total   
FROM latest 
GROUP BY 1 

Il est assez compact pour gérer facilement, sans redondance, sans jointures du tout, etc.
Si votre table partitionnée - vous pouvez facilement l'utiliser en réglant la requête juste un endroit

Vous pouvez utiliser les données ci-dessous si vous voulez factices vérifier ci-dessus requête sur un faible volume premier

WITH events AS (
    SELECT 1 AS id, 'create' AS event_type, TIMESTAMP '2016-11-25 09:17:48' AS event_timestamp, 1 AS message_id, TIMESTAMP '2016-11-25 09:17:48' AS sent_at, 0 AS status, 0.500000 AS rate UNION ALL 
    SELECT 2 AS id, 'status_update' AS event_type, TIMESTAMP '2016-11-25 09:24:38' AS event_timestamp, 1 AS message_id, TIMESTAMP '2016-11-25 09:28:49' AS sent_at, 1 AS status, 0.500000 AS rate UNION ALL 
    SELECT 3 AS id, 'create' AS event_type, TIMESTAMP '2016-11-25 09:47:48' AS event_timestamp, 2 AS message_id, TIMESTAMP '2016-11-25 09:47:48' AS sent_at, 0 AS status, 0.500000 AS rate UNION ALL 
    SELECT 4 AS id, 'status_update' AS event_type, TIMESTAMP '2016-11-25 09:54:38' AS event_timestamp, 2 AS message_id, TIMESTAMP '2016-11-25 09:48:49' AS sent_at, 1 AS status, 0.500000 AS rate UNION ALL 
    SELECT 5 AS id, 'rate_update' AS event_type, TIMESTAMP '2016-11-25 09:55:07' AS event_timestamp, 2 AS message_id, TIMESTAMP '2016-11-25 09:50:07' AS sent_at, 0 AS status, 1.000000 AS rate UNION ALL 
    SELECT 6 AS id, 'create' AS event_type, TIMESTAMP '2016-11-26 09:17:48' AS event_timestamp, 3 AS message_id, TIMESTAMP '2016-11-26 09:17:48' AS sent_at, 0 AS status, 0.500000 AS rate UNION ALL 
    SELECT 7 AS id, 'create' AS event_type, TIMESTAMP '2016-11-27 09:17:48' AS event_timestamp, 4 AS message_id, TIMESTAMP '2016-11-27 09:17:48' AS sent_at, 0 AS status, 0.500000 AS rate UNION ALL 
    SELECT 8 AS id, 'rate_update' AS event_type, TIMESTAMP '2016-11-27 09:55:07' AS event_timestamp, 4 AS message_id, TIMESTAMP '2016-11-27 09:50:07' AS sent_at, 0 AS status, 2.000000 AS rate UNION ALL 
    SELECT 9 AS id, 'rate_update' AS event_type, TIMESTAMP '2016-11-27 09:55:07' AS event_timestamp, 2 AS message_id, TIMESTAMP '2016-11-25 09:55:07' AS sent_at, 0 AS status, 2.000000 AS rate 
) 
+0

Bravo! Cela fait vraiment l'affaire et l'amélioration est super :) – vulkoingim

0

Pour chaque table qui contient plusieurs événements et où nous devons choisir la dernière, nous avons une vue en place.

Vue: user_profile_latest

SELECT * from (
    select rank() over (partition by user_id order by bq.created DESC, bq.insert_id desc) as _rank, 
* 
FROM [user_profile_event] 
) where _rank=1 

Nous maintenons un BQ record avec et créé insert_id à des fins de déduplication.

+0

Je ne suis pas sûr si je reçois ça ... Il me semble que cela va simplifier la requête, en termes de lignes écrites, mais pas dans les performances/données traitées? Corrigez-moi si je me trompe ... mais dans cette vue, nous ne pouvons pas profiter de la partition de date. Supposons que 'les partitions de la table' =' sent_at'. Si je veux interroger pour la plage de dates spécifique, je peux ajouter '_PARTITIONTIME' à chacune des requêtes et cela réduira considérablement la quantité de données traitées? – vulkoingim

+0

autant que je sache que vous êtes facturé pour les colonnes que vous utilisez, même si ici, il semble que vous lisez toutes les colonnes, mais plus tard, vous filtrez, et il a également effet, vous pouvez ajouter des conditions – Pentium10

+0

Oui, vous êtes facturé pour colonnes sélectionnées, mais également pour la quantité totale de données traitées. Ce qui peut être réduit si vous interrogez un sous-ensemble de celui-ci, qui réside dans une [partition] (https://cloud.google.com/bigquery/docs/partitioned-tables) – vulkoingim