【问题标题】:ORACLE - Expand Date range based on input date range providedORACLE - 根据提供的输入日期范围扩展日期范围
【发布时间】:2019-10-07 07:30:37
【问题描述】:

我在一个表格(即 TAB1)中有多个日期范围,如下所示。

START_DATE  |  END_DATE
-------------------------
1-Jan-2004  |  31-Dec-2005
1-Jan-2001  |  31-Dec-2001
1-Jan-2011  |  31-Dec-2015

现在基于输入日期范围,假设它与表中至少 1 行重叠,我想更改现有行的日期。

示例 1: 如果输入日期范围是 1-Jan-2012 | 2017 年 12 月 31 日,然后第 3 行应该是 2011 年 1 月 1 日 | 2017 年 12 月 31 日。其余行应保持不变。

示例 2: 如果输入日期范围是 1-Jan-2007 | 2012 年 12 月 31 日,然后第 3 行应该是 2007 年 1 月 1 日 | 2015 年 12 月 31 日。其余行应保持不变。

示例 3: 如果输入日期范围是 1-Jan-2009 | 2017 年 12 月 31 日,然后第 3 行应该是 2009 年 1 月 1 日 | 2017 年 12 月 31 日。该行的其余部分应保持不变。

示例 4: 如果输入日期范围是 1-Jan-2005 | 2012 年 12 月 31 日,然后第 3 行应该是 2006 年 1 月 1 日 | 2015 年 12 月 31 日。该行的其余部分应保持不变。

示例 5: 如果输入日期范围是 1-Jan-2003 | 2003 年 12 月 31 日,那么第 1 行应该是 2003 年 1 月 1 日 | 2005 年 12 月 31 日。该行的其余部分应保持不变。

对上面的 SQL 查询有什么建议吗?

【问题讨论】:

  • "示例 4:如果输入日期范围是 2005 年 1 月 1 日 | 2012 年 12 月 31 日,那么第 3 行应该是 2006 年 1 月 1 日" - 不应该是 1 月-2005 年?
  • 不,第 1 行已涵盖 2004 年 1 月 1 日 | 2005 年 12 月 31 日。因此应输出以下任一选项: OPTION-1: Row-1 应更改为 1-Jan-2004 | 2010 年 12 月 31 日,其余行未更改或 OPTION-2:第 3 行应为 2006 年 1 月 1 日 | 2015 年 12 月 31 日。该行的其余部分应保持不变。
  • "示例 5:如果输入日期范围是 2003 年 1 月 1 日 | 2003 年 12 月 31 日,那么第 2 行应该是 2003 年 1 月 1 日 | 2005 年 12 月 31 日" 为什么选择第 2 行?
  • 您的规则需要明确的解释。请编辑问题。
  • @Serg 我确定他指的是“示例 5”中的第 1 行而不是第 2 行。

标签: sql oracle date datetime


【解决方案1】:

它不漂亮,但可能有用


with tbl1 as
(SELECT '1-Jan-2004 | 31-Dec-2005' int FROM dual
UNION ALL
SELECT '1-Jan-2001 | 31-Dec-2001' FROM dual
UNION ALL
SELECT '1-Jan-2011 | 31-Dec-2015' FROM dual),
tbl2 as
(SELECT '1-Jan-2004 | 31-Dec-2005' int FROM dual
UNION ALL
SELECT '1-Jan-2001 | 31-Dec-2001' FROM dual
UNION ALL
SELECT '1-Jan-2007 | 31-Dec-2015' FROM dual)
SELECT * FROM 
(SELECT TO_DATE(TRIM(TRIM( '|' FROM REGEXP_SUBSTR(int, '.+[0-9] \|'))), 'dd-Mon-yyyy', 'NLS_DATE_LANGUAGE=AMERICAN') str,
TO_DATE(TRIM(TRIM( '|' FROM REGEXP_SUBSTR(int, '\|.+'))), 'dd-Mon-yyyy', 'NLS_DATE_LANGUAGE=AMERICAN') end 
FROM tbl1) a,
(SELECT TO_DATE(TRIM(TRIM( '|' FROM REGEXP_SUBSTR(int, '.+[0-9] \|'))), 'dd-Mon-yyyy', 'NLS_DATE_LANGUAGE=AMERICAN') str,
TO_DATE(TRIM(TRIM( '|' FROM REGEXP_SUBSTR(int, '\|.+'))), 'dd-Mon-yyyy', 'NLS_DATE_LANGUAGE=AMERICAN') end
FROM tbl2) b
where a.str >= b.str 
and a.end <= b.end

