【问题标题】:Remove Nulls from multiple rows; create [Start] and [End] columns从多行中删除空值;创建 [开始] 和 [结束] 列
【发布时间】:2016-11-16 14:23:21
【问题描述】:

我正在尝试创建一个表,该表按周包含开始列和结束列,不重叠月份转换。以 2016 年 1 月为例,我希望结果如下所示:

 Start      End
 1/1/2016   1/2/2016
 1/3/2016   1/9/2016
 1/10/2016  1/16/2016
 1/17/2016  1/23/2016
 1/24/2016  1/30/2016
 1/31/2016  1/31/2016  

我目前得到的查询是(我希望第 2 列和第 3 列中的记录相应地排列):

 DATES          Wk_START_END    MONTH_START_END
 1/1/2016                       1/1/2016
 1/2/2016       1/2/2016    
 1/3/2016       1/3/2016    
 1/4/2016       
 1/5/2016       
 1/6/2016       
 1/7/2016       
 1/8/2016       
 1/9/2016       1/9/2016    
 1/10/2016      1/10/2016   
 1/11/2016      
 1/12/2016      
 1/13/2016      
 1/14/2016      
 1/15/2016      
 1/16/2016      1/16/2016   
 1/17/2016      1/17/2016   
 1/18/2016      
 1/19/2016      
 1/20/2016      
 1/21/2016      
 1/22/2016      
 1/23/2016      1/23/2016   
 1/24/2016      1/24/2016   
 1/25/2016      
 1/26/2016      
 1/27/2016      
 1/28/2016      
 1/29/2016      
 1/30/2016      1/30/2016   
 1/31/2016      1/31/2016       1/31/2016

这是目前的查询:

    SELECT trunc
      (sysdate, 'YEAR')+rownum-1 DATES
      --,to_char(trunc(sysdate,'YEAR') + rownum -1 ,'D') Day_Of_Wk

    , CASE
         WHEN to_char
             (trunc
             (sysdate, 'YEAR')+rownum-1, 'D') = '1' THEN trunc
                                                (sysdate, 'YEAR')+rownum-1
         WHEN to_char
             (trunc
             (sysdate, 'YEAR')+rownum-1, 'D') = '7' THEN trunc
                                                (sysdate, 'YEAR')+rownum-1
         ELSE NULL
      END Wk_Start_End
    , CASE
         WHEN trunc
             (sysdate, 'YEAR')+rownum-1 = TRUNC
             (trunc
             (sysdate, 'YEAR')+rownum-1, 'MONTH') THEN trunc
                                               (sysdate, 'YEAR')+rownum-1
         WHEN trunc
             (sysdate, 'YEAR')+rownum-1 = Add_months
             (TRUNC
             (trunc
             (sysdate, 'YEAR')+rownum-1, 'MONTH'), 1)-1 THEN trunc
                                                    (sysdate, 'YEAR')+rownum-1
      END Month_Start_end
FROM   all_objects
WHERE  trunc
     (sysdate, 'YEAR')+rownum <= Add_months
     (trunc
     (sysdate, 'YEAR'), 12)-1;

感谢任何帮助。谢谢!

