Тестови данни :
CREATE TABLE your_table ( usr, start_date, end_date ) AS (
SELECT 'A', DATE '2017-06-01', DATE '2017-06-03' FROM DUAL UNION ALL
SELECT 'B', DATE '2017-06-02', DATE '2017-06-04' FROM DUAL UNION ALL -- Overlaps previous
SELECT 'C', DATE '2017-06-06', DATE '2017-06-06' FROM DUAL UNION ALL
SELECT 'D', DATE '2017-06-07', DATE '2017-06-07' FROM DUAL UNION ALL -- Adjacent to previous
SELECT 'E', DATE '2017-06-11', DATE '2017-06-20' FROM DUAL UNION ALL
SELECT 'F', DATE '2017-06-14', DATE '2017-06-15' FROM DUAL UNION ALL -- Within previous
SELECT 'G', DATE '2017-06-22', DATE '2017-06-25' FROM DUAL UNION ALL
SELECT 'H', DATE '2017-06-24', DATE '2017-06-28' FROM DUAL UNION ALL -- Overlaps previous and next
SELECT 'I', DATE '2017-06-27', DATE '2017-06-30' FROM DUAL UNION ALL
SELECT 'J', DATE '2017-06-27', DATE '2017-06-28' FROM DUAL; -- Within H and I
Заявка :
SELECT SUM( days ) AS total_days
FROM (
SELECT dt - LAG( dt ) OVER ( ORDER BY dt ) + 1 AS days,
start_end
FROM (
SELECT dt,
CASE SUM( value ) OVER ( ORDER BY dt ASC, value DESC, ROWNUM ) * value
WHEN 1 THEN 'start'
WHEN 0 THEN 'end'
END AS start_end
FROM your_table
UNPIVOT ( dt FOR value IN ( start_date AS 1, end_date AS -1 ) )
)
WHERE start_end IS NOT NULL
)
WHERE start_end = 'end';
Изход :
TOTAL_DAYS
----------
25
Обяснение :
SELECT dt, value
FROM your_table
UNPIVOT ( dt FOR value IN ( start_date AS 1, end_date AS -1 ) )
Това ще UNPIVOT
таблицата, така че началната и крайната дата да са в една и съща колона (dt
) и им е дадена съответна стойност от +1 за начална и -1 за крайна дата.
SELECT dt,
SUM( value ) OVER ( ORDER BY dt ASC, value DESC, ROWNUM ) AS total,
value
FROM your_table
UNPIVOT ( dt FOR value IN ( start_date AS 1, end_date AS -1 ) )
Ще даде началната и крайната дата и кумулативната сума на тези генерирани стойности. Началото на диапазон винаги ще има value=1
и total=1
и краят на диапазон винаги ще има total=0
. Ако дадена дата е по средата на диапазон, тогава тя ще има или total>1
или value=-1
и total=1
. Използвайки това, ако умножите value
и total
тогава началото на диапазон е, когато value*total=1
и краят на диапазон е, когато value*total=0
и всяка друга стойност показва дата, която е по средата на диапазон.
Което дава това:
SELECT dt,
CASE SUM( value ) OVER ( ORDER BY dt ASC, value DESC, ROWNUM ) * value
WHEN 1 THEN 'start'
WHEN 0 THEN 'end'
END AS start_end
FROM your_table
UNPIVOT ( dt FOR value IN ( start_date AS 1, end_date AS -1 ) )
След това можете да филтрирате датите, когато start_end
е NULL
което ще ви остави с таблица с редуващо се start
и end
редове, които можете да използвате LAG
за да изчислите разликата в броя дни:
SELECT dt - LAG( dt ) OVER ( ORDER BY dt ) + 1 AS days,
start_end
FROM (
SELECT dt,
CASE SUM( value ) OVER ( ORDER BY dt ASC, value DESC, ROWNUM ) * value
WHEN 1 THEN 'start'
WHEN 0 THEN 'end'
END AS start_end
FROM your_table
UNPIVOT ( dt FOR value IN ( start_date AS 1, end_date AS -1 ) )
)
WHERE start_end IS NOT NULL
Всичко, което трябва да направите тогава, е да SUM
всички разлики за end - start
; което дава заявката по-горе.