用SQL计算出30分钟间隔内不同时间的数量

3

我正在尝试找到一个SQL查询,以计算至少相差30分钟的不同开始时间的数量。

我有许多员工,当他们在一周内至少有三个不同的开始时间,并且这些开始时间与其他开始时间相差至少30分钟时,会获得信用。例如:

select count(distinct (CONVERT(VARCHAR(10), starttime, 108))), employeecode
from schedule 
where CONVERT(VARCHAR(10), starttime, 108) >= 
(select min(CONVERT(VARCHAR(10), dateadd (mi, 30, s2.starttime), 108)) from schedule s2)  
group by starttime, employeecode

我希望能够获得员工代码和不同开始时间数量的结果。例如:员工代码=9999,不同的开始时间=4。我一直在摸索中,但还没有得到有效的解决方案... 有人可以建议我哪里出了问题或者提供一个适当的解决方案吗?感谢您提前的帮助 :)

1
你需要处理23:45和00:10之间相差不到30分钟的情况吗? - Mark Byers
是的,不幸的是... 但大多数班次的开始时间为07:00、07:30、08:00、08:15、08:30等。 - Withdalot
鉴于克里斯和拉里·卢斯蒂格在下面讨论了需求,如果您包含一些示例数据和预期输出,可能会有所帮助。具体而言,7:10am、7:20am和7:45am是否应计为两个时间(因为7:45am距离7:10am超过30分钟)? - Tom H
@Tom:实际上,要求非常不同。 OP 不是在谈论两个启动之间的 30 分钟经过时间,而是将不同日期的班次开始时间相互比较(请参见我的答案下面 OP 的评论)。 - Larry Lustig
使用哪个数据库产品和版本? - Thomas
所以,为了明确起见,您是要求将时间从日期中分离出来,因为您正在寻找其他日期之间相差30分钟或更多的开始时间?因此,您正在寻找具有开始时间小于1410或大于1470分钟之间的天数,但仅在连续的天数内?如果不是连续的天数,则为1440 x nbr天? - MJB
5个回答

1
在等待确切需求的澄清时,我想提出另一种方法。我将列出它的优点和缺点...
如果开始时间通常是在某个特定时间左右(您的示例总是在半小时或整点),那么您可以将所有的开始时间分成“段”,然后计算不同“段”的数量。
例如00:00-00:30 = 段1 00:30-01:00 = 段2 ... 07:00-07:30 = 段15 ... 23:30-00:00 = 段48
要获取这些段,您只需要一个简单的(尽管相当冗长)情况语句。
这种方法的主要问题在于,当您的时间接近阈值时,它会失败。例如,07:29和07:31将位于两个不同的段中,但实际上只有2分钟的时间差。如果您是在大约相同的时间开始,您可以通过使您的段在每个小时的第15和45分钟开始和结束来稍微减轻这种情况。然后,如果开始时间都在段的中间,那么您就可以得到大部分正确的结果。
然而,在我的脑海中,这个问题并不是SQL适合解决的问题,所以如果您可以用其他语言解决它,那可能更好...

你可能可以通过一些巧妙的连接在SQL中完成它,但我无法编写可靠的SQL代码... 从算法上讲,你需要执行以下步骤。

1)选择当天最早的开始时间,并将其称为第一个开始时间。 2)选择下一个最早的时间,该时间比前一步骤的时间晚至少30分钟。 3)重复步骤2,直到没有时间可选。 4)计算时间。

从SQL的角度来看,这个问题的难点在于它试图基于先前的行创建数据,这意味着必须使用游标循环遍历时间并将内容存储在变量中。


这个问题有基于集合的解决方案,即使不使用时间段,尽管我喜欢你关于时间段的想法,因为它似乎很适合这个问题。 - Tom H
@Tom H.:是的,我认为这听起来像是固定的起始点的轮班工作,而不仅仅是任何时间,所以我认为值得记录。当你谈论其他基于集合的解决方案时,我很感兴趣... - Chris

0

[更新:根据海报在此答案的评论中对问题的澄清,我解决这个答案的问题显然不是海报试图解决的问题。我保留答案以展示另一个问题的解决方案,并且不删除澄清问题陈述的评论]

将问题分为两部分:识别“独特”的(在30分钟内)开始时间,然后计数它们。第一部分是我认为您遇到麻烦的部分。以下是一种方法:

