2016-09-19 64 views
0

我有如下表:ORACLE SQL - 連接重疊時限

╔══════╦═══════════╦═════════╗ 
║ Emp# ║ StartDate ║ EndDate ║ 
╠══════╬═══════════╬═════════╣ 
║ 1 ║ 1Jan  ║ 15Jan ║ 
║ 1 ║ 3Jan  ║ 5Jan ║ 
║ 1 ║ 10Jan  ║ 20Jan ║ 
║ 1 ║ 23Jan  ║ 25Jan ║ 
║ 1 ║ 24Jan  ║ 27Jan ║ 
╚══════╩═══════════╩═════════╝ 

我需要創建一個查詢,將完美地連接重疊,這樣對每一個可能日曆日期有每最多1行僱員。輸出應該如下:

╔══════╦═══════════╦═════════╗ 
║ Emp# ║ StartDate ║ EndDate ║ 
╠══════╬═══════════╬═════════╣ 
║ 1 ║ 1Jan  ║ 20Jan ║ 
║ 1 ║ 23Jan  ║ 27Jan ║ 
╚══════╩═══════════╩═════════╝ 

我試圖用自連接來做,但是我需要X重疊的X自連接。我希望找到解決方案的任何方向。 非常感謝您提前!

+0

請發表你嘗試過什麼至今。 – Aleksej

+1

尋找「空白和島嶼問題」。 – mustaccio

+0

我使用了自我連接,以便檢查兩個時間範圍之間是否有重疊 - 如果有的話我會選擇最小開始日期和最大結束日期。然後,我將選擇每個結束日期的最小開始日期和每個最小開始日期的最大結束日期。然而,如果我有兩個時間範圍都被第三個時間範圍重疊,這會讓我留下一些漏洞。 – Dezz

回答

0

這裏是較舊的溶液(從評論中的一個),適用於純日期。您可能希望比較此處提供的不同解決方案,以查看哪一個對您的實際數據最有效;不同的解決方案可能適用於不同的情況。

注意:我使用了輸入數據並創建了一些用於測試的數據。假設您的數據有效(所有日期都有效,它們的時間分量爲00:00:00,並且enddate始終大於或等於startdate)。該解決方案不包括inputs因式分解子查詢,它僅在下面顯示用於測試。我沒有通過emp#startdate(這方面的輸出可能會誤導)來排列結果;如果你確實需要這樣的順序,你需要明確地添加它。請注意在測試數據中使用date文字。輸出顯示我當前會話設置中的日期;如果您需要特定的格式,請使用to_date()以及所需的顯示格式型號。

QUERY:

with 
    inputs (emp#, startdate, enddate) as (
     select 1, date '2016-01-01', date '2016-01-15' from dual union all 
     select 1, date '2016-01-03', date '2016-01-05' from dual union all 
     select 1, date '2016-01-10', date '2016-01-20' from dual union all 
     select 1, date '2016-01-23', date '2016-01-25' from dual union all 
     select 1, date '2016-01-24', date '2016-01-27' from dual union all 
     select 2, date '2016-01-31', date '2016-02-28' from dual union all 
     select 2, date '2016-03-15', date '2016-03-18' from dual union all 
     select 2, date '2016-03-19', date '2016-03-19' from dual union all 
     select 2, date '2016-03-20', date '2016-03-20' from dual 
    ), 
    m (emp#, startdate, mdate) as (
     select  emp#, startdate, 
        1 + max(enddate) over (partition by emp# order by startdate 
          rows between unbounded preceding and 1 preceding) 
     from  inputs 
     union all 
     select  emp#, NULL, 1 + max(enddate) 
      from  inputs 
      group by emp# 
    ), 
    n (emp#, startdate, mdate) as (
     select emp#, startdate, mdate 
     from m 
     where startdate > mdate or startdate is null or mdate is null 
    ), 
    f (emp#, startdate, enddate) as (
     select emp#, startdate, 
       lead(mdate) over (partition by emp# order by startdate) - 1 
     from n 
    ) 
select * from f where startdate is not null 

OUTPUT(在inputs CTE數據):

EMP# STARTDATE ENDDATE   
------ ---------- ---------- 
    1 01/01/2016 20/01/2016 
    1 23/01/2016 27/01/2016 
    2 31/01/2016 28/02/2016 
    2 15/03/2016 20/03/2016 
3

這裏的理念是:

  1. 標識一組的開始位置。爲此,使用existscase
  2. 爲這些日期分配一個標誌。
  3. 積累標誌,以便所有重疊時間段具有相同的值。
  4. 使用此爲聚集

這種方法效果很好,但它需要一個輕微的調整時,這兩個時間段有開頭的重疊期相同的開始日期。所以:

select emp#, min(startdate) as startdate, max(enddate) as enddate 
from (select t.*, 
      sum(OverlapFlag) over (partition by Emp# order by startdate) as grp 
     from (select t.*, 
        (case when exists (select 1 
             from t2 
             where t2.Emp# = t.Emp# and 
              t2.startdate < t.startdate and 
              t2.enddate + 1 >= t.startdate 
            ) 
         then 0 else 1 
        end) as OverlapFlag 
      from t 
      ) t 
    ) t 
group by emp#, grp; 
+0

偉大的解決方案。 –

+0

偉大的解決方案,但它無法合併相鄰的時間間隔。例如,如果emp#1有間隔1Jan-3Jan和4Jan-9Jan,結果應該是1Jan-9Jan;然而,這裏提供的解決方案將它們分開。 – mathguy

0

我會在這裏使用PL-SQL。從StartDate開始對記錄進行排序,然後每隔一段時間查看StartDate是否仍在給定的範圍內。如果是這樣,請檢查EndDate是否擴展了範圍。

這裏是包標頭:

create or replace package mypackage as 
    type type_mytable is table of mytable%rowtype; 
    function get_ranges return type_mytable pipelined; 
end mypackage; 

封裝體:

create or replace package body mypackage as 
    function get_ranges return type_mytable pipelined as 
    v_current mytable%rowtype; 
    begin 
    for rec in 
    (
     select * 
     from mytable 
     order by emp#, startdate 
    ) loop 
     if rec.emp# = v_current.emp# and rec.startdate between v_current.startdate 
                 and v_current.enddate + 1 then 
     if rec.enddate > v_current.enddate then 
      v_current.enddate := rec.enddate; 
     end if; 
     else 
     if v_current.emp# is not null then 
      pipe row(v_current); 
     end if; 
     v_current := rec; 
     end if; 
    end loop; 
    pipe row(v_current); 
    end get_ranges; 
end mypackage; 

調用函數:

select * from table(mypackage.get_ranges) where emp# = 1; 
+0

僅當純SQL解決方案不存在或效率低下時,纔會使用PL/SQL。這裏不是這種情況。 (一個關於純粹的SQL解決方案的鏈接提供了一個關係密切的問題,在對原始問題的評論中已經提供了。)自我服務讓我在這裏這麼說,因爲我在較老的線程中提供瞭解決方案 - 但我會說同樣的事情,如果它不是我的解決方案。 – mathguy

+0

是的,我想你是對的。我也會自己使用SQL解決方案。儘管在這裏和那裏有適當的註釋,但PL/SQL可以非常易讀:-) –