在Oracle中的select中集成subselect

时间:2016-02-19 14:30:47

标签: sql oracle date subquery

我需要从表格中取一个日期并计算一个新的日期,这是工作日的增量。目前代码不考虑假期,只是周六和周日。

我是Oracle新手,但仍在努力解决它的工作原理。我需要一些帮助。

我有一个执行日期计算的脚本。在我的例子中,它只是计算从当前日期起15天的日期。这是代码:

SELECT sysdate,
(SELECT dt from
(SELECT dt, RANK() OVER (ORDER BY dt) pos
    FROM (SELECT TRUNC(sysdate) + LEVEL dt,
            CASE
                WHEN TRIM (TO_CHAR (sysdate + LEVEL, 'DAY')) = 'SATURDAY'
                  or TRIM (TO_CHAR (sysdate + LEVEL, 'DAY')) = 'SUNDAY'
                  THEN 0
                  ELSE 1
                END cnt
            FROM DUAL
        CONNECT BY LEVEL < 30)
        WHERE cnt = 1)
    WHERE pos = 15) as myDate
FROM DUAL;

结果是当前日期和未来15个工作日的日期。

然后我尝试将其包含在带有我的表的SELECT中。目标是从表中的每个记录中取出日期并执行计算。这是代码:

SELECT assgn_id, assgn_date,
(SELECT dt from
(SELECT dt, RANK() OVER (ORDER BY dt) pos
    FROM (SELECT TRUNC(assgn_date) + LEVEL dt,
            CASE
                WHEN TRIM (TO_CHAR (assgn_date + LEVEL, 'DAY')) = 'SATURDAY'
                  or TRIM (TO_CHAR (assgn_date + LEVEL, 'DAY')) = 'SUNDAY'
                  THEN 0
                  ELSE 1
                END cnt
            FROM DUAL
        CONNECT BY LEVEL < 30)
        WHERE cnt = 1)
    WHERE pos = 15) as due_date
FROM sales_assgn;

我从中得到的是一个包含sales_assgn表中所有条目的列表。第一个结果行计算了一个很好的due_date。但是,输出上的每个其他条目都与第一行具有相同的due_date。很明显,子查询不会对每条记录执行。

我需要做些什么才能使其正常工作。

2 个答案:

答案 0 :(得分:1)

如果你关心的唯一时间间隔是15天,不包括假期,那么在一个五天工作周内这将是一个21个日历日的差距,除非你从星期六开始(20个日历日间隙到星期五)或星期日(星期五的19个日历日)。因此,生成一个日期列表并通过它们进行计数是不必要的性能影响,可以像这样完成:

select assign_date + CASE when TRIM (TO_CHAR (assign_date , 'DAY')) = 'SATURDAY' then 20
                      when TRIM (TO_CHAR (assign_date , 'DAY')) = 'SUNDAY' then 19
                      else 21 end
from yourtable;

话虽这么说,如果你想要计算日子数量,以及未来的日常灵活性或加入假期表,那么这是有效的。它执行你的cnt计数器的运行总和,并注意我将30个整数的生成器移动到双重的单独查询中,然后交叉连接到源,以便对每个源行应用30计数。:

with dat as (
   select sysdate assign_date from dual union all
   select sysdate+1 assign_date from dual union all
   select sysdate+2 assign_date from dual union all
   select sysdate+3 assign_date from dual )
select assign_date
      , min(future_date) as future_Date
from (        
select assign_date
       ,trunc(assign_date) + lvl future_Date
       ,sum(CASE
            WHEN TRIM (TO_CHAR (assign_date + lvl, 'DAY')) = 'SATURDAY'
              or TRIM (TO_CHAR (assign_date + lvl, 'DAY')) = 'SUNDAY'
            THEN 0
            ELSE 1
        END ) over (partition by assign_date order by lvl) gap 
from dat
cross join  (select level lvl from dual connect by level < 30) seq
)
where gap = 15
group by assign_Date

MIN()和分组依据是必需的,因为对于星期五,未来的星期五,星期六和星期日日历天都是15个工作日。如果你想用RANK()或FIRST_VALUE()替换它,它也可以工作。

