第十六周周末总结

  这几天考试没怎么看,想看下新知识,看了看博弈论好像有点复杂。。看几天资料和知识点再做题吧。
<think>我们需要计算两个日期之间排除日的小时差(转换为小时数),且不使用函数、存储过程和WITH子句(即纯查询语句)。思路如下:1.首先,我们计算两个日期之间的总小时差(包括周末)。2.然后,减去周末所包含的小时数。难点在于如何计算两个日期之间的周末小时数。由于不能使用函数和WITH,我们可以考虑使用一个数字辅助表(TallyTable)来生成日期序列,然后对每个日期判断是否为周末六或日),如果是周末,则计算该日期在时间区间内的小时数(注意开始和结束日期的部分天)。但是,用户要求纯查询语句,且不能使用函数、存储过程、WITH。我们可以使用系统表(如master..spt_values)来生成序列,或者使用一系列UNIONALL来生成日期序列。然而,如果日期跨度很大,这种方法可能效率不高。另一种思路是数学计算:1.计算两个日期之间的总天数。2.计算这些天数中包含的完整数,每有2天周末,因此周末天数为:数*2。3.然后计算剩余天数(不足一的部分)中的周末天数。4.但是,这里需要注意开始日期和结束日期可能不是完整的一天(即具体的时间点),所以我们需要分别处理开始日期、结束日期以及中间的整天。然而,由于我们要求的是小时差,且要排除周末,那么周末的小时数不仅包括中间的完整周末日,还包括开始日期和结束日期如果是周末的部分。具体步骤:步骤1:计算总小时差总小时差=DATEDIFF(HOUR,@start_date,@end_date)步骤2:计算需要减去的周末小时数周末小时数=中间完整周末天数*24+开始日期如果是周末的部分小时数+结束日期如果是周末的部分小时数但是,如何计算中间完整周末天数?我们可以这样:总天数=DATEDIFF(DAY,@start_date,@end_date)完整数=总天数/7(整数除法)->周末天数=完整数*2剩余天数=总天数%7然后,我们需要计算剩余天数中周末的天数。注意:这里剩余天数是从开始日期之后的第一天开始算剩余天数(不包括开始日期当天),所以我们需要知道开始日期是星期几,然后向后推剩余天数,看其中包含几个周末。此外,开始日期和结束日期当天的部分小时数需要单独计算:-如果开始日期是周末,那么从开始日期到开始日期那天的结束时间(即24:00:00)的小时数(注意:如果结束日期和开始日期是同一天,则取结束时间)。-如果结束日期是周末,那么从结束日期那天的开始时间(00:00:00)到结束日期的小时数(同样,如果开始日期和结束日期同一天,则已经在上一步计算过了,所以这里要避免重复)。但是,由于不能使用函数,我们无法封装逻辑,所以需要在一个查询中完成。这会导致查询非常复杂。考虑到这些,我们可以采用以下方法:1.计算两个日期之间的所有日期(通过数字表),然后对每个日期判断是否为周末,并且该日期是否在区间内(注意部分天)。但是,如果日期跨度很大,数字表可能很大,效率不高。2.我们可以用系统表master..spt_values来生成序列(如果可用且日期跨度在0~2047天内),或者使用其他系统表。这里假设我们可以使用master..spt_values(如果环境允许)。如果不行,我们可以用其他方法生成序列,比如使用多个表的CROSSJOIN生成足够多的数字。由于用户要求纯查询,且不能使用函数等,我们选择使用数字辅助表(这里用master..spt_values,并假设跨度不超过2048天)。如果超过,则需要用其他方式生成更大的序列。具体步骤:a.生成从0到DATEDIFF(DAY,@start_date,@end_date)的数字序列(代表天数偏移)。b.对每个偏移量对应的日期(即@start_date+offset)判断是否为周末六或日)。c.对于开始日期(偏移0):如果是周末,则计算开始日期到该天结束的时间差(小时),但是如果结束日期就在同一天,则只计算开始日期到结束日期的小时数。d.对于结束日期(即最后一天,偏移为总天数):如果是周末,且不是开始日期(即总天数>0),则计算该天开始时间到结束日期的小时数。e.对于中间日期(偏移1到总天数-1):如果是周末,则直接加24小时。但是,这样处理逻辑复杂,且需要分别处理开始和结束日期。另一种思路:将整个时间段拆分成三部分:开始日期部分、中间完整日期、结束日期部分。然而,由于不能使用WITH,我们只能在一个查询中完成。这里我们尝试使用数字辅助表,并分别处理每一天(包括部分天)。我们假设有表变量或变量,但用户要求纯查询,所以我们可以用变量来存储开始日期和结束日期。但是用户要求的是纯查询语句,所以我们需要在查询中定义变量?实际上,用户可能是想在一个查询中完成,即直接替换变量值。因此,我们可以这样写:```sqlDECLARE@start_dateDATETIME='2023-10-0712:00:00';DECLARE@end_dateDATETIME='2023-10-0912:00:00';--计算总天数差(整数天,从开始日期的0点到结束日期的0点)DECLARE@total_daysINT=DATEDIFF(DAY,@start_date,@end_date);--生成序列(使用master..spt_values,范围0到@total_days)SELECT--总小时数减去周末小时数DATEDIFF(HOUR,@start_date,@end_date)-(--计算周末小时数SELECT--处理开始日期(第0天)SUM(CASE--如果是第一天(即开始日期)并且是周末WHENn.number=0AND(DATEPART(WEEKDAY,DATEADD(DAY,n.number,CAST(@start_dateASDATE)))IN(1,7)--注意:这里根据@@DATEFIRST设置,不同设置下周末的值不同,通常日是1,六是7THENCASE--如果开始日期和结束日期在同一天WHEN@total_days=0THENDATEDIFF(HOUR,@start_date,@end_date)ELSEDATEDIFF(HOUR,@start_date,DATEADD(DAY,1,CAST(@start_dateASDATE)))END--处理最后一天(即结束日期)并且不是第一天(即@total_days>0)WHENn.number=@total_daysAND(DATEPART(WEEKDAY,DATEADD(DAY,n.number,CAST(@start_dateASDATE)))IN(1,7)THENDATEDIFF(HOUR,CAST(@end_dateASDATE),@end_date)--处理中间的天(整天)WHENn.number>0ANDn.number<@total_daysAND(DATEPART(WEEKDAY,DATEADD(DAY,n.number,CAST(@start_dateASDATE)))IN(1,7)THEN24ELSE0END)ASweekend_hoursFROMmaster..spt_valuesnWHEREn.type='P'ANDn.numberBETWEEN0AND@total_days)ASwork_hours;```但是,这里有几个问题:1.依赖系统表master..spt_values,且要求@total_days>=0且不超过2047。2.对于的计算,DATEPART(WEEKDAY)依赖于@@DATEFIRST设置,不同设置返回值不同。为了避免这个问题,我们可以用另一种方式:计算日期与一个固定日期的差(比如'1900-01-01',这个日期是星期一)模7,然后判断余数(5,6代表六、日?因为1900-01-01是一,那么0就是一,1二...5六,6日)。但注意:1900-01-01实际是一,但SQLServer中1900-01-01是一吗?经查,1900-01-01是一。所以,我们可以用:(DATEDIFF(DAY,'1900-01-01',日期)%7)-0:一,1:二,2:三,3:四,4:五,5:六,6:日因此,判断周末:当余数为5或6。修改判断条件:(DATEDIFF(DAY,'1900-01-01',DATEADD(DAY,n.number,CAST(@start_dateASDATE)))%7)IN(5,6)但是,在SQLServer中,负数的模运算可能有问题,所以确保日期大于'1900-01-01'。另外,如果@start_date小于'1900-01-01',我们可以用另一个基准日期(比如'2023-01-01',已知是日?不,我们最好用一个已知是一的日期)。我们可以用:'19000101'是一。但是,为了避免负数,我们可以这样:先计算日期与基准日期的天数差,然后取模。由于日期在基准日期之后,所以天数差非负。所以,修改后的条件:(DATEDIFF(DAY,'1900-01-01',current_date)%7)IN(5,6)但是,在查询中,current_date就是DATEADD(DAY,n.number,CAST(@start_dateASDATE))因此,整个查询如下(注意:我们使用变量,但用户可能希望直接替换变量值,所以也可以写成内联值的形式):```sqlDECLARE@start_dateDATETIME='2023-10-0712:00:00';DECLARE@end_dateDATETIME='2023-10-0912:00:00';--计算总天数(整数天差)DECLARE@total_daysINT=DATEDIFF(DAY,@start_date,@end_date);SELECTDATEDIFF(HOUR,@start_date,@end_date)-ISNULL((SELECTSUM(CASEWHENn.number=0AND(DATEDIFF(DAY,'1900-01-01',CAST(@start_dateASDATE))%7)IN(5,6)THENCASEWHEN@total_days=0THENDATEDIFF(HOUR,@start_date,@end_date)ELSEDATEDIFF(HOUR,@start_date,DATEADD(DAY,1,CAST(@start_dateASDATE)))ENDWHENn.number=@total_daysAND(DATEDIFF(DAY,'1900-01-01',CAST(@end_dateASDATE))%7)IN(5,6)THENCASEWHEN@total_days>0THENDATEDIFF(HOUR,CAST(@end_dateASDATE),@end_date)ELSE0--同一天的情况已经在上面处理ENDWHENn.number>0ANDn.number<@total_daysAND(DATEDIFF(DAY,'1900-01-01',DATEADD(DAY,n.number,CAST(@start_dateASDATE)))%7)IN(5,6)THEN24ELSE0END)FROMmaster..spt_valuesnWHEREn.type='P'ANDn.numberBETWEEN0AND@total_days),0)ASwork_hours;```注意:如果@total_days为负数,需要处理(但一般开始日期小于结束日期,所以这里假设@start_date<=@end_date)。但是,上面代码在开始日期和结束日期为同一天且是周末时,会正确减去这一天的小时差。在开始日期和结束日期跨越多天时:-开始日期(第0天)如果是周末,则减去从开始时间到当天结束的小时数(24点)。-结束日期(最后一天)如果是周末,则减去从当天开始(0点)到结束时间的小时数。-中间的整天如果是周末,则减去24小时。但是,这里有一个问题:数字辅助表master..spt_values可能不可用(有些环境没有),所以我们可以用其他方法生成序列,比如使用ROW_NUMBER(),但是不使用WITH,我们可以用子查询生成序列。生成0到@total_days的序列,我们可以用:```sqlSELECTTOP(@total_days+1)ROW_NUMBER()OVER(ORDERBY(SELECTNULL))-1ASnumberFROMsys.objectsa,sys.objectsb,sys.objectsc```但是,这里使用了变量@total_days,而纯查询中不能使用变量(除非我们内联所有值)。所以,我们尝试不用变量,而用直接值。假设我们直接替换@start_date和@end_date:```sqlSELECTDATEDIFF(HOUR,'2023-10-0712:00:00','2023-10-0912:00:00')-ISNULL((SELECTSUM(CASE--第0天(开始日期)WHENn.number=0AND(DATEDIFF(DAY,'1900-01-01',CAST('2023-10-0712:00:00'ASDATE))%7)IN(5,6)THENCASEWHENDATEDIFF(DAY,'2023-10-0712:00:00','2023-10-0912:00:00')=0THENDATEDIFF(HOUR,'2023-10-0712:00:00','2023-10-0912:00:00')ELSEDATEDIFF(HOUR,'2023-10-0712:00:00',DATEADD(DAY,1,CAST('2023-10-0712:00:00'ASDATE)))END--最后一天(结束日期)WHENn.number=DATEDIFF(DAY,'2023-10-0712:00:00','2023-10-0912:00:00')AND(DATEDIFF(DAY,'1900-01-01',CAST('2023-10-0912:00:00'ASDATE))%7)IN(5,6)THENCASEWHENDATEDIFF(DAY,'2023-10-0712:00:00','2023-10-0912:00:00')>0THENDATEDIFF(HOUR,CAST('2023-10-0912:00:00'ASDATE),'2023-10-0912:00:00')ELSE0END--中间的天WHENn.number>0ANDn.number<DATEDIFF(DAY,'2023-10-0712:00:00','2023-10-0912:00:00')AND(DATEDIFF(DAY,'1900-01-01',DATEADD(DAY,n.number,CAST('2023-10-0712:00:00'ASDATE)))%7)IN(5,6)THEN24ELSE0END)FROM(SELECTTOP(DATEDIFF(DAY,'2023-10-0712:00:00','2023-10-0912:00:00')+1)ROW_NUMBER()OVER(ORDERBY(SELECTNULL))-1ASnumberFROMsys.objectsa,sys.objectsb,sys.objectsc)n),0)ASwork_hours;```注意:这里使用了sys.objects的CROSSJOIN来生成序列,只要日期跨度不是特别大(比如超过1000天),应该足够。但是,这个查询比较复杂,而且多次计算DATEDIFF(DAY,...),我们可以用变量避免重复计算,但用户要求不用变量(在纯查询中,我们只能重复计算)。另外,如果开始日期大于结束日期,我们需要处理负数情况。这里假设开始日期小于等于结束日期。测试:2023-10-07(六)12:00到2023-10-09(一)12:00总小时差:2天=48小时。周末小时数:开始日期(六):12:00到24:00->12小时结束日期(一):不是周末,0小时中间日期(日):24小时总共减去:12+24=36小时所以工作小时数:48-36=12小时(实际上,从六12:00到一12:00,排除周末,只有一0:00到12:00共12小时)。但是,我们期望的结果是:从六12:00到一12:00,排除周末六和日),所以只有一0:00到12:00(12小时)。所以正确。另一个测试:2023-10-06(五)12:00到2023-10-09(一)12:00总小时差:72小时。周末小时数:开始日期(五):不是周末,0结束日期(一):不是周末,0中间日期:六(24小时)、日(24小时)共减去48小时工作小时数:72-48=24小时(即五12:00到24:00的12小时,加上一0:00到12:00的12小时,共24小时)。但是,注意:五12:00到24:00(12小时)是工作日,六和日全天(48小时)被排除,一0:00到12:00(12小时)是工作日,所以总工作小时=12+12=24小时。正确。再测试同一天:2023-10-07(六)12:00到2023-10-0715:00总小时差:3小时开始日期是六,且同一天,所以减去3小时工作小时数:0因此,查询可以写成这样。但是,我们需要注意@@DATEFIRST设置不影响,因为我们用1900-01-01(一)作为基准。最后,我们写成纯查询(无变量),将开始日期和结束日期直接替换。完整示例(替换为具体日期):```sqlSELECTDATEDIFF(HOUR,'2023-10-0712:00:00','2023-10-0912:00:00')AStotal_hours,DATEDIFF(HOUR,'2023-10-0712:00:00','2023-10-0912:00:00')-ISNULL((SELECTSUM(CASEWHENn.number=0AND(DATEDIFF(DAY,'1900-01-01',CAST('2023-10-0712:00:00'ASDATE))%7)IN(5,6)THENCASEWHENDATEDIFF(DAY,'2023-10-0712:00:00','2023-10-0912:00:00')=0THENDATEDIFF(HOUR,'2023-10-0712:00:00','2023-10-0912:00:00')ELSEDATEDIFF(HOUR,'2023-10-0712:00:00',DATEADD(DAY,1,CAST('2023-10-0712:00:00'ASDATE)))ENDWHENn.number=DATEDIFF(DAY,'2023-10-0712:00:00','2023-10-0912:00:00')AND(DATEDIFF(DAY,'1900-01-01',CAST('2023-10-0912:00:00'ASDATE))%7)IN(5,6)THENCASEWHENDATEDIFF(DAY,'2023-10-0712:00:00','2023-10-0912:00:00')>0THENDATEDIFF(HOUR,CAST('2023-10-0912:00:00'ASDATE),'2023-10-0912:00:00')ELSE0ENDWHENn.number>0ANDn.number<DATEDIFF(DAY,'2023-10-0712:00:00','2023-10-0912:00:00')AND(DATEDIFF(DAY,'1900-01-01',DATEADD(DAY,n.number,CAST('2023-10-0712:00:00'ASDATE)))%7)IN(5,6)THEN24ELSE0END)FROM(SELECTTOP(DATEDIFF(DAY,'2023-10-0712:00:00','2023-10-0912:00:00')+1)ROW_NUMBER()OVER(ORDERBY(SELECTNULL))-1ASnumberFROMsys.objectsaCROSSJOINsys.objectsbCROSSJOINsys.objectsc)n),0)ASwork_hours;```注意:这里使用三个sys.objects的CROSSJOIN,可以生成足够大的序列(如果sys.objects有100行,则100*100*100=1000000,足够覆盖多年)。但是,如果日期差为负数(开始日期>结束日期),则DATEDIFF(DAY)为负数,TOP(负数+1)会报错。所以我们需要确保开始日期小于等于结束日期。如果用户可能需要倒序,可以在外层交换开始结束日期。由于用户问题中未提及,我们假设开始日期<=结束日期。最后,我们将其封装为一个可复用的查询,只需替换两个日期字符串即可。注意:在计算基准日期(1900-01-01)到当前日期天数差取模时,由于天数差可能很大,但取模7不会溢出,所以安全。总结:这个方法通过生成日期序列,分别计算每一天的周末小时数(部分天或整天),然后从总小时数中减去,得到排除周末的工作小时数。如果用户的环境不允许使用sys.objects,则可能需要使用其他表(如sys.columns)或创建数字辅助表。但这里假设sys.objects可用。§§相关问题§§1.如何修改此查询以排除法定节假日?2.如果日期范围很大(如几十年),如何优化此查询的性能?3.如何调整此查询以计算分钟差(排除周末)?</think>### SQL Server 纯查询语句计算排除周末的小时差 在 SQL Server 中使用纯查询语句计算两个日期之间排除日的小时差,不使用函数、存储过程和 WITH 子句,可以通过以下方案实现: #### 解决方案思路 1. **生成日期序列**:使用系统表(如 `sys.objects`)生成日期范围内的所有天数序列 2. **识别周末**:计算每个日期与基准日期的天数差,取模 7 判断周末(5=六,6=日) 3. **分段计算**: - **开始日期**:计算当天剩余小时数(若为周末) - **结束日期**:计算当天已过小时数(若为周末) - **中间日期**:减去完整的 24 小时(若为周末) #### 完整查询语句 ```sql DECLARE @start DATETIME = '2023-10-06 08:30:00'; DECLARE @end DATETIME = '2023-10-10 16:45:00'; SELECT DATEDIFF(HOUR, @start, @end) - ISNULL(( SELECT SUM( CASE -- 处理开始日期(部分天) WHEN n = 0 AND (DATEDIFF(DAY, '19000101', CAST(@start AS DATE)) % 7) IN (5,6) THEN DATEDIFF(HOUR, @start, DATEADD(DAY, 1, CAST(@start AS DATE))) -- 处理结束日期(部分天) WHEN n = DATEDIFF(DAY, @start, @end) AND (DATEDIFF(DAY, '19000101', CAST(@end AS DATE)) % 7) IN (5,6) THEN DATEDIFF(HOUR, CAST(@end AS DATE), @end) -- 处理中间整天 WHEN (DATEDIFF(DAY, '19000101', DATEADD(DAY, n, CAST(@start AS DATE))) % 7) IN (5,6) THEN 24 ELSE 0 END) FROM ( SELECT TOP (DATEDIFF(DAY, @start, @end) + 1) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1 AS n FROM sys.objects a CROSS JOIN sys.objects b ) dates ), 0) AS work_hours; ``` #### 查询说明 1. **基准日期**:使用 `1900-01-01`(一)作为基准日期计算星期几 2. **周末判断**:`(天数差 % 7) IN (5,6)` 标识六和日 3. **分段处理**: - **开始日期**:计算到当天结束的小时数 - **结束日期**:计算从当天开始的小时数 - **中间日期**:全天 24 小时扣除 4. **序列生成**: - `sys.objects` 交叉连接生成足够长的序列 - `ROW_NUMBER() - 1` 创建从 0 开始的日期偏移量 #### 测试示例 ```sql -- 五14:00 到 一10:00 (应返回20小时) SELECT... -- 替换为上述查询 -- 结果: 五(14-24时=10h) + 一(0-10时=10h) = 20h -- 六12:00 到 日12:00 (应返回0小时) SELECT... -- 替换为上述查询 ``` #### 性能优化建议 1. **减少序列长度**:添加 `WHERE n <= DATEDIFF(DAY, @start, @end)` 条件 2. **使用物理数字表**:若有预定义的数字表可替换序列生成部分 3. **日期范围限制**:添加 `WHERE @start <= @end` 防止负值 > **注意**:实际使用时替换 `@start` 和 `@end` 变量值为具体日期,或在查询中直接替换日期字符串[^1] ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值