300分求最佳的节假日、双休日扣除算法!

chechy 2005-03-26 11:00:05
现有一容量非常大的数据表(千万级),其中有一日期字段类型,需要求当天日期与该日期之差小于X日,且要排除节假日和双休日。
由于X是不定的,所以不能够建辅助字段给日期在扣除节假日和双休日的基础上加X日。
期望能够求得一最讲究效率的算法,快速求出当前日期与数据表中日期字段,排除节假日和双休日后,小于X日的算法。
节假日和双休日的表格可以任意定义,可以为它们建立任意辅助字段。
欢迎一起研讨,300分给最佳算法的提出者。
...全文
979 47 打赏 收藏 转发到动态 举报
写回复
用AI写文章
47 条回复
切换为时间正序
请发表友善的回复…
发表回复
ahlwz 2005-03-31
  • 打赏
  • 举报
回复
首先建立节假日表
再利用x>=0建立倒循环,以当前日期-1,判断是否节假日,如果是则+1 loop
chechy 2005-03-30
  • 打赏
  • 举报
回复
同意zjcxc(邹建) 的观点。我也不希望在开发的时候钻牛角尖。
从这个帖子现有的状况看,我的目的基本上已经达到了。
U2U2U2U2 2005-03-30
  • 打赏
  • 举报
回复
邹建说的对
‘不是很必要的话,不要花80%的精力来实现20%的效率提高’
gzhughie 2005-03-30
  • 打赏
  • 举报
回复
各位老大,看看我的解答,不用循环啊,请指导一下。本来连函数都不用的,但是为了写起来简单,所以用了函数。其实最好是做成存储过程,那样速度会更好。
zjcxc 2005-03-30
  • 打赏
  • 举报
回复
不过,个人觉得,一味在这个时间表和函数上去追求效率,还不如多想办法在其他方面

假设函数处理需要0.1秒,你通过各种办法把它提高到0.05秒,提高的值只有0.05秒
而如果其他查询需要2秒,你通过各种办法把它提高到1秒,提高的值却有1秒

所以不是很必要的话,不要花80%的精力来实现20%的效率提高
zjcxc 2005-03-30
  • 打赏
  • 举报
回复
效率最高的还是直接使用int/smallint类型,分别对应date/smalldate
可以在保存的时候,把当前日期以datediff(day,0,getdate())的形式保存到表中



/*--建立节假日表

按常理推算,双休日都是可以直接计算的,但考虑到实际工作中,会出现因节假日
而调整双休日为工作日的情况,所以把双休日和节假日定义都纳入节假表中,处理时仅根据
节假日表确定指定日期是否为工作日
--*/
create table tb_Holiday(
HDate int primary key clustered,
Name nvarchar(50) not null)
go

--处理的自定义函数
create function f_workdateadd(
@dt smalldatetime, --要添加工作天数的日期
@workday int --要增加的工作天
)returns smalldatetime
as
begin
declare @dt_int int
set @dt_int=datediff(day,0,@dt)

if @workday>0
while @workday>0
select @dt_int=@dt_int+@workday
,@workday=count(*)
from tb_Holiday
where HDate between @dt_int and @dt_int+@workday
else
while @workday<0
select @dt_int=@dt_int+@workday,@workday=-count(*)
from tb_Holiday
where HDate between @dt_int+@workday and @dt_int
return(@dt)
end
go

--查询最近10个工作日内的记录,这样调用(注意在日期字段上建立索引,否则无法提高效率)
select * from tb where 日期 between dbo.f_workdateadd(getdate(),-10) and convert(char(10),getdate(),120)
aioria 2005-03-29
  • 打赏
  • 举报
回复
试着写了一个自动生成一年中周末的存储过程

--自动生成一年的所有周末的存储过程
create procedure GenerateWeekdays
@year int
as
begin
declare @dateFirstDefault int
declare @dayLoop datetime
declare @weekLoop int
declare @strYear char(4)

--检查年份是否越界
if not(@year between 1753 and 9999)
begin
return 0
end