【问题讨论】:

    标签: sql oracle case truncate rownum


    【解决方案1】:

    下面的查询从头开始 - 它不使用您的任何代码(或其输出)。年份和月份在第一个 CTE 中硬编码(顶部 WITH 子句中的子查询);更有可能在您的应用程序中排除第一个名为 inputs 的 CTE,并将 ym 设置为 first_date 定义中的绑定变量(也在 WITH 子句中)。

    我使用了您的约定:一周从“一周的第 1 天”(在美国是星期日)开始,到“一周的第 7 天”结束。如果需要,可以通过 NLS 参数进行调整。

    with 
         inputs ( y, m ) as (
           select 2016, 1 from dual
         ),
         first_date ( f_dt ) as (
           select to_date(to_char(y, '0009') || '-' || to_char(m, '09'), 'yyyy-mm') 
           from   inputs
         ),
         mth_dates ( dt ) as (
           select f_dt + level - 1 from first_date
           connect by level <= last_day(f_dt) - f_dt + 1
         ),
         start_dates ( dt, rn ) as (
           select dt, row_number() over (order by dt)
           from   ( select dt from mth_dates where to_char(dt, 'd') = '1'
                    union
                    select min(dt) from mth_dates )
         ),
         end_dates ( dt, rn ) as (
           select dt, row_number() over (order by dt)
           from   ( select dt from mth_dates where to_char(dt, 'd') = '7'
                    union
                    select max(dt) from mth_dates )
         )
    select s.rn as week_nbr, s.dt as start_date, e.dt as end_date
    from   start_dates s inner join end_dates e   on s.rn = e.rn;
    
      WEEK_NBR START_DATE  END_DATE
    ---------- ---------- ----------
             1 2016-01-01 2016-01-02
             2 2016-01-03 2016-01-09
             3 2016-01-10 2016-01-16
             4 2016-01-17 2016-01-23
             5 2016-01-24 2016-01-30
             6 2016-01-31 2016-01-31
    

    应 OP 的要求添加

    要生成全年的开始和结束日期,可以使用下面的查询。

    with 
         inputs ( y ) as (
           select 2016 from dual
         ),
         first_date ( f_dt ) as (
           select to_date(to_char(y, '0009') || '-01-01', 'yyyy-mm-dd') 
           from   inputs
         ),
         year_dates ( dt ) as (
           select f_dt + level - 1 from first_date
           connect by level <= add_months(f_dt, 12) - f_dt
         ),
         start_dates ( dt, rn ) as (
           select dt, row_number() over (order by dt)
           from   ( select dt from year_dates where to_char(dt, 'd') = '1'
                                                 or extract(day from dt) = 1 )
         ),
         end_dates ( dt, rn ) as (
           select dt, row_number() over (order by dt)
           from   ( select dt from year_dates where to_char(dt, 'd') = '7'
                                                 or extract(day from dt + 1) = 1 )
         )
    select s.dt as start_date, e.dt as end_date
    from   start_dates s inner join end_dates e   on s.rn = e.rn;
    

    进一步评论:我实际上比我更喜欢 Matthew 的回答。他的解决方案只是将日期分组到适当的“集合交点”(月和周)中,并在这些组上使用max()min(),从而避免了加入的需要。这是一个比我更好的解决方案。

    为了完整起见,我在下面复制了 Matthew 的解决方案,并进行了一些小改动。

    • 首先,为了满足要求(正如 Matthew 建议的那样),我在 dte 中添加 1 以按周形成组,因此周从星期日开始,到星期六结束。

    • 其次,正如我在对 Matthew's Answer 的评论中所建议的,我直接使用“月”和“周”来组成组;不需要dense_rank()

    • 第三,为了符合良好的编码习惯,我在第一个 CTE 中为 to_date() 添加了一个明确的日期格式模型。

    学分:@Matthew McPeak

    with dtes ( dte ) as (
             select to_date ('01-Jan-2016', 'dd-Mon-yyyy') + rownum - 1
             from   dual
             connect by rownum <= 366   -- 2016 is a leap year
         ),
         dtes_grouped_by_month_week ( dte, mth, wk ) as (
             select dte, to_char(dte, 'mm'), to_char(dte+1, 'iw')
             from   dtes
         )
    select   min(dte) start_date, max(dte) end_date
    from     dtes_grouped_by_month_week
    group by mth, wk
    order by start_date;
    

    【讨论】:

    • 虽然每个答案都非常有帮助,但这个答案用激光精确度来解决。谢谢数学家!因此,如果我希望该列表持续一整年,我将如何重构前三个 CTE? (作为一名 BI 分析师,我还没有遇到过使用 WITH 子句的需要,我真的很喜欢你在这里所做的)。
    • 我将此添加到我的原始答案中。
    • 太棒了!谢谢!
    • 实际上,我比我更喜欢 Matthew 的解决方案。我会将解决方案添加到我的帖子中,并进行一些小的修改。
    • 我想我从这里的解决方案中可以更好地理解 CTE,并且可以看到为什么您喜欢 Matt 的,因为它看起来更精简?
    【解决方案2】:

    嗯。您似乎想要带有值的奇数行以及下一个非空值。我在想:

    select dte as start_date, next_dte and end_date
    from (select ao.*, rownum as seqnum, lead(dte) over (order by dates) as next_dte
          from ((select dates, Wk_START_END as dte from all_objects ao) union all
                (select dates, MONTH_START_END as dte from all_objects ao)
               ) t
          where dte is not null
         ) t
    where mod(seqnum, 2) = 1;
    

    【讨论】:

      【解决方案3】:

      这是一个查询所有 2016 年的日期。我希望你可以根据自己的目的推断它。

      基本方法是获取感兴趣范围内的所有日期(例如,2016 年全年),然后按月/周对它们进行分组。然后获取每个组中的min()max() 日期。

      WITH dtes AS
             (SELECT TO_DATE ('01-JAN-2016') + ROWNUM - 1 dte
              FROM   DUAL
              CONNECT BY ROWNUM <= 365),
           dtes_grouped_by_month_week AS
             (SELECT dte,
                     DENSE_RANK () OVER (PARTITION BY NULL ORDER BY TO_CHAR (dte, 'IYYY-MM'), TO_CHAR (dte, 'IW')) month_week
              FROM   dtes)
      SELECT MIN (dte) start_date,
             MAX (dte) end_date
      FROM   dtes_grouped_by_month_week
      GROUP BY month_week
      ORDER BY month_week;
      

      更新:一周的第一天

      看看你的例子,你似乎希望星期天是一周的第一天,而不是星期一。这很容易在查询中进行调整——只需在DENSE_RANK 中的TO_CHAR 函数中的日期加上+1。

      【讨论】:

      • 这里不需要DENSE_RANK()。相反,在dtes_grouped_... 中,您可以使用SELECT dte, mth, wk(像您一样使用to_char() 提取月份和星期);然后在外部查询GROUP BY mth, wkORDER BY start_date