Ответ 1
Мы можем использовать Oracle Analytics, а именно предложение OVER... PARTITION BY, в Oracle для этого. Предложение PARTITION BY похоже на GROUP BY, но без агрегационной части. Это означает, что мы можем группировать строки вместе (т.е. Разделять их), и они выполняют операцию над ними как отдельные группы. Когда мы работаем над каждой строкой, мы можем получить доступ к столбцам предыдущей строки выше. Это функция PARTITION BY дает нам. (PARTITION BY не относится к разбиению таблицы на производительность.)
Итак, как мы выводим неперекрывающиеся даты? Сначала мы заказываем запрос на основе полей (ID, DFROM), затем мы используем поле ID для создания наших разделов (группы строк). Затем мы проверяем значение предыдущей строки TO и текущее значение FROM строк для перекрытия с использованием выражения типа: (в псевдокоде)
max(previous.DTO, current.DFROM) as DFROM
Это базовое выражение вернет исходное значение DFROM, если оно не перекрывается, но вернет предыдущее значение TO, если есть перекрытие. Поскольку наши строки упорядочены, нам нужно только иметь дело с последней строкой. В тех случаях, когда предыдущая строка полностью перекрывает текущую строку, мы хотим, чтобы строка имела нулевой диапазон дат. Итак, мы делаем то же самое для поля DTO:
max(previous.DTO, current.DFROM) as DFROM, max(previous.DTO, current.DTO) as DTO
Как только мы сгенерировали новые результаты с установленными значениями DFROM и DTO, мы можем их суммировать и подсчитать интервалы интервалов DFROM и DTO.
Помните, что большинство вычислений даты в базе данных не являются такими, как ваши данные. Так что что-то вроде DATEDIFF (dto, dfrom) не будет включать в себя день, на который фактически ссылается, поэтому мы хотим сначала отрегулировать dto на первый день.
У меня больше нет доступа к серверу Oracle, но я знаю, что это возможно с помощью Oracle Analytics. Запрос должен выглядеть примерно так: (Пожалуйста, обновите мой пост, если вы его заработаете.)
SELECT id,
max(dfrom, LAST_VALUE(dto) OVER (PARTITION BY id ORDER BY dfrom) ) as dfrom,
max(dto, LAST_VALUE(dto) OVER (PARTITION BY id ORDER BY dfrom) ) as dto
from (
select id, dfrom, dto+1 as dto from my_sample -- adjust the table so that dto becomes non-inclusive
order by id, dfrom
) sample;
Секрет здесь - выражение LAST_VALUE (dto) OVER (PARTITION BY id ORDER BY dfrom), которое возвращает значение, предшествующее текущей строке. Таким образом, этот запрос должен выводить новые значения dfrom/dto, которые не перекрываются. Тогда это просто вопрос подпроцесса этого выполнения (dto-dfrom) и суммирование итогов.
Использование MySQL
У меня был доступ к серверу mysql, поэтому я действительно работал там. MySQL не имеет разбиения на результат (Analytics), например Oracle, поэтому нам нужно использовать переменные набора результатов. Это означает, что мы используем выражения типа @var: = xxx для запоминания последнего значения даты и настройки dfrom/dto. Один и тот же алгоритм чуть более длинный и сложный синтаксис. Мы также должны забыть последнее значение даты в любое время, когда изменится поле ID!
Итак, вот пример таблицы (те же самые значения):
create table sample(id int, dfrom date, dto date, networkDay int);
insert into sample values
(1,'2012-09-03','2012-09-07',5),
(1,'2012-09-03','2012-09-04',2),
(1,'2012-09-05','2012-09-06',2),
(1,'2012-09-06','2012-09-12',5),
(1,'2012-08-31','2012-09-04',3),
(2,'2012-09-04','2012-09-06',3),
(2,'2012-09-11','2012-09-13',3),
(2,'2012-09-05','2012-09-08',3);
В ответ на запрос выводится негруппированный результирующий набор, как указано выше: Переменная @ld - "последняя дата", а переменная @lid - "последний id". Anytime @lid изменяется, мы reset @ld на null. FYI В mysql операторы: =, где выполняется присваивание, оператор an = просто равен.
Это 3-уровневый запрос, но он может быть уменьшен до 2. Я пошел с дополнительным внешним запросом, чтобы сделать вещи более читабельными. Внутренний самый запрос прост и настраивает столбец dto не включительно и делает правильный порядок строк. Средний запрос выполняет настройку значений dfrom/dto, чтобы сделать их неперекрывающимися. Внешний запрос просто отбрасывает неиспользуемые поля и вычисляет интервал диапазона.
set @ldt=null, @lid=null;
select id, no_dfrom as dfrom, no_dto as dto, datediff(no_dto, no_dfrom) as days from (
select if(@lid=id,@ldt,@ldt:=null) as last, dfrom, dto, if(@ldt>=dfrom,@ldt,dfrom) as no_dfrom, if(@ldt>=dto,@ldt,dto) as no_dto, @ldt:=if(@ldt>=dto,@ldt,dto), @lid:=id as id,
datediff(dto, dfrom) as overlapped_days
from (select id, dfrom, dto + INTERVAL 1 DAY as dto from sample order by id, dfrom) as sample
) as nonoverlapped
order by id, dfrom;
Вышеприведенный запрос дает результаты (уведомление dfrom/dto здесь не перекрывается):
+------+------------+------------+------+
| id | dfrom | dto | days |
+------+------------+------------+------+
| 1 | 2012-08-31 | 2012-09-05 | 5 |
| 1 | 2012-09-05 | 2012-09-08 | 3 |
| 1 | 2012-09-08 | 2012-09-08 | 0 |
| 1 | 2012-09-08 | 2012-09-08 | 0 |
| 1 | 2012-09-08 | 2012-09-13 | 5 |
| 2 | 2012-09-04 | 2012-09-07 | 3 |
| 2 | 2012-09-07 | 2012-09-09 | 2 |
| 2 | 2012-09-11 | 2012-09-14 | 3 |
+------+------------+------------+------+