SQL和时态数据

SQL和时态数据,sql,sql-server,sql-server-2008,Sql,Sql Server,Sql Server 2008,给出一张预约表,如下所示: User Start End UserA 2016-01-15 12:00:00 2016-01-15 14:00:00 UserA 2016-01-15 15:00:00 2016-01-15 17:00:00 UserB 2016-01-15 13:00:00 2016-01-15 15:00:00 UserB 2016-01-15 13:3

给出一张预约表,如下所示:

User     Start                    End
UserA    2016-01-15 12:00:00      2016-01-15 14:00:00    
UserA    2016-01-15 15:00:00      2016-01-15 17:00:00    
UserB    2016-01-15 13:00:00      2016-01-15 15:00:00    
UserB    2016-01-15 13:32:00      2016-01-15 15:00:00    
UserB    2016-01-15 15:30:00      2016-01-15 15:30:00    
UserB    2016-01-15 15:45:00      2016-01-15 16:00:00    
UserB    2016-01-15 17:30:00      2016-01-15 18:00:00    
我想创建一个不同时间间隔的列表,其中相同数量的人有约会:

Start                 End                   Count
2016-01-15 12:00:00   2016-01-15 13:00:00   1
2016-01-15 13:00:00   2016-01-15 14:00:00   2
2016-01-15 14:00:00   2016-01-15 15:45:00   1
2016-01-15 15:45:00   2016-01-15 16:00:00   2
2016-01-15 16:00:00   2016-01-15 17:00:00   1
2016-01-15 17:00:00   2016-01-15 17:30:00   0
2016-01-15 17:30:00   2016-01-15 18:00:00   1
在SQL(最好是SQL Server 2008)中,我将如何做到这一点

编辑:澄清:手动,通过为每个用户制作一行,标记阻止时间,然后汇总具有标记的行数,获得结果:

Time  12  13  14  15  16  17
UserA xxxxxxxx    xxxxxxxx
UserB     xxxxxxxx   x      xx
Count 1   2   1      21   0 1

结果集将在可用的最短时间开始,在可用的最长时间结束,虽然ASCII art只有15分钟的分辨率,但我至少需要一分钟的分辨率。如果这对您来说比较容易的话,我想您可以在结果中保留带0的行。

一定有比这更简单的方法,但至少您可以单独执行每个步骤:

declare @t table ([User] varchar(19) not null,Start datetime2 not null,[End] datetime2 not null)
insert into @t([User], Start, [End]) values
('UserA','2016-01-15T12:00:00','2016-01-15T14:00:00'),
('UserA','2016-01-15T15:00:00','2016-01-15T17:00:00'),
('UserB','2016-01-15T13:00:00','2016-01-15T15:00:00'),
('UserB','2016-01-15T13:32:00','2016-01-15T15:00:00'),
('UserB','2016-01-15T15:30:00','2016-01-15T15:30:00'),
('UserB','2016-01-15T15:45:00','2016-01-15T16:00:00'),
('UserB','2016-01-15T17:30:00','2016-01-15T18:00:00')

;With Times as (
    select Start as Point from @t
    union
    select [End] from @t
), Ordered as (
    select Point,ROW_NUMBER() OVER (ORDER BY Point) as rn
    from Times
), Periods as (
    select
        o1.Point as Start,
        o2.Point as [End]
    from
        Ordered o1
            inner join
        Ordered o2
            on
                o1.rn = o2.rn - 1
), UserCounts as (
select p.Start,p.[End],COUNT(distinct [User]) as Cnt,ROW_NUMBER() OVER (Order BY p.[Start]) as rn
from
    Periods p
        left join
    @t t
        on
            p.Start < t.[End] and
            t.Start < p.[End]
group by
    p.Start,p.[End]
), Consolidated as (
    select uc.*
    from
        UserCounts uc
            left join
        UserCounts uc_anti
            on
                uc.rn = uc_anti.rn + 1 and
                uc.Cnt = uc_anti.Cnt
    where
        uc_anti.Cnt is null
    union all
    select c.Start,uc.[End],c.Cnt,uc.rn
    from
        Consolidated c
            inner join
        UserCounts uc
            on
                c.Cnt = uc.Cnt and
                c.[End] = uc.Start
)
select
    Start,MAX([End]) as [End],Cnt
from
    Consolidated
group by
    Start,Cnt
order by Start

我甚至得到了零行,我不确定我是否能够变戏法为现实

一定有比这更简单的方法,但至少你可以单独遵循每一步:

declare @t table ([User] varchar(19) not null,Start datetime2 not null,[End] datetime2 not null)
insert into @t([User], Start, [End]) values
('UserA','2016-01-15T12:00:00','2016-01-15T14:00:00'),
('UserA','2016-01-15T15:00:00','2016-01-15T17:00:00'),
('UserB','2016-01-15T13:00:00','2016-01-15T15:00:00'),
('UserB','2016-01-15T13:32:00','2016-01-15T15:00:00'),
('UserB','2016-01-15T15:30:00','2016-01-15T15:30:00'),
('UserB','2016-01-15T15:45:00','2016-01-15T16:00:00'),
('UserB','2016-01-15T17:30:00','2016-01-15T18:00:00')

