Decode360's Blog

业精于勤而荒于嬉 QQ:150355677 MSN:decode360@hotmail.com

  BlogJava :: 首页 :: 新随笔 :: 联系 ::  :: 管理 ::
  302 随笔 :: 26 文章 :: 82 评论 :: 0 Trackbacks
http://www.itpub.net/viewthread.php?tid=977079&extra=page%3D1%26amp%3Bfilter%3Ddigest
 
    很久以前看过的一个帖子,今天又翻到了,觉得还挺有意思的,摘录一下:
 
 
-----------------------
 
题目: 请模拟出 Oracle 中 Add_Months 函数功能的一个自定义函数.
要求:
1. 函数申明固定为:
create or replace function my_add_months(p_date_string varchar2,
                                         p_months      number)
  return varchar2
  不允许更改.
 
2. 代码中禁止申明日期类型的变量, 禁止使用一切与日期有关的类型转换及函数,禁止使用Oracle提供的Package, 只能使用 Oracle 的标准函数

3. 不求代码的效率高效, 但也不能太低.
 
4. 使用你可以使用的一切手段, 将源代码缩短, 不考虑编译后的代码长度, 这里只要求源代码最短.
 
5. 代码的长度以扣除其中的空白(空格, 回车换行, TAB键)后的字节长度.
 
6. 传入的日期字符串格式为 yyyymmdd, 增加的月份数传入整数,  函数内部你不需要检核传入的日期字符串是否有效(一定传入有效的), 月份数也不用考虑带小数的情况, 返回的日期字符串格式依然为 yyyymmdd
 
7. 写完函数后, 请用以下代码进行测试, 希望运行时间不要太长哦.
 
8. 只把测试结果贴出来, 函数代码先不贴.
 
9. 运算结果日期年不会超过9999, 也不会小于1600.
http://www.itpub.net/thread-977394-1-1.html 中提到1582年前使用的是凯撒时期制定的儒略历,4年一闰,这个属历史问题, 我们这里不考虑.
 
10. 函数代码中禁止使用SQL语句.
 
 
-----------------------
 
 
测试脚本:
 

set serverout on

 

declare

  ln  number ;

  ld  date ;

  ls1 varchar2 ( 8 );

  ls2 varchar2 ( 8 );

  lt  number := dbms_utility.get_time;

  ex exception ;

  y number ;

  m number ;

  d number ;

  j number ;

begin

  for j in 0 .. 5000 loop

    for y in 2000 .. 2001 loop

      for m in 2 .. 4 loop

        for d in 28 .. 31 loop

          begin

            ls1 := y || '0' || m || d;

            begin

              ld  := to_date(ls1, 'yyyymmdd' );

            exception

              when others then

                exit ; -- 过滤所有格式外的日期

            end ;

            -- 正的月份计算验证

            ld  := add_months(ld, j);

            ls2 := to_char(ld, 'yyyymmdd' );

            if nvl(my_add_months(ls1, j), '*' ) <> ls2 then

              dbms_output.put_line( 'Sorry: stop at p_date_string=' || ls1 ||

                                   ',p_months=' || j);

              dbms_output.put_line( 'my_add_months returned: ' ||

                                   my_add_months(ls1, j));

              dbms_output.put_line( 'add_months returned: ' || ls2);

              raise ex;

            end if ;

            -- 负的月份计算验证

            ls1 := to_char(add_months(ld, -j), 'yyyymmdd' );

            if nvl(my_add_months(ls2, -j), '*' ) <> ls1 then

               dbms_output.put_line( 'Sorry: stop at p_date_string=' || ls2 ||

                                   ',p_months=' || -j);

              dbms_output.put_line( 'my_add_months returned: ' ||

                                   my_add_months(ls2, -j));

              dbms_output.put_line( 'add_months returned: ' || ls1);

              raise ex;

            end if ;

          exception

            when ex then

              raise ;

            when others then

              raise ;

          end ;

        end loop ;

      end loop ;

    end loop ;

  end loop ;

  -- 计算函数的字符个数

  ln := 0 ;

  for c in ( select text

              from user_source

             where name = 'MY_ADD_MONTHS'

               and type = 'FUNCTION' ) loop

    ln := ln + nvl(lengthb(translate(c.text,

                                     '*' || chr( 9 ) || chr( 10 ) || chr( 13 ) || chr( 32 ),

                                            -- 除去所有的空格、回车、换行、 tab

                                     '*' )),

                   0 );

  end loop ;

  lt := (dbms_utility.get_time - lt) / 100 ;

  dbms_output.put_line( 'Congratulation ... Code Length: ' || ln ||

                       ' Bytes. Times: ' ||

                       to_char(to_date(to_char(lt, 'fm00000' ), 'sssss' ),

                               'hh24:mi:ss' ));

