2009-09-08 10 views
1

J'ai une table avec des données telles que ci-dessous
nombre de nombre de lignes qui se produisent pour chaque date dans la plage de dates de la colonne

Group  Start Date  End Date 
A  01/01/01  01/03/01 
A  01/01/01  01/02/01 
A  01/03/01  01/04/01 
B  01/01/01  01/01/01 
ETC 

Je cherche à produire une vue qui donne un compte pour chaque jour, comme celui-ci

Group  Date  Count 
A  01/01/01   2 
A  01/02/01   2 
A  01/03/01   2 
A  01/04/01   1 
B  01/01/01   1 

J'utilise Oracle 9 et je suis à une perte totale sur ce que la façon de gérer cela et je cherche une idée pour me commencer.
Note: Générer une table pour tenir les dates n'est pas pratique car le produit final doit se décomposer à la minute près.

+0

est "Date" dans votre vue en référence à Date de début ou Date de fin? – flayto

+0

Vous devez préciser ce que représente votre colonne "Count". Est-ce qu'il compte le nombre de fois qu'une date donnée est comprise dans une plage pour un groupe donné? – Welbog

Répondre

1

En général, je résoudre ce genre de problème avec une table de nombres:

WITH Dates AS (
    SELECT DateAdd(d, Numbers.Number - 1, '1/1/2001') AS Date 
    FROM Numbers 
    WHERE Numbers.Number BETWEEN 1 AND 100000 -- Arbitrary date range 
) 
SELECT GroupData.Group, Dates.Date, COUNT(*) 
FROM Dates 
LEFT JOIN GroupData 
    ON Dates.Date BETWEEN GroupData.StartDate AND GroupData.EndDate 
GROUP BY GroupData.Group, Dates.Date 
ORDER BY GroupData.Group, Dates.Date 
+0

Upvoted parce que c'est rapide et consight. Cependant, il s'agit d'un code SQL Server, et il devrait être "traduit" en PL/SQL. –

+0

'@Philip Kelley': c'est tout aussi bien le code' Oracle' parfaitement valide. Cependant, ceci ne produira pas de compte pour chaque groupe/date: si pour une date donnée il n'y a pas d'enregistrements pour un certain groupe, tous ces groupes seront fusionnés dans un seul enregistrement avec un 'NULL' comme groupe et '1' en tant que compte. – Quassnoi

+0

@Quassnoi: ce code Oracle n'est pas parfaitement valide. La fonction DateAdd n'existe pas et vous ne pouvez pas avoir une colonne appelée GROUP. –

2

Vous pouvez utiliser la méthode décrite dans ces SO:

En gros: se joindre à un calendrier généré et GROUP BY votre sous-ensemble de colonnes.

SQL> WITH DATA AS (
    2 SELECT 'A' grp, to_date('01/01/01') start_date, to_date('01/03/01') end_date FROM DUAL 
    3 UNION ALL SELECT 'A', to_date('01/01/01'), to_date('01/02/01') FROM DUAL 
    4 UNION ALL SELECT 'A', to_date('01/03/01'), to_date('01/04/01') FROM DUAL 
    5 UNION ALL SELECT 'B', to_date('01/01/01'), to_date('01/01/01') FROM DUAL 
    6 ), calendar AS (
    7 SELECT to_date('01/01/01') + ROWNUM - 1 d 
    8 FROM dual 
    9 CONNECT BY LEVEL <= to_date('01/04/01') - to_date('01/01/01') + 1 
10 ) 
11 SELECT data.grp, calendar.d, COUNT(*) cnt 
12 FROM data 
13 JOIN calendar ON calendar.d BETWEEN data.start_date AND data.end_date 
14 GROUP BY data.grp, calendar.d; 

GRP D     CNT 
--- ----------- ---------- 
A 04/01/2001   1 
A 02/01/2001   2 
B 01/01/2001   1 
A 03/01/2001   2 
A 01/01/2001   2 
3
WITH q AS 
     (
     SELECT (
       SELECT MIN(start_date) 
       FROM mytable 
       ) + level - 1 AS mydate 
     FROM dual 
     CONNECT BY 
       level <= (
       SELECT MAX(end_date) - MIN(start_date) 
       FROM mytable 
       ) 
     ) 
SELECT group, mydate, 
     (
     SELECT COUNT(*) 
     FROM mytable mi 
     WHERE mi.group = mo.group 
       AND q BETWEEN mi.start_date AND mi.end_date 
     ) 
FROM q 
CROSS JOIN 
     (
     SELECT DISTINCT group 
     FROM mytable 
     ) mo 

Mise à jour:

Une meilleure requête et plus rapide en utilisant des fonctions analytiques.

L'idée principale est que le nombre de plages contenant chaque date est la différence avant le comptage des plages commencées avant cette date et le nombre de plages qui se terminaient avant elle.

SELECT cur_date, 
     grouper, 
     SUM(COALESCE(scnt, 0) - COALESCE(ecnt, 0)) OVER (PARTITION BY grouper ORDER BY cur_date) AS ranges 
FROM (
     SELECT (
       SELECT MIN(start_date) 
       FROM t_range 
       ) + level - 1 AS cur_date 
     FROM dual 
     CONNECT BY 
       level <= 
       (
       SELECT MAX(end_date) 
       FROM t_range 
       ) - 
       (
       SELECT MIN(start_date) 
       FROM t_range 
       ) + 1 
     ) dates 
CROSS JOIN 
     (
     SELECT DISTINCT grouper AS grouper 
     FROM t_range 
     ) groups 
LEFT JOIN 
     (
     SELECT grouper AS sgrp, start_date, COUNT(*) AS scnt 
     FROM t_range 
     GROUP BY 
       grouper, start_date 
     ) starts 
ON  sgrp = grouper 
     AND start_date = cur_date 
LEFT JOIN 
     (
     SELECT grouper AS egrp, end_date, COUNT(*) AS ecnt 
     FROM t_range 
     GROUP BY 
       grouper, end_date 
     ) ends 
ON  egrp = grouper 
     AND end_date = cur_date - 1 
ORDER BY 
     grouper, cur_date 

Cette requête est terminée en 1 secondes sur 1,000,000 lignes.

Voir cette entrée dans mon blog pour plus de détails:

+0

Tu m'as battu dessus. Approche solide ... –

Questions connexes