;With Times as (
    select Start as Point from @t
    union
    select [End] from @t
), Ordered as (
    select Point,ROW_NUMBER() OVER (ORDER BY Point) as rn
    from Times
), Periods as (
    select
        o1.Point as Start,
        o2.Point as [End]
    from
        Ordered o1
            inner join
        Ordered o2
            on
                o1.rn = o2.rn - 1
), UserCounts as (
select p.Start,p.[End],COUNT(distinct [User]) as Cnt,ROW_NUMBER() OVER (Order BY p.[Start]) as rn
from
    Periods p
        left join
    @t t
        on
            p.Start < t.[End] and
            t.Start < p.[End]
group by
    p.Start,p.[End]
), Consolidated as (
    select uc.*
    from
        UserCounts uc
            left join
        UserCounts uc_anti
            on
                uc.rn = uc_anti.rn + 1 and
                uc.Cnt = uc_anti.Cnt
    where
        uc_anti.Cnt is null
    union all
    select c.Start,uc.[End],c.Cnt,uc.rn
    from
        Consolidated c
            inner join
        UserCounts uc
            on
                c.Cnt = uc.Cnt and
                c.[End] = uc.Start
)
select
    Start,MAX([End]) as [End],Cnt
from
    Consolidated
group by
    Start,Cnt
order by Start

我甚至得到了一个零行,我不确定我是否能够变出它的存在

如果你有一个表,这种查询更容易编写。但在这个例子中,我用一个。CTE返回约会块,然后我们可以将其加入约会数据。我无法确定示例数据中的间隔模式,因此我以一小时为单位显示了结果。您可以修改此部分,或者在第二个表中定义自己的部分

样本数据

我使用了两个变量来限制返回的结果,使其仅限于那些在给定起点和终点内的约会

/* Set an start and end point for the next query    
 */
DECLARE @Start  DATETIME = '2016-01-15 12:00:00';
DECLARE @End    DATETIME = '2016-01-15 18:00:00';

WITH Calendar AS
    (
            /* Anchor returns start of first appointment    
             */
            SELECT
                @Start                                          AS [Start],
                DATEADD(SECOND, -1, DATEADD(HOUR, 1, @Start))   AS [End] 

        UNION ALL

            /* Recursion, keep adding new records until end of last appointment    
             */
            SELECT
                DATEADD(HOUR, 1, [Start])   AS [Start],
                DATEADD(HOUR, 1, [End])     AS [End]
            FROM
                Calendar
            WHERE
                [End] <= @End
    )
SELECT
    c    [Start],
    c    [End],
    COUNT(DISTINCT s    [User]) AS [Count]
FROM
    Calendar AS c
        LEFT OUTER JOIN @Sample AS s            ON s    [Start] BETWEEN c    [Start] AND c    [End]
                                                OR s    [End] BETWEEN c    [Start] AND c    [End]
GROUP BY
    c    [Start],
    c    [End]
;

因为约会可以超过一个小时,所以可能会导致多行。这就解释了为什么7个样本行总共返回9个。

如果您有一个表,这种查询更容易编写。但在这个例子中,我用一个。CTE返回约会块,然后我们可以将其加入约会数据。我无法确定示例数据中的间隔模式,因此我以一小时为单位显示了结果。您可以修改此部分,或者在第二个表中定义自己的部分

样本数据

我使用了两个变量来限制返回的结果,使其仅限于那些在给定起点和终点内的约会

/* Set an start and end point for the next query    
 */
DECLARE @Start  DATETIME = '2016-01-15 12:00:00';
DECLARE @End    DATETIME = '2016-01-15 18:00:00';

WITH Calendar AS
    (
            /* Anchor returns start of first appointment    
             */
            SELECT
                @Start                                          AS [Start],
                DATEADD(SECOND, -1, DATEADD(HOUR, 1, @Start))   AS [End] 

        UNION ALL

            /* Recursion, keep adding new records until end of last appointment    
             */
            SELECT
                DATEADD(HOUR, 1, [Start])   AS [Start],
                DATEADD(HOUR, 1, [End])     AS [End]
            FROM
                Calendar
            WHERE
                [End] <= @End
    )
SELECT
    c    [Start],
    c    [End],
    COUNT(DISTINCT s    [User]) AS [Count]
FROM
    Calendar AS c
        LEFT OUTER JOIN @Sample AS s            ON s    [Start] BETWEEN c    [Start] AND c    [End]
                                                OR s    [End] BETWEEN c    [Start] AND c    [End]
GROUP BY
    c    [Start],
    c    [End]
;

因为约会可以超过一个小时,所以可能会导致多行。这就解释了为什么7个样本行总共返回9个。

您真的需要0行吗?基于零源行将行变为存在可能很棘手。在所需的结果集中,从哪里获得开始和结束时间?它们与您的数据不匹配,也不遵循模式。@DevlEnvonate-我认为具有相同用户数的相邻时段被合并,而且用户的约会似乎彼此重叠。您真的需要0行吗?基于零源行将行变为存在可能很棘手。在所需的结果集中,从哪里获得开始和结束时间?它们与您的数据不匹配,也不遵循模式。@DevlEnformnate-我认为具有相同用户数的相邻时段是合并的,而且用户的约会似乎彼此重叠。