exception

  when ex then

    null ;

end ;

/

 


 
-----------------------
 
 
随便挑了一个答案来看看,确实是要耗费很多精力的事情:
 
--sdxiong

CREATE OR REPLACE FUNCTION MY_ADD_MONTHS(P_DATE_STRING VARCHAR2 ,

                                         P_MONTHS      NUMBER )

  RETURN VARCHAR2 IS

  H INT := 100 ;

  S INT := P_DATE_STRING;

  Y INT := S/H/H;

  M INT := S/H MOD H;

  D INT := S MOD H;

 

  PROCEDURE P IS

  BEGIN

    S := 28 + 3232332323030 / 10 **M MOD 10 ; -- 每个月最后一天

    IF M= 2 AND (Y MOD 400 = 0 OR Y MOD 4 < Y MOD H / H) THEN -- 判断 Y 是否闰年

       S := 29 ;

    END IF ;

  END ;

 

BEGIN

 

  P;

  D := D + D * INSTR(D,S); -- D 为月末,则不需要这个 D ,取新月份的月末期

 

  M := Y* 12 +M+P_MONTHS;

  Y := M/ 12 -.55; -- 计算新年份 ( 保证不进位或退位 ) ,用 Y:=(M-7)/12 好理解一些

  M := M-Y* 12 ; -- 计算新月份

 

  P; -- 计算新月份的月末

 

  RETURN Y*H*H + M*H + LEAST(D,S);

 

END ;

/

 
 
-----------------------
 
 
其它脚本:
 

/**********************
nyfor:
**********************/

create or replace function my_add_months
(
   p_date_string varchar2,
   p_months      number
) return varchar2 is
   c int := 100;
   a int := p_date_string;
   y int := a / c / c;
   m int := a / c - y * c;
   t int;
   procedure p is
   begin
      t := substr(525454554545, m, 1);
      t := t + 26 + 1 / (t + mod(y, 4) + instr(0, mod(y, c)) * mod(y, 400));
   end;
begin
   p;
   a := mod(a, c);
   a := trunc(a / t) * c + a;
   m := m + p_months + y * 12 - 7;
   y := m / 12;
   m := m - y * 12 + 7;
   p;
   return y * c * c + m * c + least(a, t);
end;

--如果要确保取出每一个隐式转换,包含精度的转换, 也会增加很多字节的. 我的转换后如下:
create or replace function my_add_months
(
   p_date_string varchar2,
   p_months      number
) return varchar2 is
   c int := 100;
   a int := to_number(p_date_string);
   y int := round(to_number(a) / c / c);
   m int := round(to_number(a) / c - y * c);
   t int;
   procedure p is
   begin
      t := to_number(substr('525454554545', m, 1));
      t := round(t + 26 + 1 / (t + mod(y, 4) + instr(0, mod(y, c)) * mod(y, 400)));
   end;
begin
   p;
   a := mod(to_number(a), c);
   a := trunc(to_number(a) / t) * c + to_number(a);
   m := m + p_months + y * 12 - 7;
   y := round(m / 12);
   m := m - y * 12 + 7;
   p;
   return to_char(y * c * c + m * c + least(to_number(a), t));
end;
--加入所有的显示转换及精度转换后 475 Bytes.
 
 
 
 
 
/**********************
DragonBill:
***********************/
 
--Congratulation ... Code Length: 344 Bytes. Times: 00:00:08
create or replace function my_add_months(p_date_string varchar2,
                                         p_months      number)
  return varchar2