SELECT employeecode, starttime FROM schedule S1
    WHERE NOT EXISTS (SELECT * FROM schedule S2 
        WHERE S2.employeecode = S1.employeecode AND
              S2.starttime > DATEADD(mi, -29, S1.starttime)

一些注释:
  • 我从您原始查询中复制了日期数学逻辑,而不是查找语法。

  • 我假设starttime为DATETIME。

  • 我使用29分钟,以便如果starttimes相隔30分钟或更多,则他们将获得奖金(如您的问题说明所述)。实际上,您应该通过使用秒进行日期数学计算,并减去(29 * 60)+59来完成此操作。我的版本对员工略微慷慨,超过了您的问题说明所指定的范围。

  • 您可以将此查询封装在视图或内部查询中,并执行以下操作(假设它是视图):

    SELECT employeecode, count() FROM unique_starts_view WHERE starttime BETWEEN (beginning of period) AND (end of period) GROUP BY employeecode HAVING COUNT() >= 3

  • NOT EXISTS技术可能会很慢,因此最好将该查询限制在您感兴趣的时期内。


我尝试过使用NOT EXISTS,但遇到了一些问题,可能是由于其中包含的顺序和细节。我还受到限制,无法创建视图(!),因此内部查询可能是答案。 - Withdalot
如果时间非常接近,那么这样做不会出现返回不足的问题吗?例如7.00,7.20,7.40,8.00,8.20...它们彼此之间都不超过30分钟,因此如果我正确理解了您的SQL(这可能并非如此),它将无法返回所需的结果集... - Chris
@Chris:它将返回7:00的时间(假设数据库在6:30之后没有其他记录)。我认为这就是OP想要的 - 只有最后开始时间30分钟后的记录。至少,这是我对问题陈述的解释。我确实意识到他可能希望从您的列表中返回7:00、7:40和8:20。 - Larry Lustig
抱歉造成困惑-澄清问题: 员工每天至少开始一次(例如周一07:00),第二天(周二07:30)等等,直到(周三07:30,周四06:00,周五06:00)... 星期一算作1 星期二和星期三算作1 星期四和星期五算作1,随后这是支付第一个信用的日子(3个开始时间)。如果星期六的开始时间是08:00,则会再获得一笔信用。 - Withdalot
@Larry Lustig:看起来我的问题是我和你读题不一样。 :) - Chris
@Withdalot:根据您在评论中提供的更详细问题陈述,我非常怀疑我的解决方案是否符合您的要求(它是针对不同的问题设计的——更像是一个呼叫中心,在一天内频繁“开始”,但只有在上一个开始后30分钟才能获得信用)。我建议您更新您的问题,提供样本数据和期望的结果,以使问题更清晰明了。 - Larry Lustig

0

使用Chris提到的时间段(不要与时间强盗混淆):

CREATE TABLE Start_Periods
(
    begin_time    TIME        NOT NULL,
    end_time      TIME        NOT NULL,
    time_period   TINYINT     NOT NULL
    CONSTRAINT PK_Start_Periods PRIMARY KEY CLUSTERED (begin_time),
    CONSTRAINT CK_Start_Periods_begin_before_end CHECK (begin_time < end_time OR end_time = '00:00:00.000')
)
INSERT INTO Start_Periods (begin_time, end_time, time_period)
SELECT '00:00:00.000', '00:15:00.000', 1 UNION ALL
SELECT '00:15:00.000', '00:45:00.000', 2 UNION ALL
SELECT '00:45:00.000', '01:15:00.000', 3 UNION ALL
SELECT '01:15:00.000', '01:45:00.000', 4 UNION ALL
SELECT '01:45:00.000', '02:15:00.000', 5 UNION ALL
SELECT '02:15:00.000', '02:45:00.000', 6 UNION ALL
SELECT '02:45:00.000', '03:15:00.000', 7 UNION ALL
SELECT '03:15:00.000', '03:45:00.000', 8 UNION ALL
--...
SELECT '23:15:00.000', '23:45:00.000', 48 UNION ALL
SELECT '23:45:00.000', '00:00:00.000', 1

你的查询变成了:

SELECT
    SCH.employee_code,
    COUNT(DISTINCT SP.time_period) AS different_time_starts
FROM
    Schedule SCH
INNER JOIN Start_Periods SP ON
    SP.begin_time <= SCH.start_time AND
    SP.end_time > SCH.start_time
GROUP BY
    SCH.employee_code

0

根据您的原始帖子,我假设您的数据库产品是基于SQL Server的,但您没有提到版本。如果您正在使用SQL Server 2005及更高版本,则可以尝试以下操作:

With StartTimes As
    (
    Select StartDateTime 
        , Row_Number() Over( Order By StartDateTime ) As Seq 
        , DatePart(hh, StartDateTime) * 60 + DatePart(mi, StartDateTime) As Minutes
    From Schedule
    )
Select *
From StartTimes As S1
Where Exists(
            Select 1
            From StartTimes As S2
            Where S1.Seq <> 1
                And Abs(S2.Minutes - S1.Minutes) >= 30
            )

0

只是给你一个想法:

  • 仅用一周构建SQL
  • schedule进行SELF JOIN,同时相减每个开始时间
  • 现在为每个客户计算所有大于30分钟的DISTINCT差异->这将给出您的起始时间数量

这应该给您所需的结果。


这是非常好的反馈,谢谢大家。MicSim的理论似乎最接近我试图做的事情,但我能问一下具体的SQL吗?我对SQL太菜了,不知道怎么做... 进一步澄清:员工的开始时间可以在任何时间安排,但这不是基于时间范围的。员工可以被安排在07:15,只要下一个开始时间至少相差30分钟,它就符合“三个交替开始时间”班次之一,有助于获得信用。 - Withdalot

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接