输出:

01.01.2011  31.12.2015  01.01.2007  31.12.2015
01.01.2004  31.12.2005  01.01.2004  31.12.2005
01.01.2001  31.12.2001  01.01.2001  31.12.2001

【讨论】:

    【解决方案2】:

    这是您可以使用的select

    -- First your sample-data:
    WITH tab1(rn, start_date, end_date) AS 
      (SELECT 1, DATE'2004-01-01', DATE'2005-12-31' FROM dual UNION ALL 
       SELECT 2, DATE'2001-01-01', DATE'2001-12-31' FROM dual UNION ALL 
       SELECT 3, DATE'2011-01-01', DATE'2015-12-31' FROM dual)
    -- Then the different inputs:
       --, INP(start_date, end_date) AS (SELECT  DATE'2012-01-01', DATE'2017-12-31' FROM dual)
       --, INP(start_date, end_date) AS (SELECT  DATE'2007-01-01', DATE'2012-12-31' FROM dual)
       --, INP(start_date, end_date) AS (SELECT  DATE'2009-01-01', DATE'2017-12-31' FROM dual)
       --, INP(start_date, end_date) AS (SELECT  DATE'2005-01-01', DATE'2012-12-31' FROM dual)
       , INP(start_date, end_date) AS (SELECT  DATE'2003-01-01', DATE'2003-12-31' FROM dual)
    -- Get all fitting rows - this is enough for example 1,2,3,5:
       , fit AS (SELECT tab1.rn
                      , LEAST(inp.start_date, tab1.start_date) start_date
                      , GREATEST(inp.end_date, tab1.end_date) end_date
                      , tab1.end_date prev_end
                      , ROW_number() over(ORDER BY tab1.end_date DESC) rw
                   FROM tab1
                   JOIN inp
                     ON inp.end_date + 1 >= tab1.start_date
                    AND inp.start_date - 1 <= tab1.end_date)
    -- If there is a second fitting row adjust start_date:
    SELECT fit.rn
         , CASE WHEN prev.prev_end IS NULL THEN fit.start_date
                ELSE GREATEST(prev.prev_end + 1, fit.start_date)
           END start_date
         , fit.end_date
      FROM fit
      LEFT JOIN fit prev 
        ON prev.rw = fit.rw + 1
     WHERE fit.rw = 1
    

    然后您可以在UPDATEMERGE 中使用此语句根据需要更改行。

    【讨论】:

      【解决方案3】:

      您可以尝试简单的case when 来实现:

      SQL> WITH tab1(start_date, end_date) AS
        2    (SELECT DATE'2004-01-01', DATE'2005-12-31' FROM dual UNION ALL
        3     SELECT DATE'2001-01-01', DATE'2001-12-31' FROM dual UNION ALL
        4     SELECT DATE'2011-01-01', DATE'2015-12-31' FROM dual)
        5  SELECT -- original query starts from here
        6      CASE
        7          WHEN DATE '&&START_DATE' - 1 BETWEEN T1.START_DATE AND T1.END_DATE
        8               OR DATE '&&END_DATE' + 1 BETWEEN T1.START_DATE AND T1.END_DATE
        9          THEN LEAST(T1.START_DATE, DATE '&&START_DATE')
       10          ELSE T1.START_DATE
       11      END AS START_DATE,
       12      CASE
       13          WHEN DATE '&&START_DATE' - 1 BETWEEN T1.START_DATE AND T1.END_DATE
       14               OR DATE '&&END_DATE' + 1 BETWEEN T1.START_DATE AND T1.END_DATE
       15          THEN GREATEST(T1.END_DATE, DATE '&&END_DATE')
       16          ELSE T1.END_DATE
       17      END AS END_DATE
       18  FROM
       19      TAB1 T1; -- example 2
      Enter value for start_date: 2007-01-01
      Enter value for end_date: 2012-12-31
      
      START_DATE  END_DATE
      ----------- -----------
      01-jan-2004 31-dec-2005
      01-jan-2001 31-dec-2001
      01-jan-2007 31-dec-2015
      
      SQL>
      

      干杯!!

      【讨论】:

        【解决方案4】:

        据我了解的规则

        with 
        -- test data
        t1 (id, START_DATE, END_DATE) as
        ( 
        SELECT 1,  to_date('1-Jan-2004'), to_date('31-Dec-2005')  FROM DUAL UNION ALL
        SELECT 2,  to_date('1-Jan-2001'), to_date('31-Dec-2001')  FROM DUAL UNION ALL
        SELECT 3,  to_date('1-Jan-2011'), to_date('31-Dec-2015')  FROM DUAL 
        )
        -- test inputs
        , inp (id, START_DATE, END_DATE) as
        ( 
        SELECT 1,  to_date('1-Jan-2012'), to_date('31-Dec-2017')  FROM DUAL UNION ALL
        SELECT 2,  to_date('1-Jan-2007'), to_date('31-Dec-2012')  FROM DUAL UNION ALL
        SELECT 3,  to_date('1-Jan-2009'), to_date('31-Dec-2017')  FROM DUAL UNION ALL
        SELECT 4,  to_date('1-Jan-2005'), to_date('31-Dec-2012')  FROM DUAL UNION ALL
        SELECT 5,  to_date('1-Jan-2003'), to_date('31-Dec-2003')  FROM DUAL 
        )
        --  find matches and extend the original interval
        , extended as (
        SELECT inp.id inp_id, t1.id id, least(t1.START_DATE, inp.START_DATE) START_DATE, greatest(t1.END_DATE, inp.END_DATE) END_DATE
        , row_number() over (partition by inp.id order by least(t1.START_DATE, inp.START_DATE)) rn
        FROM t1
        join inp on inp.START_DATE <= t1.END_DATE+1 and t1.START_DATE <= inp.END_DATE +1
        )
        -- select the only (rn=1) original data row the input is applied to, find and exclude intersections with other original data rows
        , truncated as 
        (select e.inp_Id, e.id
         , case when t1.id is not null then 
              case when e.START_DATE < t1.START_DATE then e.START_DATE else t1.END_DATE + 1 end
           else e.START_DATE end START_DATE
         , case when t1.id is not null then 
              case when e.START_DATE < t1.START_DATE then t1.START_DATE - 1 else e.END_DATE end
           else e.END_DATE end END_DATE
        from extended e 
        left join t1 on e.id != t1.id  and e.START_DATE <= t1.END_DATE and t1.START_DATE <= e.END_DATE 
        where e.rn=1
        )
        -- see the results after the input is applied
        select t1.id, e2.inp_id, coalesce(e2.START_DATE, t1.START_DATE) START_DATE, coalesce(e2.END_DATE,t1.END_DATE) END_DATE
        from t1 
        left join truncated e2 on t1.id = e2.id
        order by e2.inp_id, t1.id;
        

        返回

        INP_ID  ID  START_DATE  END_DATE
        1   3   01-JAN-11   31-DEC-17
        2   3   01-JAN-07   31-DEC-15
        3   3   01-JAN-09   31-DEC-17
        4   1   01-JAN-04   31-DEC-10
        5   1   01-JAN-03   31-DEC-05
            2   01-JAN-01   31-DEC-01
        

        所以输入 1,2 和 3 更新第 3 行,输入 4 和 5 更新第 1 行,第 2 行永远不会更新。

        【讨论】:

          猜你喜欢
          • 2022-01-13
          • 1970-01-01
          • 1970-01-01
          • 2014-01-07
          • 1970-01-01
          • 1970-01-01
          • 2016-10-25
          • 2019-10-30
          • 1970-01-01
          相关资源
          最近更新 更多