比较DateTime结构以查找空闲槽

Sim*_*ely 16 c# sql sql-server datetime scheduling

我想搜索列表中所有用户的事件,并检索每个用户在上午7点到下午7点之间没有30分钟或更长时间的所有时间.

但是,如果方法被标记为"重复出现",即比特重复出现设置为1,那么该事件将在其开始后的52周内重复出现(因此时间不可用).在存储过程中处理这些事件的检索.

到目前为止,我的代码如下.我正在以正确的方式写这个程序吗?我不确定如何继续按照我的意愿返回功能.有人能帮我这个吗?

List<string> usernames = //List of usernames.
DateTime start = //DateTime for start of period you would like to schedule meeting
DateTime end = //DateTime for end of period
//int mins = //duration of meeting (must be 30mins or greater)

foreach (string username in usernames) {
   //retrieve events for this user
    var db = Database.Open("mPlan");
    List<DateTime> startTimes;
    List<DateTime  endTimes;
    // This stored procedure returns all events of a user in a given time period, 
    // including recurring events.
    var record = db.Query("EXEC dbo.GetEvents @0, @1, @2", username, start, end);
    foreach(var record in result) {
          startTimes.Add(record.event_start);
          endTimes.Add(record.event_end);
    }
    // so now I have a list of all start times and end times of events
    // for one user and could save all this data in a list
  }
Run Code Online (Sandbox Code Playgroud)

表结构:

DECLARE @Users TABLE
(    
    UserID   INT IDENTITY(1,1),
    Username VARCHAR(32)
);

DECLARE @Groups TABLE
(
    GroupID   INT IDENTITY(1,1),
    GroupName VARCHAR(32)
);

DECLARE @Membership TABLE
(
    UserID  INT,
    GroupID INT
);

DECLARE @event TABLE
(
    event_id    INT IDENTITY(1,1),
    event_start DATETIME,
    event_end   DATETIME,
    group_id    INT,
    recurring   BIT
);
Run Code Online (Sandbox Code Playgroud)

我想要的功能示例:

用户将数据库中的多个用户添加到列表中.用户选择他希望与所有这些用户会面的时间段.我的算法计算所有用户都可以免费使用的所有时间段(即适合在所有用户之间进行会议且超过30分钟的时间).

附加信息 :

示例案例:

  • 用户A尝试与用户B组织会议.所有时段都是免费的.我希望算法返回开始时间和结束时间的所有可能组合的DateTime start和DateTime结束,其中> 30mins和== duration(参数).

  • 典型案例:用户A计划在下午6点至晚上7点之间的所有时间进行活动.他试图与用户B组织一次会议,持续时间为1小时.用户B没有组织任何活动 - 返回DateTime 6PM和DateTime 7pm以指示会议的开始和结束时间.

  • 重复案例:用户A在周一下午5点至下午6点举行定期活动.他试图在六个星期的星期一组织一次2小时的会议.DateTime start和DateTime的所有组合结束,返回2小时的差异.下午5点至晚上7点的时间不会被退回,因为此事件是重复出现的,并且每周发生52周.

以下是存储过程,它检索设置时间段(开始,结束)的所有用户事件:

ALTER PROCEDURE dbo.GetEvents 
  @UserName VARCHAR(50), 
  @StartDate DATETIME, 
  @EndDate DATETIME 
AS 

BEGIN 
-- DEFINE A CTE TO GET ALL GROUPS ASSOCIATED WITH THE CURRENT USER 
;WITH Groups AS  
(   SELECT  GroupID  
    FROM    Membership  m 
            INNER JOIN Users u 
                ON m.UserID = u.UserID 
    WHERE   Username = @UserName 
    GROUP BY GroupID 
), 
-- DEFINE A CTE TO GET ALL EVENTS FOR THE GROUPS DEFINED ABOVE 
AllEvents AS 
(   SELECT  e.* 
    FROM    event e 
            INNER JOIN Groups m  
                ON m.GroupID = e.group_id 
    UNION ALL 
    SELECT  e.event_id, e.title, e.description, 
      DATEADD(WEEK, w.weeks, e.event_start), 
      DATEADD(WEEK, w.weeks, e.event_end), 
      e.group_id, e.recurring 
    FROM    event e 
            INNER JOIN Groups m  
                ON m.GroupID = e.group_id 
            CROSS JOIN  
            (   SELECT  ROW_NUMBER() OVER (ORDER BY Object_ID) AS weeks 
                FROM    SYS.OBJECTS 
            ) AS w 
    WHERE  e.recurring = 1 
)    
-- GET ALL EVENTS WHERE THE EVENTS FALL IN THE PERIOD DEFINED 
SELECT  * 
FROM    AllEvents 
WHERE   Event_Start >= @StartDate 
AND     Event_End <= @EndDate 