AS
   C INT := p_date_string;
   H INT := 100;
   Y INT := C/H/H;
   M INT := MOD(C/H, H);
   D INT := MOD(C, H);
   Z INT := Y * 12 + M + p_months;
 
   PROCEDURE P
   AS
   BEGIN
      C := 27 + SUBSTR(43434434342 - SIGN(MOD(Y, 16 - 4 * INSTR(Y/H,'.'))), 1 - M, 1);
   END;
 
BEGIN
 
   P;
 
   M := MOD(Z - 1, 12) + 1;
   Y := (Z - M) / 12;
   Z := C;
 
   P;
 
   RETURN Y*H*H + M*H + LEAST(D + TRUNC(D/Z) * 3, C);
END;
 
 
--Congratulation ... Code Length: 339 Bytes. Times: 00:00:08
create or replace function my_add_months(p_date_string varchar2,
                                         p_months      number)
  return varchar2
AS
   C INT := p_date_string;
   H INT := 100;
   Y INT := C/H/H;
   M INT := MOD(C/H, H);
   D INT := MOD(C, H);
   Z INT := Y * 12 + M + p_months;
 
   PROCEDURE P
   AS
   BEGIN
      C := 27 + SUBSTR(43434434342 - SIGN(MOD(Y, 16 - 4 * INSTR(Y/H,'.'))), 1 - M, 1);
   END;
 
BEGIN
 
   P;
 
   M := MOD(Z - 1, 12) + 1;
   Y := (Z - M) / 12;
   Z := D/C/2;
 
   P;
 
   RETURN Y*H*H + M*H + LEAST(D + Z * 3, C);
END;
 
 
/*********************
判断闰年: 非百年看后两位能否被4整除, 百年看前两位能否被4整除,  再往前推, 百年能被100整除,
又 100 = 4 * 25, 故闰百年必能被16整除, 年/100, 能整除的无小数点, 是百年, 不能整除的有小数点,
凑巧小数点的位置为3, 综合以上, 故, 非百年 MOD 4 , 百年 MOD 16, 能整除的都是闰年.
这也就是为什么用 INSTR(Y/H,'.') 而不用 SIGN(MOD(Y, H)) 的原因, 虽然它们的长度是一样,
但 12 * {0, 1} 和 4 * {0, 3} 相比, 多了一个Byte
 
现在可以飚到329了
**********************/
 
--Congratulation ... Code Length: 329 Bytes. Times: 00:00:08
create or replace function my_add_months(p_date_string varchar2,
                                         p_months      number)
  return varchar2
AS
   C INT := p_date_string;
   H INT := 100;
   Y INT := C/H/H;
   M INT := C/H - Y * H;
   D INT := MOD(C, H);
   Z INT := Y * 12 + M + p_months;
 
   PROCEDURE P
   AS
   BEGIN
      C := 27 + SUBSTR(43434434342 - SIGN(MOD(Y, 16 - 4 * INSTR(Y/H,'.'))), 1 - M, 1);
   END;
 
BEGIN
 
   P;
 
   Y := (Z - 7) / 12;
   M := Z - Y * 12;
   Z := D/C/2;
 
   P;
 
   RETURN Y*H*H + M*H + LEAST(D + Z * 3, C);
END;
/

--Congratulation ... Code Length: 319 Bytes. Times: 00:00:09
create or replace function my_add_months(p_date_string varchar2,
                                         p_months      number)
  return varchar2
AS
   C INT := p_date_string;
   H INT := 100;
   Y INT := C/H/H;
   M INT := C/H - Y * H;
   D INT := C MOD H;
   Z INT := Y * 12 + M + p_months;
 
   PROCEDURE P
   AS
   BEGIN
      C := 27 + SUBSTR(43434434341 + 0 ** (Y MOD (4 + 12 * 0 ** (Y MOD H))), 1 - M, 1);
   END;
 
BEGIN
 
   P;
 
   Y := (Z - 7) / 12;
   M := Z - Y * 12;
   Z := D/C/2;
 
   P;
 
   RETURN Y * H * H + M * H + LEAST(C, Z || D);
END;
/
 
 




-The End-

posted on 2009-05-14 21:42 decode360-3 阅读(445) 评论(0)  编辑  收藏 所属分类: SQL Dev

只有注册用户登录后才能发表评论。


网站导航: