подсчитать количество строк, которые встречаются для каждой даты в диапазоне дат столбца

У меня есть таблица с такими данными, как показано ниже

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

Я хочу создать представление, которое подсчитывает каждый день, например

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

Я использую Oracle 9 и совершенно не понимаю, как с этим справиться, и ищу любую идею, которая поможет мне начать работу.
Примечание. Создание таблицы для хранения дат нецелесообразно, потому что конечный продукт должен сломаться. вплоть до минуты.


person Will Dieterich    schedule 08.09.2009    source источник
comment
Относится ли дата, по вашему мнению, к дате начала или дате окончания?   -  person flayto    schedule 08.09.2009
comment
Вам нужно уточнить, что представляет ваш столбец Count. Подсчитывается ли количество раз, когда данная дата находится в пределах диапазона для данной группы?   -  person Welbog    schedule 08.09.2009


Ответы (3)


Обычно я решаю такую ​​проблему с помощью таблицы чисел:

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
person Cade Roux    schedule 08.09.2009
comment
Проголосовал, потому что это быстро и лаконично. Однако это код SQL Server, и его необходимо перевести на PL/SQL. - person Philip Kelley; 08.09.2009
comment
@Philip Kelley: это также вполне допустимый код Oracle. Однако это не будет генерировать количество для каждой группы/даты: если для данной даты нет записей для определенной группы, все эти группы будут объединены в одну запись с NULL в качестве группы и 1 в качестве количества. - person Quassnoi; 08.09.2009
comment
@Quassnoi: это не совсем правильный код Oracle. Функция DateAdd не существует, и у вас не может быть столбца с именем GROUP. - person Rob van Wijk; 10.09.2009

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

Обновление:

Более качественный и быстрый запрос с использованием аналитических функций.

Основная идея заключается в том, что количество диапазонов, содержащих каждую дату, представляет собой разницу до количества диапазонов, начавшихся до этой даты, и количества диапазонов, которые закончились до нее.

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

Этот запрос выполняется за 1 секунды для 1,000,000 строк.

Смотрите эту запись в моем блоге для более подробной информации:

person Quassnoi    schedule 08.09.2009

Вы можете использовать метод, описанный в этих SO:

В основном: присоединитесь к сгенерированному календарю и ГРУППИРУЙТЕ по своему подмножеству столбцов.

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
person Vincent Malgrat    schedule 08.09.2009