2016-11-20 2 views
3

Étant donné chaque ligne représentant une tâche, avec l'heure de début et l'heure de fin, comment puis-je calculer le nombre de tâches en cours (démarrées et non terminées) au démarrage de chaque tâche (y compris lui-même) en utilisant une fonction de fenêtre avec COUNT OVER? Est-ce qu'une fonction de fenêtre est la bonne approche?COUNT() OVER conditionnée à la LIGNE COURANTE

Exemple, table donnée tasks:

task_id start_time end_time 
    a   1   10 
    b   2   5 
    c   5   15 
    d   8   13 
    e  12   20 
    f  21   30 

Calculer running_tasks:

task_id start_time end_time running_tasks 
    a   1   10   1   # a 
    b   2   5   2   # a,b 
    c   5   15   2   # a,c (b has ended) 
    d   8   13   3   # a,c,d 
    e  12   20   3   # c,d,e (a has ended) 
    f  21   30   1   # f (c,d,e have ended) 

Répondre

2
select  task_id,start_time,end_time,running_tasks 

from  (select  task_id,tm,op,start_time,end_time 

         ,sum(op) over 
         (
          order by tm,op 
          rows  unbounded preceding 
         ) as running_tasks 

      from  (select  task_id,start_time as tm,1 as op,start_time,end_time 
         from  tasks 

         union all 

         select  task_id,end_time as tm,-1 as op,start_time,end_time 
         from  tasks 
         ) t 
      )t 

where  op = 1 
; 
+0

Dudu, merci - solution intelligente, et il résout l'exemple simplifié spécifique. J'espérais obtenir une solution plus généralisée où la fonction de fenêtre pourrait être conditionnée sur la ligne actuelle - est-ce possible? –

+0

@NewDev, je ne comprends pas votre intention s'il vous plaît fournir un exemple. –

+0

Est-il possible de 'COUNT' sur une fonction de fenêtre avec une condition pour ne compter que les lignes qui satisfont une condition par rapport à la ligne courante.Par exemple, si la ligne actuelle est 'start_time = 8', la fonction de la fenêtre ne peut-elle compter que les lignes dont' end_time> 8' et 'start_time <= 8'? –

2

Vous pouvez utiliser une sous-requête corrélative, qui dans ce cas est une auto-jointure; aucune fonction analytique n'est nécessaire. Après avoir activé standard SQL (décocher la case « Utiliser héritage SQL » sous « Afficher les options » dans l'interface utilisateur), vous pouvez exécuter cet exemple:

WITH tasks AS (
    SELECT 
    task_id, 
    start_time, 
    end_time 
    FROM UNNEST(ARRAY<STRUCT<task_id STRING, start_time INT64, end_time INT64>>[ 
    ('a', 1, 10), 
    ('b', 2, 5), 
    ('c', 5, 15), 
    ('d', 8, 13), 
    ('e', 12, 20), 
    ('f', 21, 30) 
    ]) 
) 
SELECT 
    *, 
    (SELECT COUNT(*) FROM tasks t2 
    WHERE t.start_time >= t2.start_time AND 
    t.start_time < t2.end_time) AS running_tasks 
FROM tasks t 
ORDER BY task_id; 
+0

"aucune fonction analytique n'est nécessaire"? et cela vous semble être un avantage? –

+1

Oui - il est généralement plus difficile d'expliquer les fonctions analytiques aux nouveaux utilisateurs SQL que ce ne sont des concepts tels que les jointures et les agrégations. Dans ce cas, l'OP a maintenant des réponses qui donnent deux points de vue différents sur le problème, ce qui est génial :) –

+0

Je l'ai, mais j'ajouterais au moins un petit signe - "avertissement! Danger de performance à venir!" –

2

Comme Elliott a mentionné - « il est généralement plus difficile d'expliquer les fonctions analytiques aux nouveaux utilisateurs » et même les utilisateurs établis ne sont pas toujours 100% bons (tout en étant très très proche)!
Ainsi, alors que la réponse de Dudu Markovitz est grande - malheureusement, elle est toujours incorrecte (au moins selon la façon dont j'ai compris la question). Le cas où ce n'est pas correct quand vous avez plusieurs tâches commencé à la start_time - de sorte que ces tâches ont mal « tâches en cours d'exécution » résultat

À titre d'exemple - traiterai exemple:

task_id start_time end_time 
    a   1   10 
    aa  1   2 
    aaa  1   8 
    b   2   5 
    c   5   15 
    d   8   13 
    e  12   20 
    f  21   30 

Je pense , vous attendez ci-dessous résultat:

task_id start_time end_time running_tasks 
    a   1   10   3   # a,aa,aaa 
    aa  1   2   3   # a,aa,aaa 
    aaa  1   8   3   # a,aa,aaa 
    b   2   5   3   # a,aaa,b (aa has ended) 
    c   5   15   3   # a,aaa,c (b has ended) 
    d   8   13   3   # a,c,d (aaa has ended) 
    e  12   20   3   # c,d,e (a has ended) 
    f  21   30   1   # f (c,d,e have ended)  

Si vous essayez avec le code de Dudu - vous obtiendrez ci-dessous à la place

task_id start_time end_time running_tasks 
    a   1   10   1   
    aa  1   2   2   
    aaa  1   8   3   
    b   2   5   3   
    c   5   15   3   
    d   8   13   3   
    e  12   20   3   
    f  21   30   1   

Comme vous pouvez voir le résultat pour les tâches a et aa mal.
La raison est en raison de l'utilisation de ROWS UNBOUNDED PRECEDING au lieu de RANGE UNBOUNDED PRECEDING - petite mais très importante nuance!

donc ci-dessous requête vous donnera un résultat correct

SELECT task_id,start_time,end_time,running_tasks 
FROM (
    SELECT 
    task_id, tm, op, start_time, end_time, 
    SUM(op) OVER (ORDER BY tm ,op RANGE UNBOUNDED PRECEDING) AS running_tasks 
    FROM (
    SELECT 
     task_id, start_time AS tm, 1 AS op, start_time, end_time 
    FROM tasks UNION ALL 
    SELECT 
     task_id, end_time AS tm, -1 AS op, start_time, end_time 
    FROM tasks 
) t 
)t 
WHERE op = 1 
ORDER BY start_time  

résumé rapide:
RANGS UNBOUNDED PRÉCÉDANT - définit le cadre de la fenêtre en fonction de la position de lignes
tandis que
GAMME UNBOUNDED PRÉCÉDANT - définit le cadre de la fenêtre basé sur les valeurs des lignes

Encore une fois - comme Elliott l'a mentionné - il est beaucoup plus complexe d'y entrer que le concept JOIN - mais il en vaut la peine (car il est beaucoup plus efficace han rejoint) - voir plus sur Window Frame Clause et ROWS vs RANGE utiliser