在T-SQL中计算相交的时间间隔

时间:2016-08-18 03:42:27

标签: sql sql-server tsql sql-server-2012

CODE:

CREATE TABLE #Temp1 (CoachID INT, BusyST DATETIME, BusyET DATETIME)
CREATE TABLE #Temp2 (CoachID INT, AvailableST DATETIME, AvailableET DATETIME)

INSERT INTO #Temp1 (CoachID, BusyST, BusyET)
SELECT 1,'2016-08-17 09:12:00','2016-08-17 10:11:00'
UNION
SELECT 3,'2016-08-17 09:30:00','2016-08-17 10:00:00'
UNION
SELECT 4,'2016-08-17 12:07:00','2016-08-17 13:10:00'

INSERT INTO #Temp2 (CoachID, AvailableST, AvailableET)
SELECT 1,'2016-08-17 09:07:00','2016-08-17 11:09:00'
UNION
SELECT 2,'2016-08-17 09:11:00','2016-08-17 09:30:00'
UNION
SELECT 3,'2016-08-17 09:24:00','2016-08-17 13:08:00'
UNION
SELECT 1,'2016-08-17 11:34:00','2016-08-17 12:27:00'
UNION
SELECT 4,'2016-08-17 09:34:00','2016-08-17 13:00:00'
UNION
SELECT 5,'2016-08-17 09:10:00','2016-08-17 09:55:00'

--RESULT-SET QUERY GOES HERE

DROP TABLE #Temp1
DROP TABLE #Temp2

期望的输出:

CoachID CanCoachST                  CanCoachET                  NumOfCoaches
1       2016-08-17 09:12:00.000     2016-08-17 09:24:00.000     2 --(ID2 = 2,5)
1       2016-08-17 09:24:00.000     2016-08-17 09:30:00.000     3 --(ID2 = 2,3,5)
1       2016-08-17 09:30:00.000     2016-08-17 09:34:00.000     1 --(ID2 = 5)
1       2016-08-17 09:34:00.000     2016-08-17 09:55:00.000     2 --(ID2 = 4,5)
1       2016-08-17 09:55:00.000     2016-08-17 10:00:00.000     1 --(ID2 = 4)
1       2016-08-17 10:00:00.000     2016-08-17 10:11:00.000     2 --(ID2 = 3,4)
3       2016-08-17 09:30:00.000     2016-08-17 09:34:00.000     1 --(ID2 = 5)
3       2016-08-17 09:34:00.000     2016-08-17 09:55:00.000     2 --(ID2 = 4,5)
3       2016-08-17 09:55:00.000     2016-08-17 10:00:00.000     1 --(ID2 = 4)
4       2016-08-17 12:07:00.000     2016-08-17 12:27:00.000     2 --(ID2 = 1,3)
4       2016-08-17 12:27:00.000     2016-08-17 13:08:00.000     1 --(ID2 = 3)
4       2016-08-17 13:08:00.000     2016-08-17 13:10:00.000     0 --(No one is available)

目标: 将#Temp1视为团队教练表(ID1)及其会议时间(ST1 =会议开始时间和ET1 =会议结束时间)。 将#Temp2视为团队教练表(ID2)及其总可用时间(ST2 =可用开始时间和ET2 =可用结束时间)。

现在,我们的目标是找到#Temp2的所有可能的教练,这些教练可以在#Temp1的教练会议期间接受教练。