--set @dateFirstDefault=@@datefirst --保存当前设置
--set datefirst 1 --将每周第一天设为周一
set @dayLoop=dateadd(year,@year-1900,'1900-01-01')
set @weekLoop=1
set @strYear=cast(@year as char(4))

declare @nameTemp char(50)
--declare @hdateTemp datetime
--declare @remarkTemp char(100)

--如果元旦是周日
if(datepart(weekday,@dayLoop)+@@datefirst=8)
begin
set @nameTemp=@strYear+'年 第'+cast(@weekLoop as char(2)) +'周 星期日'

insert into Holiday([Name],HDate,Remark)
values(@nameTemp,@dayLoop,@nameTemp)

set @dayLoop=DateAdd(day,1,@dayLoop)
set @weekLoop=@weekLoop+1
end

--取得元旦后第一个周六
set @dayLoop=DateAdd(day,6-datepart(weekday,@dayLoop),@dayLoop)

declare @intLoop int
set @intLoop=1
while(@intLoop<=52)
begin
--加入周六
set @nameTemp=@strYear+'年 第'+cast(@weekLoop as char(2)) +'周 星期六'
insert into Holiday([Name],HDate,Remark)
values(@nameTemp,@dayLoop,@nameTemp)

--加入周日
set @nameTemp=@strYear+'年 第'+cast(@weekLoop as char(2)) +'周 星期日'
insert into Holiday([Name],HDate,Remark)
values(@nameTemp,dateadd(day,1,@dayLoop),@nameTemp)

set @dayLoop=dateadd(day,7,@dayLoop)
set @weekLoop=@weekLoop+1
set @intLoop=@intLoop+1
end
--set datefirst @dateFirstDefault
return 1
end


wyb0026 2005-03-29
  • 打赏
  • 举报
回复
1我认为这个问题的关键是效率问题,正如邹建所说日期字段一定要用字符型这样在其上见索引,索引的所占的空间以及查询速度才能有大幅度的提升(这是多年来工作的结论)这是很关键的
2其次就是排出的算法能够真正用到这个索引且正确也是问题关键,我一向认为双休日可计算出来但是节假日不具有确定性,建表也罢,添加辅助字段时非常有必要,但另建表要好一些
所以我想应该是这样分两步走
A,根据X值通过假期表和计算获得最终的时间缎,这个消耗资源非常小因为假期表应该很小
B.根据却定的间段对表进行查询,直接利用索引,只遍历表一次得出结果这样的效率应该最高
个人看法仅供参考
aioria 2005-03-29
  • 打赏
  • 举报
回复
这有一段我原来写的生成周末的代码
create table Holiday
(
HolidayID int identity not null,
"HDate" smalldatetime not null,
"Name" char(50) not null,
"Remark" char(100)
)
go


alter table "Holiday"
add constraint "PK_Holiday" primary key clustered ("HolidayID")

create unique index "Holiday_HDate_ind"
on Holiday("HDate")

create unique index "Holiday_Name_ind"
on Holiday("Name")

-------------------------------------------------------------------