END 
Run Code Online (Sandbox Code Playgroud)

Aar*_*and 62

想象一下表:

USE tempdb;
GO

CREATE TABLE dbo.Users
(    
    UserID   INT IDENTITY(1,1),
    Username VARCHAR(32)
);

CREATE TABLE dbo.Groups
(
    GroupID   INT IDENTITY(1,1),
    GroupName VARCHAR(32)
);

CREATE TABLE dbo.Membership
(
    UserID  INT,
    GroupID INT
);

CREATE TABLE dbo.[event]
(
    event_id    INT IDENTITY(1,1),
    event_start DATETIME,
    event_end   DATETIME,
    group_id    INT,
    recurring   BIT
);
Run Code Online (Sandbox Code Playgroud)

并且想象一些样本数据并不那么难以提供:

INSERT dbo.Users(Username) 
    SELECT 'User A' UNION ALL SELECT 'User B';

INSERT dbo.Groups(GroupName) 
    SELECT 'Group 1' UNION ALL SELECT 'Group 2';

INSERT dbo.Membership(UserID, GroupID)
    SELECT 1,1 UNION ALL SELECT 2,2;


INSERT dbo.[event](event_start, event_end, group_id, recurring)
-- user A, almost all day meeting on a specific date
SELECT '20120313 07:00', '20120313 18:00', 1, 0 

-- user A, recurring meeting every Monday
UNION ALL SELECT '20120312 17:00', '20120312 18:00', 1, 1 

-- user A, recurring meeting every Tuesday (future)
UNION ALL SELECT '20120327 14:00', '20120327 15:00', 1, 1; 
GO
Run Code Online (Sandbox Code Playgroud)

现在我们可以构建这个存储过程:

CREATE PROCEDURE dbo.GetPossibleMeetingTimes
    @AskingUserID INT,
    @TargetUserID INT,
    @Duration     INT,           -- in minutes!
    @StartDate    SMALLDATETIME, -- assumes date, no time!
    @EndDate      SMALLDATETIME  -- again - date, no time!
AS
BEGIN
    SET NOCOUNT ON;

    ;WITH dRange(d) AS
    (
        -- get the actual dates in the requested range
        -- limited to number of rows in sys.objects

        SELECT TOP (DATEDIFF(DAY, @StartDate, @EndDate)+1) 
            DATEADD(DAY, n-1, @StartDate)
         FROM (SELECT n = ROW_NUMBER() OVER (ORDER BY [object_id])
          FROM sys.objects) AS x
    ), possible(ds, de) AS
    (
        -- get all the timeslots of @Duration minutes 
        -- between 7:00 AM and 7:00 PM for each day in 
        -- the range - these are all *potential* slots

        SELECT DATEADD(MINUTE, 30*rn, DATEADD(HOUR, 7, dRange.d)),
            DATEADD(MINUTE, 30*rn + @Duration, DATEADD(HOUR, 7, dRange.d))
        FROM (SELECT TOP (720/30) rn = ROW_NUMBER() OVER
        (ORDER BY [object_id])-1 FROM sys.objects) AS x
        CROSS JOIN dRange
    )
    SELECT p.ds, p.de FROM possible AS p 
    WHERE p.de <= DATEADD(HOUR, 19, DATEADD(DAY, DATEDIFF(DAY, 0, p.de), 0)) 
    AND NOT EXISTS 
    (
        SELECT 1 FROM 
        (
            -- filter down to users with events on the days in the range

            SELECT group_id, event_start, event_end
                FROM dbo.[event] 
                WHERE event_start >= @StartDate 
                AND event_start < DATEADD(DAY, 1, @EndDate)
            UNION ALL 

            -- also include users with recurring events on same weekday(s)
            -- normalized to the matching day in the range

            SELECT group_id, 
              event_start = DATEADD(DAY, DATEDIFF(DAY, event_start, p.ds), event_start),
              event_end   = DATEADD(DAY, DATEDIFF(DAY, event_end,   p.ds), event_end)
            FROM dbo.[event]
            WHERE recurring = 1 
            AND event_start <= DATEADD(DAY, 1, @EndDate) -- ignore future events
                    AND event_start >= DATEADD(WEEK, -52, @EndDate) -- 52 weeks out 
            AND DATEDIFF(DAY, event_start, p.ds) % 7 = 0 -- same weekday
        ) AS sub
        WHERE sub.group_id IN 
        (
            -- this checks that events are within previously scheduled times

            SELECT GroupID FROM dbo.Membership
              WHERE UserID IN (@AskingUserID, @TargetUserID)
              AND (p.de > sub.event_start AND p.ds < sub.event_end)
        )
    )
    ORDER BY p.ds, p.de;
