我想搜索列表中所有用户的事件,并检索每个用户在上午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
}
表格结构:
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
);
我想要的功能示例:
用户将数据库中的多个用户添加到列表中。用户选择他希望与所有这些用户会面的时间段。我的算法计算所有用户都可以免费使用的所有时间段(即适合在所有用户之间召开会议并且> 30分钟的时间)。
其他信息:
示例案例:
用户A尝试与用户B组织会议。所有时间段都是 自由。我希望算法返回DateTime开始和 DateTime结束了开始时间和结束的所有可能组合 时间> 30分钟和==持续时间(参数)。
典型案例:用户A计划在下午6点之前的所有时间进行活动 - 晚上7点。他试图与用户B组织一次会议 1小时。用户B没有组织任何活动 - 日期时间6PM和 返回DateTime 7pm以指示开始和结束时间 会议。
重复案例:用户A在周一下午5点至下午6点有一个定期活动。 他试图在六个星期的星期一组织一次2小时的会议。所有 返回DateTime start和DateTime end的组合,其中返回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
答案 0 :(得分: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
);
想象一些样本数据并不那么难以提供:
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
现在我们可以构建这个存储过程:
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
示例电话:
-- 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';
结果:
-- 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';
结果:
-- 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';
结果:
现在请注意,我处理了您未考虑或未提及的几个因素(例如将来开始的重复事件)。另一方面,我也没有处理其他一些因素(例如,夏令时,如果它可能会对此产生影响),并且没有测试所有可能的情况(例如,已经存在的同一天的多个事件)。
我测试过,如果你传入一个范围(例如2012-03-12 - &gt; 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
);
现在对存储过程进行了非常小的更改(我遗漏了未更改的位):
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)
)
...
那么你的电话会稍微改变为:
EXEC dbo.GetPossibleMeetingTimes
@UserIDList = '1,2,3,4,5',
@Duration = 30,
@StartDate = '20120314',
@EndDate = '20120314';
只需确保请求者包含在以逗号分隔的列表中。
PS本附录未经测试。