【问题标题】:Integrating a subselect in a select in Oracle在 Oracle 的选择中集成子选择
【发布时间】:2016-02-19 14:30:47
【问题描述】:

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

我是 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 计算。但是,输出中的每个其他条目都具有与第一行相同的到期日期。所以显然子查询并没有对每条记录执行。

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

【问题讨论】:

  • 我怀疑您的查询是否有效。因为 assgn_date 不能用于内部查询。您确定您在此处粘贴了正确的查询吗?
  • 我很惊讶它竟然运行了,因为你访问了assgn_date 这么多嵌套级别。您使用的是哪个版本的 Oracle?
  • 我使用的是 11g。它肯定像描述的那样工作。我希望能够用一个函数来做到这一点,但我们不允许添加函数。我尝试将内部级别的 DUAL 更改为 sales_assgn 表,其中将外部级别 id 链接到内部级别。但它只是坐在那里旋转。
  • 我知道这是非常低效的。目前,实现一些新功能是一次性的努力。正在运行 sql 以初始化到期日期。将来它将通过应用程序进行维护。我需要从 SQL 执行到期日期的加载,而不是使用 Java 开发加载。所以这整个练习不是我的选择。这是一个了解 Oracle 的机会。这是唯一的好处。
  • 如果您的查询确实有效,那么试试这个。您缺少“partition by”,(SELECT dt, RANK() OVER (partition by assgn_date ORDER BY dt) pos FROM (SELECT assgn_date, TRUNC(assgn_date) + LEVEL dt,

标签: sql oracle date subquery


【解决方案1】:

这可能是 Oracle 中的错误,我找不到任何其他解释。
Oracle 似乎“优化”了子查询并为所有记录返回相同的结果。

试试这个查询,它适用于 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;

本质的变化就在这里,我在 SELECT 子句中添加了assgn_date

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

【讨论】:

  • 是的,解决了它。我唯一能想到的是 select 的签名不包括源表中的任何内容。通过将 assgn_dte 添加到第二级子查询,Oracle 选择不对其进行优化。谢谢你。我现在有 2 个可行的解决方案。实际上,这个解决方案的工作速度比我想象的要快得多。非常非常感谢!!!
【解决方案2】:

如果您关心的唯一间隔是除节假日之外的 15 天,那么在每周工作 5 天的情况下,除非您从星期六开始,否则这将始终是 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 个整数的生成器从 dual 移动到单独的查询中,然后将其交叉连接到源,以便对每个源行应用 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() 和 group by 是必需的,因为对于星期五,未来的星期五、星期六和星期日日历日都是未来 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;

【讨论】:

  • 谢谢。我需要一段时间来检查它并确保我理解它在做什么。感谢大家的cmets和帮助。
  • 添加了另一个仅包含内部位的查询以及一些附加列,以帮助您了解它的工作原理。干杯,
  • 令人惊讶的是,不同的观点可以产生完全不同的结果!您建议只使用标准的 21 天增量,除非开始日期是在周末,否则这太棒了。在这一点上,没有任何关于考虑假期的说法。所以我不是。我将使用您的第一个解决方案。如果出现假期问题,那么我将使用您的第二个解决方案。我真的很感激帮助。谢谢!!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-29
  • 2018-09-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多