END
GO
Run Code Online (Sandbox Code Playgroud)

示例调用:

-- Case 1: User A tries to meet with User B on a day where 
-- both schedules are clear.

EXEC dbo.GetPossibleMeetingTimes
    @AskingUserID = 1,
    @TargetUserID = 2,
    @Duration     = 30,
    @StartDate    = '20120314', -- no events for either user
    @EndDate      = '20120314';
Run Code Online (Sandbox Code Playgroud)

结果:

没有任何用户的事件

-- Case 2: User A tries to meet with User B for an hour, on 
-- a day where user A has meetings from 7 AM to 6 PM.

EXEC dbo.GetPossibleMeetingTimes
    @AskingUserID = 1,
    @TargetUserID = 2,
    @Duration     = 60,
    @StartDate    = '20120313', -- user A has an almost all-day event
    @EndDate      = '20120313';
Run Code Online (Sandbox Code Playgroud)

结果:

用户A几乎整天都很忙

-- Case 3: User A tries to meet with User B for two hours, on 
-- a weekday where User A has a recurring meeting from 5-6 PM

EXEC dbo.GetPossibleMeetingTimes
    @AskingUserID = 1,
    @TargetUserID = 2,
    @Duration     = 120,        
    @StartDate    = '20120319', -- user A has a recurring meeting
    @EndDate      = '20120319';
Run Code Online (Sandbox Code Playgroud)

结果:

用户A有一个定期会议

现在请注意,我处理了您未考虑或未提及的几个因素(例如将来开始的重复事件).另一方面,我也没有处理其他一些因素(例如,夏令时,如果它可能会对此产生影响),并且没有测试所有可能的情况(例如,已经存在的同一天的多个事件).

我测试过如果你传入一个范围(例如2012-03-12 - > 2012-03-14),你基本上只会得到上述结果的联合,大致相同的时间段可用(这些时间根据持续时间而有所不同)当然).重要的是停电时段得到尊重.我没有测试将来开始重复事件的情况的逻辑,并且提供的日期范围包括事件的第一个实例之前和之后的工作日.

如果任何情况对您不起作用,那么这就是为什么使用样本数据向您展示所有案例非常重要,而不是单词问题,并解释给定数据的查询所需结果.

编辑 - 要处理超过2个用户,您只需要进行一些更改.如果添加拆分功能,如下所示:

CREATE FUNCTION dbo.SplitInts( @List VARCHAR(MAX) )
RETURNS TABLE
AS
   RETURN 
   ( SELECT Item = CONVERT(INT, Item) FROM (
      SELECT Item = x.i.value('(./text())[1]', 'INT') FROM (
       SELECT [XML] = CONVERT(XML, '<i>' + REPLACE(@List, ',', '</i><i>') 
         + '</i>').query('.')) AS a CROSS APPLY [XML].nodes('i') AS x(i)) AS y
       WHERE Item IS NOT NULL
   );
Run Code Online (Sandbox Code Playgroud)

现在对存储过程进行了非常小的更改(我遗漏了未更改的位):

ALTER PROCEDURE dbo.GetPossibleMeetingTimes
    @UserIDList   VARCHAR(MAX),  -- removed other two parameters
    @Duration     INT,           
    @StartDate    SMALLDATETIME, 
    @EndDate      SMALLDATETIME  
AS
...
        WHERE sub.group_id IN -- changed the code within this subquery
        (
            SELECT GroupID FROM dbo.Membership AS m
              INNER JOIN dbo.SplitInts(@UserIDList) AS i
              ON m.UserID = i.Item
              WHERE (p.de > sub.event_start AND p.ds < sub.event_end)
        )
...
Run Code Online (Sandbox Code Playgroud)

那么你的电话会稍微改变为:

EXEC dbo.GetPossibleMeetingTimes
     @UserIDList = '1,2,3,4,5',
     @Duration   = 30,
     @StartDate  = '20120314',
     @EndDate    = '20120314';
Run Code Online (Sandbox Code Playgroud)

只需确保请求者包含在以逗号分隔的列表中.

PS这个附录未经测试.

  • 在他真实的世界里,你可能会向某人收取半天的工作费用.在这里你得到我的+1 :). (17认同)
  • 谢谢Mikael.我开始怀疑我们的个人资料是否应该有PayPal链接.:-) (12认同)
  • @SimonKiely我已经更新了解决方案,允许超过2个用户. (3认同)
  • +1超出职责要求! (3认同)
  • 对不起,现在试试.混合两个功能. (2认同)
  • 不,这是SQL可以回答的问题.为什么要将所有数据推送到应用程序以让应用程序回答它?如果有多个应用程序怎么办? (2认同)