很久以前看过的一个帖子,今天又翻到了,觉得还挺有意思的,摘录一下:
-----------------------
题目: 请模拟出 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-