подсчитать количество строк, которые встречаются для каждой даты в диапазоне дат столбца
Вопрос
У меня есть таблица с данными, как показано ниже.
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 и совершенно не понимаю, как с этим справиться, и ищу любую идею, с которой можно было бы начать.
Примечание:Создание таблицы для хранения дат нецелесообразно, поскольку конечный продукт должен быть разбит по минутам.
Решение
Обычно я решаю такого рода задачи с помощью таблицы чисел:
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
Другие советы
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
ряды.
Более подробную информацию смотрите в этой записи в моем блоге:
Вы можете использовать метод, описанный в этих SO:
По сути:присоединитесь к сгенерированному календарю и GROUP BY по вашему подмножеству столбцов.
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