因此,例如,对于教练ID1 = 1,谁在9:12和10:11之间忙碌(数据可以跨越多天,如果该信息很重要),我们有 教练ID2 = 2和5可以在9:12和9:24之间教练 ,教练ID2 = 2,3和5,可以在9:24到9:30之间指导 ,教练ID2 = 5,可以在9:30到9:34之间指导 ,教练ID2 = 4和5,可以在9:34和9:55之间指导 ,教练ID2 = 4,可以在9:55到10:00之间指导 和教练ID2 = 3和4可以在10:00到10:11之间进行指导(注意ID 3如何,虽然在9:24和13:08之间的#Temp2表中可用,但它不能指导ID1 = 1点在9点24分到10点之间,因为它也在9点30分到10点之间忙碌。

到目前为止我的努力:到目前为止只处理打破#Temp1的时间段。还需要弄清楚A)如何从输出中删除非繁忙时间窗口B)添加字段/将其映射到右边的T1 CoachID。

;WITH ED
AS (SELECT BusyET, CoachID FROM #Temp1  
    UNION ALL   
    SELECT BusyST, CoachID FROM #Temp1
    )
,Brackets
AS (SELECT MIN(BusyST) AS BusyST
        ,(  SELECT MIN(BusyET)
            FROM ED e
            WHERE e.BusyET > MIN(BusyST)
            ) AS BusyET
    FROM #Temp1 T   
    UNION ALL   
    SELECT B.BusyET
        ,e.BusyET
    FROM Brackets B
    INNER JOIN ED E ON B.BusyET < E.BusyET
    WHERE NOT EXISTS (
            SELECT *
            FROM ED E2
            WHERE E2.BusyET > B.BusyET
                AND E2.BusyET < E.BusyET
            )
    )
SELECT *
FROM Brackets
ORDER BY BusyST;

我想我需要加入比较两个表之间的ST / ET日期,其中ID彼此不匹配。但我很难弄清楚如何实际获得会议时间窗口和唯一计数。

更新了更好的架构/数据集。另请注意,即使CoachID 4未被安排&#34;在最后几分钟,他仍然被列为忙碌状态。并且可能存在这样的情况:在这种情况下没有其他人可以工作,在这种情况下,我们可以返回0 cnt记录(如果它真的很复杂,则不返回它。)

同样,我们的目标是找到所有可用CoachID的计数和组合,以及可以指导忙表中列出的CoachID的可用时间窗口。

更新了更多样本描述匹配样本数据。

7 个答案:

答案 0 :(得分:2)

本回答中的查询受到了Itzik Ben-Gan的Packing Intervals的启发。

起初,我并不了解要求的完整复杂性,并认为Table1Table2中的时间间隔不重叠。我认为同一个教练不能同时忙碌和可用。

事实证明我的假设是错误的,因此我在下面留下的查询的第一个变体必须通过初步步骤进行扩展,从Table1中存储的区间中减去存储在Table2中的所有区间。 {1}}。

它使用了类似的想法。每个开始的&#34;可用&#34;间隔标记为+1 EventType,并且&#34;可用&#34; interval用-1 EventType标记。对于&#34;忙碌&#34;间隔标记反转。 &#34;忙&#34; interval以-1开头,以+1结束。这是在C1_Subtract

中完成的

然后运行总计告诉我们&#34;真正&#34;可用间隔为(C2_Subtract)。最后,CTE_Available只留下真正的&#34;可用间隔。

示例数据

我添加了几行来说明如果没有可用的教练会发生什么。我还添加了CoachID=9,它不在查询的第一个变体的初始结果中。

CREATE TABLE #Temp1 (CoachID INT, BusyST DATETIME, BusyET DATETIME);
CREATE TABLE #Temp2 (CoachID INT, AvailableST DATETIME, AvailableET DATETIME);
-- Start time is inclusive
-- End time is exclusive

INSERT INTO #Temp1 (CoachID, BusyST, BusyET) VALUES
(1, '2016-08-17 09:12:00','2016-08-17 10:11:00'),
(3, '2016-08-17 09:30:00','2016-08-17 10:00:00'),
(4, '2016-08-17 12:07:00','2016-08-17 13:10:00'),

(6, '2016-08-17 15:00:00','2016-08-17 16:00:00'),
(9, '2016-08-17 15:00:00','2016-08-17 16:00:00');

INSERT INTO #Temp2 (CoachID, AvailableST, AvailableET) VALUES
(1,'2016-08-17 09:07:00','2016-08-17 11:09:00'),
(2,'2016-08-17 09:11:00','2016-08-17 09:30:00'),
(3,'2016-08-17 09:24:00','2016-08-17 13:08:00'),
(1,'2016-08-17 11:34:00','2016-08-17 12:27:00'),
(4,'2016-08-17 09:34:00','2016-08-17 13:00:00'),
(5,'2016-08-17 09:10:00','2016-08-17 09:55:00'),

(7,'2016-08-17 15:10:00','2016-08-17 15:20:00'),
(8,'2016-08-17 15:15:00','2016-08-17 15:25:00'),
(7,'2016-08-17 15:40:00','2016-08-17 15:55:00'),
(9,'2016-08-17 15:05:00','2016-08-17 15:07:00'),
(9,'2016-08-17 15:40:00','2016-08-17 16:55:00');

CTE_Available

的中间结果
+---------+-------------------------+-------------------------+
| CoachID |       AvailableST       |       AvailableET       |
+---------+-------------------------+-------------------------+
|       1 | 2016-08-17 09:07:00.000 | 2016-08-17 09:12:00.000 |
|       1 | 2016-08-17 10:11:00.000 | 2016-08-17 11:09:00.000 |
|       1 | 2016-08-17 11:34:00.000 | 2016-08-17 12:27:00.000 |
|       2 | 2016-08-17 09:11:00.000 | 2016-08-17 09:30:00.000 |
|       3 | 2016-08-17 09:24:00.000 | 2016-08-17 09:30:00.000 |
|       3 | 2016-08-17 10:00:00.000 | 2016-08-17 13:08:00.000 |
|       4 | 2016-08-17 09:34:00.000 | 2016-08-17 12:07:00.000 |
|       5 | 2016-08-17 09:10:00.000 | 2016-08-17 09:55:00.000 |
|       7 | 2016-08-17 15:10:00.000 | 2016-08-17 15:20:00.000 |
|       7 | 2016-08-17 15:40:00.000 | 2016-08-17 15:55:00.000 |
|       8 | 2016-08-17 15:15:00.000 | 2016-08-17 15:25:00.000 |
|       9 | 2016-08-17 16:00:00.000 | 2016-08-17 16:55:00.000 |
+---------+-------------------------+-------------------------+