如果你想更好地了解它是如何搞清楚的,我在这里为内部查询部分添加了几列,以帮助你掌握它。

with dat as (
   select sysdate assign_date from dual union all
   select sysdate+1 assign_date from dual union all
   select sysdate+2 assign_date from dual union all
   select sysdate+3 assign_date from dual )
select assign_date
       ,trunc(assign_date) + lvl future_Date
       ,lvl
       ,CASE
            WHEN TRIM (TO_CHAR (assign_date + lvl, 'DAY')) = 'SATURDAY'
              or TRIM (TO_CHAR (assign_date + lvl, 'DAY')) = 'SUNDAY'
            THEN 0
            ELSE 1
        END cnt
       ,sum(CASE
            WHEN TRIM (TO_CHAR (assign_date + lvl, 'DAY')) = 'SATURDAY'
              or TRIM (TO_CHAR (assign_date + lvl, 'DAY')) = 'SUNDAY'
            THEN 0
            ELSE 1
        END ) over (partition by assign_date order by lvl) gap 
from dat
cross join  (select level lvl from dual connect by level < 30) seq
order by assign_Date, lvl

这实际上引导我朝着更简单的方式凝聚到正确的未来日而不需要在外部查询中使用MIN()或RANK,因为它将始终是gap = 15和cnt = 1的行,并且重复内部查询中的相同CASE语句更有效。再次证明,几乎总有多种解决方案。

with dat as (
   select sysdate assign_date from dual union all
   select sysdate+1 assign_date from dual union all
   select sysdate+2 assign_date from dual union all
   select sysdate+3 assign_date from dual )
SELECT assign_date
     , future_date
FROM (   
    select assign_date
           ,trunc(assign_date) + lvl future_Date
           ,lvl
           ,CASE
                WHEN TRIM (TO_CHAR (assign_date + lvl, 'DAY')) = 'SATURDAY'
                  or TRIM (TO_CHAR (assign_date + lvl, 'DAY')) = 'SUNDAY'
                THEN 0
                ELSE 1
            END cnt
           ,sum(CASE
                WHEN TRIM (TO_CHAR (assign_date + lvl, 'DAY')) = 'SATURDAY'
                  or TRIM (TO_CHAR (assign_date + lvl, 'DAY')) = 'SUNDAY'
                THEN 0
                ELSE 1
            END ) over (partition by assign_date order by lvl) gap 
    from dat
    cross join  (select level lvl from dual connect by level < 30) seq
    order by assign_Date, lvl 
)
where gap = 15 
and cnt = 1;

答案 1 :(得分:1)

这可能是Oracle中的一个错误,我无法找到任何其他解释 似乎Oracle&#34;优化&#34;子查询并返回所有记录的相同结果。

试试这个查询,它适用于Oracle 12c(我还没有在11.2上测试过它)

SELECT assgn_id, assgn_date,
      ( 
         SELECT dt from  (
            SELECT dt, RANK() OVER (ORDER BY dt) pos
                , s.assgn_date
            FROM (
                SELECT TRUNC(s.assgn_date) + LEVEL dt,
                       CASE WHEN TRIM (TO_CHAR (s.assgn_date + LEVEL, 'DAY','NLS_DATE_LANGUAGE = American')) 
                                 IN ('SATURDAY', 'SUNDAY')
                            THEN 0 ELSE 1
                      END cnt
                FROM DUAL
                CONNECT BY LEVEL < 30
            )
            WHERE cnt = 1
          )
          WHERE pos = 15
      ) as due_date
FROM sales_assgn s;

基本的变化在这里,我已将assgn_date添加到SELECT子句中:

SELECT dt, RANK() OVER (ORDER BY dt) pos
                    , s.assgn_date

我还在CASE表达式中添加了NLS设置,因为如果NLS_DATE_LANGUAGE设置不是英语或美国语,则查询不起作用。

CASE WHEN TRIM (TO_CHAR (s.assgn_date + LEVEL, 'DAY','NLS_DATE_LANGUAGE = American')) 
          IN ('SATURDAY', 'SUNDAY')
     THEN 0 ELSE 1
     END cnt
相关问题