可以有人在函数中解释我的查询

时间:2015-06-24 14:04:16

标签: oracle function plsql

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;

1 个答案:

答案 0 :(得分:1)

正如您在评论中提到的,该功能的关键是分层子查询:

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子句指定父项和子项的关联方式)。在这个例子中,我们发现了一个棘手的使用(或滥用)连接的方法。

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

  1. 请注意,在CONNECT BY中与LEVEL进行比较的表达式是一个常量,它是结束日期之后的开始日期和结束日期之后的天数(为什么结束日期之后的第二天?因为它是使用&lt;和结束日期之后的第二天是间隔的第一天):

    (TO_DATE(p_date_end,&#39; DD-MON-RRRR&#39;) - TO_DATE(p_date_start,&#39; DD-MON-RRRR&#39;))+ 2

  2. select获取DUAL行(它只有一行)此行具有LEVEL 1(分层查询使用伪列LEVEL来指示它开始评估的根的深度)。

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

    TO_DATE(p_date_start,&#39; DD-MON-RRRR&#39;)+ LEVEL - 1

    这是开始日期加上级别减1:这是开始日期。

  5. 现在开始分层评估的新周期:再次评估上一个周期(开始日期)中生成的行(新行将具有级别2)。
  6. 如果它在生成的天数范围内(由CONNECT BY子句控制),则会生成一个新日期(开始日期后的第二天)。
  7. 新的周期开始(第3级)....
  8. 并且该过程重复进行,直到LEVEL大于要生成的天数(这与从开始日期到结束日期迭代所需的级别数相同)。
  9. 函数中的外部查询仅过滤SATURDAYS和SUNDAYS并计算剩余天数。

    虽然oracle非常有效地评估此查询,但此函数使用强力解决方案。

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

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

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

    请注意,TO_CHAR(日期,&#39; d&#39;)以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;