现在,我们可以在查询的第一个变体中使用CTE_Available的这些中间结果,而不是#Temp2。请参阅查询的第一个变体下面的详细说明。

完整查询

WITH
C1_Subtract
AS
(
    SELECT
        CoachID
        ,AvailableST AS ts
        ,+1 AS EventType
    FROM #Temp2

    UNION ALL

    SELECT
        CoachID
        ,AvailableET AS ts
        ,-1 AS EventType
    FROM #Temp2

    UNION ALL

    SELECT
        CoachID
        ,BusyST AS ts
        ,-1 AS EventType
    FROM #Temp1

    UNION ALL

    SELECT
        CoachID
        ,BusyET AS ts
        ,+1 AS EventType
    FROM #Temp1
)
,C2_Subtract AS
(
    SELECT
        C1_Subtract.*
        ,SUM(EventType)
            OVER (
            PARTITION BY CoachID
            ORDER BY ts, EventType DESC
            ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
        AS cnt
        ,LEAD(ts) 
            OVER (
            PARTITION BY CoachID
            ORDER BY ts, EventType DESC)
        AS NextTS
    FROM C1_Subtract
)
,CTE_Available
AS
(
    SELECT
        C2_Subtract.CoachID
        ,C2_Subtract.ts AS AvailableST
        ,C2_Subtract.NextTS AS AvailableET
    FROM C2_Subtract
    WHERE cnt > 0
)
,CTE_Intervals
AS
(
    SELECT
        TBusy.CoachID AS BusyCoachID
        ,TBusy.BusyST
        ,TBusy.BusyET
        ,CA.CoachID AS AvailableCoachID
        ,CA.AvailableST
        ,CA.AvailableET
        -- max of start time
        ,CASE WHEN CA.AvailableST < TBusy.BusyST
        THEN TBusy.BusyST
        ELSE CA.AvailableST 
        END AS ST
        -- min of end time
        ,CASE WHEN CA.AvailableET > TBusy.BusyET
        THEN TBusy.BusyET
        ELSE CA.AvailableET
        END AS ET
    FROM
        #Temp1 AS TBusy
        CROSS APPLY
        (
            SELECT
                TAvailable.*
            FROM
                CTE_Available AS TAvailable
            WHERE
                -- the same coach can't be available and busy
                TAvailable.CoachID <> TBusy.CoachID
                -- intervals intersect
                AND TAvailable.AvailableST < TBusy.BusyET
                AND TAvailable.AvailableET > TBusy.BusyST
        ) AS CA
)
,C1 AS
(
    SELECT
        BusyCoachID
        ,AvailableCoachID
        ,ST AS ts
        ,+1 AS EventType
    FROM CTE_Intervals

    UNION ALL

    SELECT
        BusyCoachID
        ,AvailableCoachID
        ,ET AS ts
        ,-1 AS EventType
    FROM CTE_Intervals

    UNION ALL

    SELECT
        CoachID AS BusyCoachID
        ,CoachID AS AvailableCoachID
        ,BusyST AS ts
        ,+1 AS EventType
    FROM #Temp1

    UNION ALL

    SELECT
        CoachID AS BusyCoachID
        ,CoachID AS AvailableCoachID
        ,BusyET AS ts
        ,-1 AS EventType
    FROM #Temp1
)
,C2 AS
(
    SELECT
        C1.*
        ,SUM(EventType)
            OVER (
            PARTITION BY BusyCoachID
            ORDER BY ts, EventType DESC, AvailableCoachID
            ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
        - 1 AS cnt
        ,LEAD(ts) 
            OVER (
            PARTITION BY BusyCoachID 
            ORDER BY ts, EventType DESC, AvailableCoachID) 
        AS NextTS
    FROM C1
)
SELECT
    BusyCoachID AS CoachID
    ,ts AS CanCoachST
    ,NextTS AS CanCoachET
    ,cnt AS NumOfCoaches
FROM C2
WHERE ts <> NextTS
ORDER BY BusyCoachID, CanCoachST
;

最终结果

+---------+-------------------------+-------------------------+--------------+
| CoachID |       CanCoachST        |       CanCoachET        | NumOfCoaches |
+---------+-------------------------+-------------------------+--------------+
|       1 | 2016-08-17 09:12:00.000 | 2016-08-17 09:24:00.000 |            2 |
|       1 | 2016-08-17 09:24:00.000 | 2016-08-17 09:30:00.000 |            3 |
|       1 | 2016-08-17 09:30:00.000 | 2016-08-17 09:34:00.000 |            1 |
|       1 | 2016-08-17 09:34:00.000 | 2016-08-17 09:55:00.000 |            2 |
|       1 | 2016-08-17 09:55:00.000 | 2016-08-17 10:00:00.000 |            1 |
|       1 | 2016-08-17 10:00:00.000 | 2016-08-17 10:11:00.000 |            2 |
|       3 | 2016-08-17 09:30:00.000 | 2016-08-17 09:34:00.000 |            1 |
|       3 | 2016-08-17 09:34:00.000 | 2016-08-17 09:55:00.000 |            2 |
|       3 | 2016-08-17 09:55:00.000 | 2016-08-17 10:00:00.000 |            1 |
|       4 | 2016-08-17 12:07:00.000 | 2016-08-17 12:27:00.000 |            2 |
|       4 | 2016-08-17 12:27:00.000 | 2016-08-17 13:08:00.000 |            1 |
|       4 | 2016-08-17 13:08:00.000 | 2016-08-17 13:10:00.000 |            0 |
|       6 | 2016-08-17 15:00:00.000 | 2016-08-17 15:10:00.000 |            0 |
|       6 | 2016-08-17 15:10:00.000 | 2016-08-17 15:15:00.000 |            1 |
|       6 | 2016-08-17 15:15:00.000 | 2016-08-17 15:20:00.000 |            2 |
|       6 | 2016-08-17 15:20:00.000 | 2016-08-17 15:25:00.000 |            1 |
|       6 | 2016-08-17 15:25:00.000 | 2016-08-17 15:40:00.000 |            0 |
|       6 | 2016-08-17 15:40:00.000 | 2016-08-17 15:55:00.000 |            1 |
|       6 | 2016-08-17 15:55:00.000 | 2016-08-17 16:00:00.000 |            0 |
|       9 | 2016-08-17 15:00:00.000 | 2016-08-17 15:10:00.000 |            0 |
|       9 | 2016-08-17 15:10:00.000 | 2016-08-17 15:15:00.000 |            1 |
|       9 | 2016-08-17 15:15:00.000 | 2016-08-17 15:20:00.000 |            2 |
|       9 | 2016-08-17 15:20:00.000 | 2016-08-17 15:25:00.000 |            1 |
|       9 | 2016-08-17 15:25:00.000 | 2016-08-17 15:40:00.000 |            0 |
|       9 | 2016-08-17 15:40:00.000 | 2016-08-17 15:55:00.000 |            1 |
|       9 | 2016-08-17 15:55:00.000 | 2016-08-17 16:00:00.000 |            0 |
+---------+-------------------------+-------------------------+--------------+

我建议创建以下索引以避免执行计划中的某些排序。

CREATE UNIQUE NONCLUSTERED INDEX [IX_CoachID_BusyST] ON #Temp1
(
    CoachID ASC,
    BusyST ASC
);

CREATE UNIQUE NONCLUSTERED INDEX [IX_CoachID_BusyET] ON #Temp1
(
    CoachID ASC,
    BusyET ASC
);

CREATE UNIQUE NONCLUSTERED INDEX [IX_CoachID_AvailableST] ON #Temp2
(
    CoachID ASC,
    AvailableST ASC
);

CREATE UNIQUE NONCLUSTERED INDEX [IX_CoachID_AvailableET] ON #Temp2
(
    CoachID ASC,
    AvailableET ASC
);

在实际数据上,瓶颈可能在其他地方,这可能取决于数据分布。查询相当复杂,如果没有实际数据进行调整就会产生过多的猜测。

查询的第一个变体

逐步运行查询,CTE-by-CTE并检查中间结果以查明其工作原理。

CTE_Intervals为我们提供了一个与繁忙间隔相交的可用间隔列表。 C1将开始和结束时间都放在与相应EventType相同的列中。这将帮助我们跟踪间隔开始或结束的时间。 总计EventType计算可用的教练数。 C1工会忙于教练,在没有教练的情况下正确统计案件。

WITH
CTE_Intervals
AS
(
    SELECT
        TBusy.CoachID AS BusyCoachID
        ,TBusy.BusyST
        ,TBusy.BusyET
        ,CA.CoachID AS AvailableCoachID
        ,CA.AvailableST
        ,CA.AvailableET
        -- max of start time
        ,CASE WHEN CA.AvailableST < TBusy.BusyST
        THEN TBusy.BusyST
        ELSE CA.AvailableST 
        END AS ST
        -- min of end time
        ,CASE WHEN CA.AvailableET > TBusy.BusyET
        THEN TBusy.BusyET
        ELSE CA.AvailableET
        END AS ET
    FROM
        #Temp1 AS TBusy
        CROSS APPLY
        (
            SELECT
                TAvailable.*
            FROM
                #Temp2 AS TAvailable
            WHERE
                -- the same coach can't be available and busy
                TAvailable.CoachID <> TBusy.CoachID
                -- intervals intersect
                AND TAvailable.AvailableST < TBusy.BusyET
                AND TAvailable.AvailableET > TBusy.BusyST
        ) AS CA
)
,C1 AS
(
    SELECT
        BusyCoachID
        ,AvailableCoachID
        ,ST AS ts
        ,+1 AS EventType
    FROM CTE_Intervals

    UNION ALL

    SELECT
        BusyCoachID
        ,AvailableCoachID
        ,ET AS ts
        ,-1 AS EventType
    FROM CTE_Intervals

    UNION ALL

    SELECT
        CoachID AS BusyCoachID
        ,CoachID AS AvailableCoachID
        ,BusyST AS ts
        ,+1 AS EventType
    FROM #Temp1

    UNION ALL

    SELECT
        CoachID AS BusyCoachID
        ,CoachID AS AvailableCoachID
        ,BusyET AS ts
        ,-1 AS EventType
    FROM #Temp1
)
,C2 AS
(
    SELECT
        C1.*
        ,SUM(EventType)
            OVER (
            PARTITION BY BusyCoachID
            ORDER BY ts, EventType DESC, AvailableCoachID
            ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
        - 1 AS cnt
        ,LEAD(ts) 
            OVER (
            PARTITION BY BusyCoachID 
            ORDER BY ts, EventType DESC, AvailableCoachID) 
        AS NextTS
    FROM C1
)
SELECT
    BusyCoachID AS CoachID
    ,ts AS CanCoachST
    ,NextTS AS CanCoachET
    ,cnt AS NumOfCoaches
FROM C2
WHERE ts <> NextTS
ORDER BY BusyCoachID, CanCoachST
;

DROP TABLE #Temp1;
DROP TABLE #Temp2;

<强>结果

我为每一行添加了评论,其中包含可用教练的ID。

现在我理解为什么我的初步结果与预期结果不一样。

+---------+---------------------+---------------------+--------------+
| CoachID |       CanCoachST    |       CanCoachET    | NumOfCoaches |
+---------+---------------------+---------------------+--------------+
|       1 | 2016-08-17 09:12:00 | 2016-08-17 09:24:00 |            2 |  2,5
|       1 | 2016-08-17 09:24:00 | 2016-08-17 09:30:00 |            3 |  2,3,5
|       1 | 2016-08-17 09:30:00 | 2016-08-17 09:34:00 |            2 |  3,5
|       1 | 2016-08-17 09:34:00 | 2016-08-17 09:55:00 |            3 |  3,4,5
|       1 | 2016-08-17 09:55:00 | 2016-08-17 10:11:00 |            2 |  3,4
|       3 | 2016-08-17 09:30:00 | 2016-08-17 09:34:00 |            2 |  1,5
|       3 | 2016-08-17 09:34:00 | 2016-08-17 09:55:00 |            3 |  1,4,5
|       3 | 2016-08-17 09:55:00 | 2016-08-17 10:00:00 |            2 |  1,4
|       4 | 2016-08-17 12:07:00 | 2016-08-17 12:27:00 |            2 |  3,1
|       4 | 2016-08-17 12:27:00 | 2016-08-17 13:08:00 |            1 |  3
|       4 | 2016-08-17 13:08:00 | 2016-08-17 13:10:00 |            0 |  none
|       6 | 2016-08-17 15:00:00 | 2016-08-17 15:10:00 |            0 |  none
|       6 | 2016-08-17 15:10:00 | 2016-08-17 15:15:00 |            1 |  7
|       6 | 2016-08-17 15:15:00 | 2016-08-17 15:20:00 |            2 |  7,8
|       6 | 2016-08-17 15:20:00 | 2016-08-17 15:25:00 |            1 |  8
|       6 | 2016-08-17 15:25:00 | 2016-08-17 15:40:00 |            0 |  none
|       6 | 2016-08-17 15:40:00 | 2016-08-17 15:55:00 |            1 |  7
|       6 | 2016-08-17 15:55:00 | 2016-08-17 16:00:00 |            0 |  none
+---------+---------------------+---------------------+--------------+

答案 1 :(得分:1)

我能说的最好,你要找的是这样的:

<span id="numactions">5</span>

<button type="button" onClick="clickME()">Click me</button>

<script>
    var clicks = document.getElementById("numactions");

    function clickME() {
        clicks -= 1;
        document.getElementById("numactions").innerHTML = clicks;
    }
</script>

CTE会将你的#Temp1教练分成半小时的时段,然后我假设你想找到#Temp2中所有不是同一个ID并且开始转换的人更早或同时或在同一时间结束...... 注意:我假设这里的街区只能是半小时。

编辑:没关系......我刚刚意识到你也想要从结果集中对#Temp1中的繁忙人员进行折扣,所以我在申请中添加了一个not exists子句...

答案 2 :(得分:1)

此查询将执行计算:

SELECT TT1.ID1
 , case when TT2.ST2 < TT1.ST1 THEN TT1.ST1 ELSE TT2.ST2 END
 , case when TT2.ET2 > TT1.ET1 THEN TT1.ET1 ELSE TT2.ET2 END
 , COUNT(distinct TT2.id2) 
FROM #Temp1 TT1 INNER JOIN #Temp2 TT2
ON TT1.ET1 > TT2.ST2 AND TT1.ST1 < TT2.ET2 AND TT1.ID1 <> TT2.ID2
GROUP BY TT1.ID1
 , case when TT2.ST2 < TT1.ST1 THEN TT1.ST1 ELSE TT2.ST2 END
 , case when TT2.ET2 > TT1.ET1 THEN TT1.ET1 ELSE TT2.ET2 END

然而,结果将包括教练cal填写整个时段的位置,例如对于教练1将有三个位置:从9:00到9:30,替补教练#2,从9:替补教练#4和时间段从上午9点到10点用替补教练#3和#4替换。这是整个结果:

ID1                                                         
----------- ----------------------- ----------------------- -----------
1           2016-08-17 09:00:00.000 2016-08-17 09:30:00.000 1
1           2016-08-17 09:00:00.000 2016-08-17 10:00:00.000 2
1           2016-08-17 09:30:00.000 2016-08-17 10:00:00.000 1
3           2016-08-17 09:30:00.000 2016-08-17 10:00:00.000 3
4           2016-08-17 12:00:00.000 2016-08-17 12:30:00.000 1
4           2016-08-17 12:00:00.000 2016-08-17 13:00:00.000 1

答案 3 :(得分:1)

这是您考虑到与可用教练重叠的繁忙教练的预期结果。

| CoachID | CanCoachST       | CanCoachET       | NumOfCoaches | CanCoach |
|---------|------------------|------------------|--------------|----------|
| 1       | 2016-08-17 09:12 | 2016-08-17 09:24 | 2            | 2, 5     |
| 1       | 2016-08-17 09:24 | 2016-08-17 09:30 | 3            | 2, 3, 5  |
| 1       | 2016-08-17 09:30 | 2016-08-17 09:34 | 1            | 5        |
| 1       | 2016-08-17 09:34 | 2016-08-17 09:55 | 2            | 4, 5     |
| 1       | 2016-08-17 09:55 | 2016-08-17 10:00 | 1            | 4        |
| 1       | 2016-08-17 10:00 | 2016-08-17 10:11 | 2            | 3, 4     |
| 3       | 2016-08-17 09:30 | 2016-08-17 09:34 | 1            | 5        |
| 3       | 2016-08-17 09:34 | 2016-08-17 09:55 | 2            | 4, 5     |
| 3       | 2016-08-17 09:55 | 2016-08-17 10:00 | 1            | 4        |
| 4       | 2016-08-17 12:07 | 2016-08-17 12:27 | 2            | 1, 3     |
| 4       | 2016-08-17 12:27 | 2016-08-17 13:08 | 1            | 3        |
| 4       | 2016-08-17 13:08 | 2016-08-17 13:10 | 0            | NULL     |

#Temp1作为忙碌的教练:

| CoachID | BusyST           | BusyET           |
|---------|------------------|------------------|
| 1       | 2016-08-17 09:12 | 2016-08-17 10:11 |
| 3       | 2016-08-17 09:30 | 2016-08-17 10:00 |
| 4       | 2016-08-17 12:07 | 2016-08-17 13:10 |

#Temp2作为可用教练:

| CoachID | AvailableST      | AvailableET      |
|---------|------------------|------------------|
| 1       | 2016-08-17 09:07 | 2016-08-17 11:09 |
| 1       | 2016-08-17 11:34 | 2016-08-17 12:27 |
| 2       | 2016-08-17 09:11 | 2016-08-17 09:30 |
| 3       | 2016-08-17 09:24 | 2016-08-17 13:08 |
| 4       | 2016-08-17 09:34 | 2016-08-17 13:00 |
| 5       | 2016-08-17 09:10 | 2016-08-17 09:55 |

下面的脚本有点长。

;
with
st 
(
    CoachID,
    CanCoachST
)
as
(
    select 
        bound.CoachID,
        s.BusyST 
    from 
        #Temp1 as s 
    cross apply
    (
        select
            b.CoachID,
            b.BusyST,
            b.BusyET
        from
            #Temp1 as b
        where 1 = 1
        and s.BusyST between b.BusyST and b.BusyET
    )
    as bound

    union all

    select 
        bound.CoachID,
        s.BusyET 
    from 
        #Temp1 as s 
    cross apply
    (
        select
            b.CoachID,
            b.BusyST,
            b.BusyET
        from
            #Temp1 as b
        where 1 = 1
        and s.BusyET between b.BusyST and b.BusyET
        and s.CoachID != b.CoachID
    )
    as bound

    union all

    select 
        bound.CoachID,
        s.AvailableST 
    from 
        #Temp2 as s 
    cross apply
    (
        select
            b.CoachID,
            b.BusyST,
            b.BusyET
        from
            #Temp1 as b
        where 1 = 1
        and s.AvailableST between b.BusyST and b.BusyET
    )
    as bound

    union all

    select 
        bound.CoachID,
        s.AvailableET 
    from 
        #Temp2 as s 
    cross apply
    (
        select
            b.CoachID,
            b.BusyST,
            b.BusyET
        from
            #Temp1 as b
        where 1 = 1
        and s.AvailableET between b.BusyST and b.BusyET
        and s.CoachID != b.CoachID
    )
    as bound
),
d as
(
    select distinct
        CoachID,
        CanCoachST
    from
        st
),
r as
(
    select
        row_number() over (order by CoachID, CanCoachST) as RowID,
        CoachID,
        CanCoachST
    from
        d
),
rng as
(
    select
        r1.RowID,
        r1.CoachID,
        r1.CanCoachST,
        case when r1.CoachID = r2.CoachID 
            then r2.CanCoachST else t.BusyET end as CanCoachET
    from
        r as r1
    left join
        r as r2
    on
        r1.RowID = r2.RowID - 1
    left join
        #Temp1 as t
    on
        t.CoachID = r1.CoachID
),
c as
(
    select
        rng.RowID,
        rng.CoachID,
        rng.CanCoachST,
        rng.CanCoachET,
        t2.CoachID as CanCoachID
    from
        rng
    cross join
        #Temp1 as t1
    cross join
        #Temp2 as t2
    where 1 = 1
    and t2.CoachID != rng.CoachID
    and t2.AvailableST <= rng.CanCoachST
    and t2.AvailableET >= rng.CanCoachET
),
b as
(
    select
        rng.RowID,
        rng.CoachID,
        rng.CanCoachST,
        rng.CanCoachET,
        t1.CoachID as BusyCoachID
    from
        rng
    cross join
        #Temp1 as t1
    where 1 = 1
    and t1.CoachID != rng.CoachID
    and t1.BusyST <= rng.CanCoachST
    and t1.BusyET >= rng.CanCoachET
),
e as
(
    select
        c.RowID,
        c.CoachID,
        c.CanCoachST,
        c.CanCoachET,
        c.CanCoachID
    from
        c

    except

    select
        b.RowID,
        b.CoachID,
        b.CanCoachST,
        b.CanCoachET,
        b.BusyCoachID
    from
        b
),
f as
(
    select
        rng.RowID,
        rng.CoachID,
        rng.CanCoachST,
        rng.CanCoachET,
        e.CanCoachID
    from
        rng
    left join   
        e
    on
        e.RowID = rng.RowID
)
select
    f.CoachID,
    f.CanCoachST,
    f.CanCoachET,
    count(f.CanCoachID) as NumOfCoaches,
    stuff
    (
        (
            select ', ' + cast(f1.CanCoachID as varchar(5))
                from f as f1 where f1.RowID = f.RowID
                for xml path('')
        ), 
        1, 2, ''
    )
    as CanCoach
from
    f
group by
    f.RowID,
    f.CoachID,
    f.CanCoachST,
    f.CanCoachET
order by
    1, 2

答案 4 :(得分:1)

我建议使用区间/时间表的概念。 另一种解释方法是考虑“时间维度表”

定义所有时间,然后以您关注的粒度参考时间间隔记录您的事实。因为你有时间在7分钟和11分钟结束,我选择1分钟间隔,但我建议间隔15-30分钟。

通过这样做,可以很容易地加入/比较表格。

考虑以下设计/实施:

- 维度表

-- drop table #intervals
create table #intervals(intervalId  int identity(1,1) not null primary key clustered,intervalStartTime datetime unique)
declare @s datetime, @e datetime, @i int 
set @s = '2016-08-16'
set @e = '2016-08-18'
set @i = 1
while (@s <= @e )
begin 
 insert into #intervals(intervalStartTime) values(@s)
 set @s = dateadd(minute, @i, @s)
end 

- 事实表:

-- drop table #Fact

create table #Fact(intervalId int,coachid int,isBusy int default(0),isAvailable int default(0))

- 记录每个教练的时间

   insert into #Fact(coachid,intervalId)
select distinct c.coachid, i.intervalId  
from 
(
select distinct coachid from #temp1
union
select distinct coachid from #temp2
) c cross join #intervals i

-- record free / busy info
update f set isbusy = 1 
from #intervals i inner join #fact f on i.intervalId  = f.intervalId  
inner join #temp1 t on f.coachid = t.coachid and i.intervalStartTime  between t.BusyST and t.BusyET

    -- record free / busy info
update f set isAvailable = 1 
from #intervals i inner join #fact f on i.intervalId  = f.intervalId  
inner join #temp2 t on f.coachid = t.coachid and i.intervalStartTime  between t.AvailableST and t.AvailableET

- 构建查询以查找常见时间等

select * from #intervals i inner join #Fact f on i.intervalId = f.intervalId

- 示例结果显示可用的教练数与免费

选择i.intervalId,i.intervalStartTime,sum(isBusy)作为coachesBusy,sum(isAvailable)作为coachesAvailable 来自#intervals i inner join #Fact f on i.intervalId = f.intervalId group by i.intervalId,i.intervalStartTime 总和(isBusy)&lt;总和(isAvailable)

enter image description here

然后,您可以根据需要查找常用或唯一的间隔ID。

如果您需要进一步说明,请与我们联系。

答案 5 :(得分:1)

我使用的是一个小数字表...你不需要日期,只需数字。我在这里构建的内容比在实际场景中使用的内容要小。

CREATE TABLE dbo.Numbers (Num INT PRIMARY KEY CLUSTERED);

WITH E1 AS (SELECT N FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))     AS t(N))
,E2 AS (SELECT N = 1 FROM E1 AS a, E1 AS b)
,E4 AS (SELECT N = 1 FROM E2 AS a, E2 AS b)
,cteTally AS (SELECT N = 0 UNION ALL
                SELECT N = ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM     E4)
INSERT INTO dbo.Numbers (Num)
SELECT N FROM cteTally;

请注意下面的@startDate ...人为地接近您正在处理的日期,并且在一个真实的产品场景中,您可以更早地将该日期与更大的Numbers表一起使用。

以下是您的问题的解决方案,它将适用于较旧的SQL Server版本(以及您已标记的2012):

DECLARE @startDate DATETIME = '20160817';
WITH cteBusy AS
(
SELECT  num.Num
    ,   busy.CoachID
FROM #Temp1 AS busy
JOIN dbo.Numbers AS num
    ON num.Num >= DATEDIFF(MINUTE, @startDate, busy.BusyST)
    AND num.Num < DATEDIFF(MINUTE, @startDate, busy.BusyET)
)
, cteAvailable AS
(
SELECT  num.Num
    ,   avail.CoachID
FROM #Temp2 AS avail
JOIN dbo.Numbers AS num
    ON num.Num >= DATEDIFF(MINUTE, @startDate, avail.AvailableST)
    AND num.Num < DATEDIFF(MINUTE, @startDate, avail.AvailableET)
LEFT JOIN cteBusy AS b
    ON b.Num = num.Num
    AND b.CoachID = avail.CoachID
WHERE b.Num IS NULL 
)
,   cteGrouping AS
(
SELECT  b.Num
    ,   b.CoachID
    ,   NumOfCoaches = COUNT(a.CoachID)
FROM cteBusy AS b
LEFT JOIN cteAvailable AS a
    ON a.Num = b.Num
GROUP BY b.Num, b.CoachID
)
,   cteFinal AS
(
SELECT  cte.Num
    ,   cte.CoachID
    ,   cte.NumOfCoaches
    ,   block = cte.Num - ROW_NUMBER() OVER(PARTITION BY cte.CoachID, cte.NumOfCoaches ORDER BY cte.Num)
FROM cteGrouping AS cte
)
SELECT  cte.CoachID
,   CanCoachST = DATEADD(MINUTE, MIN(cte.Num), @startDate)
,   CanCoachET = DATEADD(MINUTE, MAX(cte.Num) + 1, @startDate)
,   cte.NumOfCoaches
FROM cteFinal AS cte
GROUP BY cte.CoachId, cte.NumOfCoaches, cte.block
ORDER BY cte.CoachID, CanCoachST;

享受!

答案 6 :(得分:1)

我相信以下查询会有效,但我对性能没有任何承诺。

var WebPartElementID ="ctl00_ctl34_g_db6615a7_4c3b_4a14_9bbc_43ce9d63c24d_FormControl0";
var WebPartData = document.getElementById(WebPartElementID);    
WebPartData.onkeypress = stopRKey;    

function stopRKey(evt) {
            var evt = (evt) ? evt : ((event) ? event : null);
            var node = (evt.target) ? evt.target : ((evt.srcElement) ? evt.srcElement : null);
            alert(evt.keyCode);
            if ((evt.keyCode == 13)) { document.getElementById("ctl00_ctl34_g_db6615a7_4c3b_4a14_9bbc_43ce9d63c24d_FormControl0_V1_I1_R1_I1_T4").focus(); return false; }
}