?
??? 很久以前看過的一個帖子,今天又翻到了,覺得還挺有意思的,摘錄一下:
?
?
-----------------------
?
題目: 請模擬出 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. 只把測試結果貼出來, 函數代碼先不貼.
?
?
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-