2016-08-08 61 views
0

我有一個非常大的數據集,其中包含人員以及他們保險的開始和結束日期。每個人可以有多個重疊日期的記錄。我需要爲每個人找到每個「島」的開始和結束。PL/SQL:在由開始和結束定義的重疊日期範圍中查找島嶼

例如:

SKP_PERSON DATE_INSURANCE_START DATE_INSURANCE_END SKP_INSURANCE 
1 1   7.11.2015    1.1.3000    1 
2 1   7.11.2015    1.1.3000    2 
3 2   10.4.2015    1.8.2016 23:59:59 3 
4 3   28.3.2016    1.1.3000    4 
5 4   5.12.2015    31.12.2015 23:59:59 5 
6 4   5.12.2015    1.5.2016 23:59:59 6 
7 4   1.2.2016    1.5.2016 23:59:59 7 
8 5   15.1.2016    2.3.2016 23:59:59 8 
9 5   15.3.2016    2.6.2016 23:59:59 9 

結果我需要的是這樣的:

SKP_PERSON DATE_INSURANCE_START DATE_INSURANCE_END 
1 1   7.11.2015    1.1.3000    
2 2   10.4.2015    1.8.2016 23:59:59  
3 3   28.3.2016    1.1.3000    
4 4   5.12.2015    1.5.2016 23:59:59  
5 5   15.1.2016    2.3.2016 23:59:59  
6 5   15.3.2016    2.6.2016 23:59:59  

我設法找到通過連接所有可能的時間(從分鐘(開始溶液)最大值(結束)),併爲每一天找到滯後和主導值 - 但是記錄太多,日期範圍太大,因此需要很長時間。有沒有更有效的解決方案使用PL/SQL?

編輯: 我試過(簡體)查詢:

WITH table1 AS (
SELECT d.dtime_day, COUNT(i.dkp_insurance), i.skp_person 
FROM date d --a date table, contains a record for every day 
JOIN insurance i ON d.dtime_day BETWEEN i.DATE_INSURANCE_START AND i.DATE_INSURANCE_END  
GROUP BY d.dtime_day, i.skp_person 
) 
SELECT * FROM 
(
SELECT distinct skp_person, 
CASE WHEN LAG(dtime_day) OVER (PARTITION BY skp_person ORDER BY dtime_day) <> dtime_day -1 THEN dtime_day END AS start, 
CASE WHEN LEAD(dtime_day) OVER (PARTITION BY skp_person ORDER BY dtime_day) <> dtime_day +1 THEN dtime_day END AS end 
FROM table1 t1) 
WHERE start IS NOT NULL OR end IS NOT NULL 
ORDER BY skp_person 
; 
+0

你能提供你已經試過SQL?另外,這些表上是否有索引?謝謝。 – Nick

+0

我不太清楚我在使用索引的數據倉庫中的表格 - SKP_INSURANCE是主鍵。我將在編輯中添加代碼。 – vergis

+0

幾天前我回答過類似的問題,請看看。從你寫的代碼中我可以看出你可以處理SQL,你所需要的只是算法的想法(解決問題的方法);如果你認爲你可以使用我的解決方案,但你需要進一步的幫助,請說出來。祝你好運! http://stackoverflow.com/questions/36387048/get-envelope-ie-overlapping-time-spans/36408651#36408651 – mathguy

回答

0

我改編了我以前的解決方案以適應這種情況(請參閱原始問題的評論)。需要愚蠢的+1/86400(增加一秒)來處理表中奇怪的結束日期/時間。

with 
    inputs (skp_person, date_insurance_start, date_insurance_end) as (
     select 1, to_date('7.11.2015', 'dd.mm.yyyy'), to_date('1.1.3000'   , 'dd.mm.yyyy')    from dual union all 
     select 1, to_date('7.11.2015', 'dd.mm.yyyy'), to_date('1.1.3000'   , 'dd.mm.yyyy')    from dual union all 
     select 2, to_date('10.4.2015', 'dd.mm.yyyy'), to_date('1.8.2016 23:59:59' , 'dd.mm.yyyy hh24:mi:ss') from dual union all 
     select 3, to_date('28.3.2016', 'dd.mm.yyyy'), to_date('1.1.3000'   , 'dd.mm.yyyy')    from dual union all 
     select 4, to_date('5.12.2015', 'dd.mm.yyyy'), to_date('31.12.2015 23:59:59', 'dd.mm.yyyy hh24:mi:ss') from dual union all 
     select 4, to_date('5.12.2015', 'dd.mm.yyyy'), to_date('1.5.2016 23:59:59' , 'dd.mm.yyyy hh24:mi:ss') from dual union all 
     select 4, to_date('1.2.2016' , 'dd.mm.yyyy'), to_date('1.5.2016 23:59:59' , 'dd.mm.yyyy hh24:mi:ss') from dual union all 
     select 5, to_date('15.1.2016', 'dd.mm.yyyy'), to_date('2.3.2016 23:59:59' , 'dd.mm.yyyy hh24:mi:ss') from dual union all 
     select 5, to_date('15.3.2016', 'dd.mm.yyyy'), to_date('2.6.2016 23:59:59' , 'dd.mm.yyyy hh24:mi:ss') from dual 
    ), 
    m (skp_person, date_insurance_start, m_date) as (
     select skp_person, date_insurance_start, 
       max(date_insurance_end + 1/86400) 
       over (partition by skp_person order by date_insurance_start 
        rows between unbounded preceding and 1 preceding) 
     from inputs 
     union all 
     select skp_person, null, max(date_insurance_end + 1/86400) 
     from inputs 
     group by skp_person 
    ), 
    f (skp_person, date_insurance_start, e_date) as (
     select skp_person, date_insurance_start, 
       lead(m_date) over 
       (partition by skp_person order by date_insurance_start) 
     from m 
     where date_insurance_start > m_date 
      or date_insurance_start is null or m_date is null 
    ) 
select skp_person, date_insurance_start, e_date - 1/86400 as date_insurance_end 
from f where date_insurance_start is not null 
; 

輸出:(使用我NLS_DATE_FORMAT設置)

SKP_PERSON DATE_INSURANCE_STAR DATE_INSURANCE_END 
---------- ------------------- ------------------- 
     1 07.11.2015 00:00:00 01.01.3000 00:00:00 
     2 10.04.2015 00:00:00 01.08.2016 23:59:59 
     3 28.03.2016 00:00:00 01.01.3000 00:00:00 
     4 05.12.2015 00:00:00 01.05.2016 23:59:59 
     5 15.01.2016 00:00:00 02.03.2016 23:59:59 
     5 15.03.2016 00:00:00 02.06.2016 23:59:59 
+0

非常感謝,結果看起來正確。評論的解決方案几乎奏效,它只是有時不會將連續的日期開始和結束的羣組連接起來。 – vergis

+0

是的 - 舊的解決方案需要適應奇數的結束日期,這就是我在這裏發佈的解決方案中所做的。祝你好運! – mathguy

0

這裏的理念是:

  • 使用lag()或一些其他方法來確定當一個島嶼開始
  • 構建當島開始時爲1的標誌
  • 運行累積和
  • Reaggregate

結果查詢是這樣的:

select skp_person, 
     min(date_insurance_start) as date_insurance_start, 
     min(date_insurance_end) as date_insurance_end 
from (select t.*, 
      sum(isIslandFlag) over (partition by skp_person order by date_insurance_start) as grp 
     from (select t.*, 
        (case when exists (select 1 
             from t t2 
             where t2.skp_person = t.skp_person and 
              t2.date_insurance_start between t.date_insurance_start and t.date_insurance_end 
            ) 
         then 0 else 1 
        end) as IsIslandFlag 
      from t 
      ) t 
    ) t 
group by skp_person, grp; 

注意:此方法不是傻瓜證明,但它在大多數現實世界的情況下工作。例如,如果您有多個策略在同一天開始,那麼它需要稍微調整一下。

+0

我將不得不正確地查看查詢,但它不能正常工作。該示例中的SKP_PERSON 4將返回2015年5月5日和2015年12月31日。無論如何,如果沒有其他,這是一個靈感。 – vergis

+0

您的值是以日期還是字符串存儲的? –

相關問題