【问题标题】:Can can someone explain me the query in the function有人可以解释一下函数中的查询吗
【发布时间】:2015-09-10 18:53:26
【问题描述】:
CREATE OR REPLACE FUNCTION exlude_weekends (p_date_start    DATE,
                                         p_date_end      DATE)
RETURN NUMBER
AS
  l_no_of_days   NUMBER := NULL;
BEGIN
  SELECT   COUNT ( * )  INTO   l_no_of_days
 FROM   (SELECT date_extraction, TO_CHAR (date_extraction, 'DAY')
            FROM (SELECT TO_DATE(p_date_start,'DD-MON-RRRR')
                                 + LEVEL - 1 date_extraction FROM   DUAL CONNECT BY   LEVEL <
                                    (TO_DATE (p_date_end, 'DD-MON-RRRR')- TO_DATE (p_date_start,'DD-MON-RRRR'))+ 2)
            WHERE   TRIM (TO_CHAR (date_extraction, 'DAY')) NOT IN     ('SATURDAY', 'SUNDAY'));

RETURN l_no_of_days;
EXCEPTION
   WHEN OTHERS
  THEN
     RETURN 0;
END exlude_weekends;

【问题讨论】:

  • 当然。它返回开始和结束之间的天数,不包括周末。
  • @GolezTrol,不,解释不被接受;你应该逐行解释......大声笑:)
  • @Rahul 我意识到我只是随口问了一个问题。你能帮我理解“级别”,特别是“+ LEVEL - 1 date_extraction”这一行
  • 上下文?如果您只想询问查询,请自行显示并最好格式化。你想达到什么目的?
  • 它使用分层查询生成行。进一步阅读:orafaq.com/wiki/…docs.oracle.com/cd/B19306_01/server.102/b14200/…

标签: oracle function plsql


【解决方案1】:

正如你在 cmets 中提到的,函数的关键是分层子查询:

SELECT TO_DATE(p_date_start,'DD-MON-RRRR') + LEVEL - 1 date_extraction
  FROM DUAL 
  CONNECT BY LEVEL < 
             (TO_DATE(p_date_end,'DD-MON-RRRR')-
              TO_DATE(p_date_start,'DD-MON-RRRR'))+ 2

分层查询尝试遍历树(CONNECT BY 子句指定父母和孩子的关系)。在这个例子中,我们发现了 connect by 的一个棘手的使用(或滥用)。

此子查询生成从 p_date_start 到 p_date_end(包括两者)的日期。它是怎么做到的?

  1. 请注意,在 CONNECT BY 中与 LEVEL 比较的表达式是一个常数,它是从开始日期到结束日期之后的天数(为什么是结束日期之后的一天?因为它使用

    (TO_DATE(p_date_end,'DD-MON-RRRR')-TO_DATE(p_date_start,'DD-MON-RRRR'))+2

  2. select 获取 DUAL 行(它只有一行),该行的 LEVEL 为 1(分层查询使用伪列 LEVEL 来指示从根开始计算的深度)。

  3. CONNECT BY 检查此级别 (1) 是否在要生成的天数范围内。
  4. 计算表达式:

    TO_DATE(p_date_start,'DD-MON-RRRR') + LEVEL - 1

    这是开始日期加上等级减一:这是开始日期。

  5. 现在分层评估中的新循环开始:再次评估在前一个循环(开始日期)中生成的行(新行将具有级别 2)。
  6. 如果它在要生成的天数范围内(由 CONNECT BY 子句控制),则会生成一个新日期(开始日期之后的第二天)。
  7. 一个新的循环开始(3级)....
  8. 并且该过程迭代直到 LEVEL 大于要生成的天数(这与从开始日期到结束日期迭代所需的级别数相同)。

函数中的外部查询只过滤 SATURDAYS 和 SUNDAYS 并计算剩余天数。

虽然 oracle 对这个查询的评估非常有效,但这个函数使用了蛮力解决方案。

可以使用更优雅的数学解决方案(无需迭代)。我们有一个公式可以计算两个日期之间特定星期的数量:

TRUNC(( END – START – DAYOFWEEK(END-DAYOFWEEKTOBECOUNTED) + 8) / 7)

其中 DAYOFWEEK 是一个返回 0-6 的函数(0 星期日,1 星期一 ... 6 星期六)。 DAYOFWEEKTOBECOUNTED 是以相同格式计算的天数。

注意 TO_CHAR(date, 'd') 以 1..7 格式返回星期几,我们必须纠正为 0..6 格式(在我的地区,星期一是一周的第一天,所以我得到星期天0 和星期六为 6,mod 函数如下):

MOD(TO_NUMBER(TO_CHAR(p_date_end, 'd')), 7)

最后,我们想要间隔中的天数减去星期日(第 0 天)和星期六(第 6 天)的数量。因此,使用数学方法的最终过程将是:

CREATE OR REPLACE FUNCTION exlude_weekends (p_date_start    DATE,
                                            p_date_end      DATE)
RETURN NUMBER
AS
  l_no_of_days NUMBER := NULL;
BEGIN
  SELECT TRUNC(p_date_end - p_date_start) + 1 - 
      (   TRUNC((p_date_end - p_date_start - 
             MOD(to_number(to_char(p_date_end - 0, 'd')), 7)+8)/7)
        + TRUNC((p_date_end - p_date_start - 
             MOD(to_number(to_char(p_date_end - 6, 'd')), 7)+8)/7)
      ) 
    INTO l_no_of_days
    FROM DUAL;
  RETURN l_no_of_days;
EXCEPTION
  WHEN OTHERS
   THEN
     RETURN 0;
END exlude_weekends;

【讨论】:

  • 对一些非常迟钝的编码进行了很好的分解
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-04-19
  • 2011-11-22
  • 2016-12-12
  • 2011-02-09
  • 2023-04-08
  • 2011-04-06
  • 1970-01-01
相关资源
最近更新 更多