获得调度程序数据库

时间:2017-08-02 19:36:40

标签: ms-access access-vba

我正在创建一个数据库,我希望在给定的日期和时间范围内每天为随机的2小时窗口安排任务。例如,Task1可以在凌晨5点到下午5点之间的任何时间从1月1日到1月12日运行。因此,数据库将在每个日期安排任务为随机的2小时窗口(不早于凌晨5点开始,不迟于晚上5点停止)。它可能会为Task1抛出类似的东西:

   Date      Start_Time      Stop_Time
  01 Jan        06:32          08:32
  02 Jan        14:24          16:24
  03 Jan        08:05          10:05
  04 Jan        12:17          14:17
  05 Jan        11:23          13:23
  06 Jan        12:53          14:53
  07 Jan        09:11          11:11
  08 Jan        05:27          05:27
  09 Jan        12:46          14:46

除了每个任务的条件(必须在日期范围内和给定时间范围内每天安排)之外,在任何一天的任何给定点上不得超过2个任务重叠,并且任何任务都不会遇到第二天(因此他们不能在晚上10点后开始)。

到目前为止,我的数据库执行此操作,虽然速度很慢,所以我想知道我使用的方法是否效率最高。

对于表格,我有一个(tblWindows),它基本上只包含一个名为WindowStart的列,该列填充了当天的每一分钟,从00:00开始到23:59结束。从字面上看,有1440条记录 - 每天每分钟有一条记录。

我有另一个表(tblTask​​Configs),我有每个要安排的任务的配置。这是我为每个要安排的任务指定开始/停止日期和开始/停止时间的地方。

最后,我的tblSchdTasks表会跟踪计划任务的时间。

关于操作,它是这样的:

Open tblTaskConfigs recordset.  For each TaskConfig record:
   1) Save the following into variables:
      - StartDate
      - StopDate
      - StartTime
      - StopTime
   2) For each date the task is to be scheduled on:
      A) Using DCount on tblSchdTasks, check if that task has already been scheduled for that date: 
         - Yes: Skip to the next date
         - No: 
            I) Open a query recordset (qryAvailWin) that contains available windows for that date that fall within the TaskConfig's start/stop times (times from tblWindows in which there are no more than 1 task that overlaps those times).
            II) Choose a random record from qryAvailWin to determine the start time of the Task to be scheduled.
            III) Open a tblSchdTasks recordset and create a new record for the task and it's randomly-selected time for that day.

所以,我打开了tblTask​​Configs记录集,并循环遍历每条记录。对于每个记录,每天都要安排任务,我打开另外2个记录集(qryAvailWin& tblSchdTasks)来检查可用时间并实际安排任务。

对于持续56天的1项任务,此操作大约需要108-113秒。我怀疑是因为它打开和关闭了总共113个记录集(1 +(56 x 2))。另外,qryAvailWin有三个参数(CurrDate,StartTime和StopTime),我需要在每次打开之前设置这些参数,以便它只显示与该日期和TaskConfig相关的可用窗口。

你能想到一种更有效的方法吗?

1 个答案:

答案 0 :(得分:0)

有一些时间,所以我编写了解决问题的方法。 既然你没有给我很多关于细节的工作,你将不得不填写很多空白,例如变量名和WHERE标准。

首先,2个辅助函数用于选择随机数和日期:

Public Function RandomRange(Lower As Double, Upper As Double, Optional IncludeDecimals As Boolean = True) As Double
    'Returns a random number between Lower and Upper, either with or without decimals
    RandomRange = Lower + (Rnd() * (Upper - Lower + IIf(IncludeDecimals, 0, 1)))
    If IncludeDecimals = True Then Exit Function
    RandomRange = Int(RandomRange)
End Function

Public Function RandomTime(Lower As Date, Upper As Date) As Date
    'Returns a random time between Lower and Upper
    Dim randomTimeDbl As Double
    randomTimeDbl = CDbl(Lower) + (Rnd() * CDbl(Upper - Lower))
    RandomTime = CDate(randomTimeDbl)
End Function

然后,计划任务间隔天的实际逻辑:

Public Function ScheduleTasksBetweenDays(StartDate As Date, EndDate As Date, TaskIdentifier As Variant)
    'Open the scheduled tasks table
    Dim rsSchdTasks As Recordset
    Set rsSchdTasks = CurrentDb.OpenRecordset("SELECT * FROM tblSchdTasks SORT BY Date ASC, Start_Time ASC")

    'Second recordset for tasks only on a specific date
    Dim rsFiltered As Recordset

    'Create a collection to save valid start and end dates
    Dim startEndDates As Collection
    'And create a collection for overlapping times
    Dim overlappingTimes As Collection

    'Create two arrays to save start-end date pairs
    Dim startEndDatePair(2) As Date
    Dim previousTaskStartEndTime(2) As Date
    'Create an int to hold random values
    Dim randomInt As Integer

    'Make an iterator for a while loop, and one for the for loop
    Dim dateIterator As Date
    Dim i As Integer
    dateIterator = StartDate
    'Iterate through all the dates
    While dateIterator < EndDate

        'Set rsFiltered to all records of that date
        rsSchdTasks.Filter = "Date = #" & Format(dateIterator, "yyyy\/mm\/dd") & "#"
        'Open the filtered recordset
        Set rsFiltered = rsSchdTasks.OpenRecordset()

        'Use the already instantiated recordset instead of a DCount, if the task is already set, next iteration
        rsFiltered.FindFirst "TaskIdentifier = " & TaskIdentifier
        If Not rsFiltered.NoMatch Then GoTo NextDate
        'Start at the first already set taks, iterate through them
        rsFiltered.MoveFirst
        'Clean up the overlappingTimes collection
        Set overlappingTimes = New Collection
        'Initialize the first task as the previous one
        If Not rsFiltered.EOF Then
            previousTaskStartEndTime(1) = rsFiltered.Fields("Start_Time")
            previousTaskStartEndTime(2) = rsFiltered.Fields("End_Time")
            rsFiltered.MoveNext
        End If
        Do While Not rsFiltered.EOF
            'If the start time of the next task is less than the end time
            If previousTaskStartEndTime(2) > rsFiltered.Fields("Start_Time") Then
                'The overlapping segment is from end of previous to start of new
                startEndDatePair(1) = rsFiltered.Fields("Start_Time")
                startEndDatePair(2) = previousTaskStartEndTime(2)
                'Add those that interval to the collection of invalid intervals
                overlappingTimes.Add startEndDatePair
            End If
            previousTaskStartEndTime(1) = rsFiltered.Fields("Start_Time")
            previousTaskStartEndTime(2) = rsFiltered.Fields("End_Time")
            rsFiltered.MoveNext
        Loop
        'Now, we have a collection of all times that can't overlap with the task

        'Lets set the next collection of possible start times

        'First possible start time is 5 AM
        startEndDatePair(1) = #5:00:00 AM#
        'Open up a blank collection of possible valid intervals
        Set startEndDates = New Collection

        'Next step seems familliar, loop through the invalid times to calculate the valid ones
        For i = 1 To overlappingTimes.Count
            'If the start of the next task is more than 2 hours away than the possible start time, we can plan it before this task
            If startEndDatePair(1) + #2:00:00 AM# < overlappingTimes(i)(1) Then
                'Set the maximum start time to 2 hours before the next task
                startEndDatePair(2) = overlappingTimes(i)(1) - #2:00:00 AM#
                'Add it to the collection of possible times
                startEndDates.Add startEndDatePair
            End If
            'Set the next possible valid interval to be behind this task
            startEndDatePair(1) = overlappingTimes(i)(2)
        Next i
        'All unchanged since last edit here

        'Check if we can put our task behind the last one, if so, last possible start time is 10 PM
        If startEndDatePair(1) < #10:00:00 PM# Then
                'Set it's maximum end time
                startEndDatePair(2) = #10:00:00 PM#
                startEndDates.Add startEndDatePair
        End If

        'Now, we have an array with all possible intervals where our task can start at
        If startEndDates.Count = 0 Then
            'Apparently, this day is already full and we can't plan the task. Do something adequate for that scenario
            GoTo NextDate
        End If

        'pick a random interval from our intervals array
        randomInt = RandomRange(1, startEndDates.Count, False)

        'Assign our array to that interval
        startEndDatePair(1) = startEndDates(randomInt)(1)
        startEndDatePair(2) = startEndDates(randomInt)(1)
        'update the schdTasks table
        rsSchdTasks.Edit
        rsSchdTasks.AddNew
        rsSchdTasks.Fields("Date") = dateIterator
        'Pick a random start time within our interval
        rsSchdTasks.Fields("Start_Time") = RandomTime(startEndDatePair(1), startEndDatePair(2))
        'End = 2 hours later
        rsSchdTasks.Fields("End_Time") = rsSchdTasks.Fields("Start_Time") + #2:00:00 AM#
        rsSchdTasks.Fields("TaskIdentifier") = TaskIdentifier
        rsSchdTasks.Update
NextDate:
        dateIterator = dateIterator + 1
    Loop
End Function

您可能会注意到,改进包括:

  • 您只需打开一次tblSchdTasks。每天过滤打开的表并将其放入新的记录集
  • 您不再使用DCount,而是检查已打开的表格
  • 不是只选择随机时间并检查它们是否有效,而是收集所有可能的开始和结束间隔,然后选择其中一个间隔,然后在该间隔内选择一个时间点
  • 不再需要(imho怪异的)tblWindows
  • 您现在在开始日期和结束日期中包含秒数
  • 你不再需要qryAvailWin,它正在寻找一张巨大的桌子并且可能占用你很多时间
  • 您已经打开了113个记录集并执行了56个DCounts(与记录集一样重),打开1记录集,过滤并搜索了56次

可能的缺点:

  • 如果间隔的大小确实不相等(一个是长的,另一个是短的),它们都有相同的机会被挑选出来完成任务。这不是很难修复,但是,我已经投入了足够的努力