public void GenerateWeekends(int year)
{
DateTime dayLoop = new DateTime(year,1,1);
int week = 1;
string query = "";

string name = "";
DateTime date;
string remark = "";

if (dayLoop.DayOfWeek == 0) //元旦是周日
{
name = year.ToString() + "年 第" + week.ToString() + "周 星期日";
date = dayLoop;
remark = name;

query = "insert into Holiday(Name,HDate,Remark)"
+ " values('" + name + "','" + date.ToShortDateString() + "','" + remark + "')";
ExecuteNonQuery(query);

dayLoop += new TimeSpan(1,0,0,0,0);
week ++;
}

dayLoop += new TimeSpan(6 - (int)(dayLoop.DayOfWeek),0,0,0,0); //找到第一个周六
for (int i = 0; i < 50; i++)
{
//加入星期六
name = year.ToString() + "年 第" + week.ToString() + "周 星期六";
date = dayLoop;
remark = name;

query = "insert into Holiday(Name,HDate,Remark)"
+ " values('" + name + "','" + date.ToShortDateString() + "','" + remark + "')";
ExecuteNonQuery(query);

//加入星期日
name = year.ToString() + "年 第" + week.ToString() + "周 星期日";
date = dayLoop + new TimeSpan(1,0,0,0,0);
remark = name;

query = "insert into Holiday(Name,HDate,Remark)"
+ " values('" + name + "','" + date.ToShortDateString() + "','" + remark + "')";
ExecuteNonQuery(query);

//时间增加一周
dayLoop += new TimeSpan(7,0,0,0,0);
week ++;
}

//对51、52周单独处理,因为可能已经到第二年了
for (int i = 0; i < 2; i++)
{
//加入星期六
name = year.ToString() + "年 第" + week.ToString() + "周 星期六";
date = dayLoop;
remark = name;

if (date.Year == year)
{
query = "insert into Holiday(Name,HDate,Remark)"
+ " values('" + name + "','" + date.ToShortDateString() + "','" + remark + "')";
ExecuteNonQuery(query);
}

//加入星期日
name = year.ToString() + "年 第" + week.ToString() + "周 星期日";
date = dayLoop + new TimeSpan(1,0,0,0,0);
remark = name;

if (date.Year == year)
{
query = "insert into Holiday(Name,HDate,Remark)"
+ " values('" + name + "','" + date.ToShortDateString() + "','" + remark + "')";
ExecuteNonQuery(query);
}

//时间增加一周
dayLoop += new TimeSpan(7,0,0,0,0);
week ++;
}

}
jinjazz 2005-03-29
  • 打赏
  • 举报
回复
双休日直接从函数取得,把节假日的排班信息记录在一个表中,
排班信息包括上班的双休日(+1),节日中的休息日(如果是双休则type为0,不是双休则-1)
这样辅助表中的记录应该很少,而且直观,最后把type求和一下就可以知道差值了
jinjazz 2005-03-29
  • 打赏
  • 举报
回复
--我的思路,以及测试结果~~请大家指正

create function WorkDays(@mStartDate datetime,@mEndDate datetime)
returns integer
begin
declare @m integer
declare @n integer
if @mEndDate < @mStartDate set @n=-1 else
begin
set @m=datediff(dd,@mStartDate,@mEndDate)
set @n=(@m/7)*2
if @m % 7<>0
begin
if datepart(weekday,@mStartDate)>datepart(weekday,@mEndDate)
set @n=@n+2
if datepart(weekday,@mStartDate)=1 set @n=@n+1
if datepart(weekday,@mEndDate)=1 set @n=@n-1
end
set @n=@m-@n
end
return(@n)
end
go

create table tb_Holiday(
HDate smalldatetime primary key clustered,-- 日期
Type smallint,--增减
Name nvarchar(50) not null)
go

insert into tb_Holiday
select '2005-1-1',0,'元旦' union -- 周六
select '2005-1-2',0,'元旦' union -- 周日
select '2005-1-3',-1,'元旦-' union
select '2005-2-5',1,'春节+' union
select '2005-2-6',1,'春节+' union
select '2005-2-9',-1,'春节-' union
select '2005-2-10',-1,'春节-' union
select '2005-2-11',-1,'春节-' union
select '2005-2-12',0,'春节' union -- 周六
select '2005-2-13',0,'春节' union -- 周日
select '2005-2-14',-1,'春节-' union
select '2005-2-15',-1,'春节-'

select dbo.WorkDays('2004-12-20',getdate())+sum(Type) from tb_Holiday where HDate >'2004-12-20'


drop table tb_Holiday
drop function WorkDays

/*

-----------
67
*/
aioria 2005-03-29
  • 打赏
  • 举报
回复
仅仅使用函数的方法不利于扩展,我同意sonique(雨季不再来)和zjcxc(邹建)的做法
jinjazz 2005-03-29
  • 打赏
  • 举报
回复
节日必须考虑到公历和农历两种情况

如果公历只考虑法定3天比如国庆和五一的话比较容易计算,如果加上调班就麻烦点

