Dữ liệu thử nghiệm :
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
Truy vấn :
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';
Đầu ra :
TOTAL_DAYS
----------
25
Giải thích :
SELECT dt, value
FROM your_table
UNPIVOT ( dt FOR value IN ( start_date AS 1, end_date AS -1 ) )
Điều này sẽ UNPIVOT
bảng sao cho ngày bắt đầu và ngày kết thúc nằm trong cùng một cột (dt
) và được cung cấp giá trị tương ứng là +1 cho ngày bắt đầu và -1 cho ngày kết thúc.
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 ) )
Sẽ cung cấp ngày bắt đầu và ngày kết thúc và tổng tích lũy của các giá trị được tạo đó. Phần đầu của một dải ô sẽ luôn có value=1
và total=1
và phần cuối của một dải ô sẽ luôn có total=0
. Nếu một ngày ở giữa một phạm vi thì ngày đó sẽ có total>1
hoặc value=-1
và total=1
. Sử dụng điều này, nếu bạn nhân value
và total
thì đầu của một phạm vi là khi value*total=1
và phần cuối của một dải ô là khi value*total=0
và bất kỳ giá trị nào khác cho biết một ngày đang ở giữa một phạm vi.
Đó là những gì điều này mang lại:
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 ) )
Sau đó, bạn có thể lọc ra các ngày khi start_end
là NULL
sẽ để lại cho bạn một bảng có start
xen kẽ và end
hàng mà bạn có thể sử dụng LAG
để tính toán số ngày chênh lệch:
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
Tất cả những gì bạn cần làm sau đó là SUM
tất cả sự khác biệt cho end - start
; đưa ra truy vấn ở trên.