农历主要是春节~~这个在考虑一下
jinjazz 2005-03-29
  • 打赏
  • 举报
回复
我以前的一个delphi算法,改为SQL函数,可以直接算出除去双休日之外的工作日,不需要循环和辅助表

create function WorkDays(@mStartDate datetime,@mEndDate datetime)
returns integer
begin
declare @m integer
declare @n integer
if @mEndDate < @mStartDate set @n=-1 else
begin
set @m=datediff(dd,@mStartDate,@mEndDate)
set @n=(@m/7)*2
if @m % 7<>0
begin
if datepart(weekday,@mStartDate)>datepart(weekday,@mEndDate)
set @n=@n+2
if datepart(weekday,@mStartDate)=1 set @n=@n+1
if datepart(weekday,@mEndDate)=1 set @n=@n-1
end
set @n=@m-@n
end
return(@n)
end


delphi 代码是
function WorksDay( //计算两个日期间有多少个工作日
mStartDate: TDate; //开始日期
mEndDate: TDate //终止日期
): Integer; //返回两个日期间的工作日,如果输入错误则返回-1
var m,n:integer;
begin
Result := -1;
if mEndDate < mStartDate then Exit;
m:=DaysBetween(mStartDate,mEndDate);
n:=(m div 7)*2;
if m mod 7<>0 then
begin
if DayOfWeek(mStartDate)>DayOfWeek(mEndDate) then n:=n+2;
if DayOfWeek(mStartDate)=1 then n:=n+1;
if DayOfWeek(mEndDate)=1 then n:=n-1;
end;
result:=m-n;
end;


tl_pear 2005-03-29
  • 打赏
  • 举报
回复
获益非浅!
天地客人 2005-03-29
  • 打赏
  • 举报
回复
再帮你想想方法吧!
gzhughie 2005-03-29
  • 打赏
  • 举报
回复
说明:上帖中的辅表“只记录工作日”的意思是:表里不记录节假日和休息日的日期,也就是说表中的日期是不连贯的,但是序号是连贯的。



gzhughie 2005-03-29
  • 打赏
  • 举报
回复
/*
思路,辅助表只记录正常工作日,给顺序排列的工作日不间断的序号,那么如果只要在序号上做加减法,就可以定位到需要的日期,不需要循环。
*/


/*建表:

主表 main(mainID,mainDate(主表日期字段),...)

辅表 workDay 只记录工作日,表中一行数据代表一天
附表字段定义
WorkDateNO int (递增序号,不间断)
WorkDate datetime (工作日,如果软件的生命期为10年,那么表中记录数不会大于3560,数据量不大)

*/


--处理的自定义函数
create function f_workdateadd
(
@startDay datetime --起始查找日期
@worknum int --条件时间差X
) returns datetime
as
begin
declare @returnDate datetime
declare @i

select @i = WorkDateNO from workDay where datediff(day,WorkDate,@startDay)=0
select @returnDate = WorkDate where WorkDateNO = @i-@worknum

return @returnDate
end
go


--查询最近10个工作日内的记录,这样调用(注意在日期字段上建立索引,否则无法提高效率)

select *
from main
where mainDate between dbo.f_workdateadd(getdate(),10) and convert(char(10),getdate(),120)



chechy 2005-03-29
  • 打赏
  • 举报
回复
to jinjazz(近身剪*10年磨一贴) :本贴的目的不是求双休日或者春节的算法,这个我早就有现成的了。
本贴的目的主要是讨论如果提高性能。
我已经向微软提出问题,询问datetime,smalldatetime以及char到底那种效率比较高。可能过些日子会有结果。
Keown 2005-03-28
  • 打赏
  • 举报
回复
建立日期序号对应的资源资料表,不再局限于周六、日,想把哪个数字算成休息或者节假日均可。二期显示日期可关联日期资源表显示成农历、各国特殊日历均可。。。

加载更多回复(27)

27,579

社区成员

发帖
与我相关
我的任务
社区描述
MS-SQL Server 应用实例
社区管理员
  • 应用实例社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