每小时每天的单位数

时间:2015-02-02 01:02:49

标签: sql-server tsql sql-server-2008-r2

我有一个表格,显示有关容器访问的信息:容器的编号,时间和超时。

如果我想知道,我现在能拥有多少个容器:

SELECT count(nbr) from TBL
WHERE time_in <getdate() and (time_out is null or time_out>getdate())

我明白,如果我想在2014年2月1日知道这个号码,我会做类似的事情:

SELECT count(nbr) from TBL
WHERE time_in <convert(date,'2014-02-01')
and (time_out >convert(date,'2014-02-01') or time_out is null)

但是现在,我想知道从2月1日到现在每小时有多少个容器。每小时。 我认为唯一的方法是创建一个过程,该过程将在where子句中使用的datetime变量循环:

declare @d datetime
set @d= convert(datetime, '2014-02-01')
declare @t table (T datetime, C int)

while @d<getdate
begin
  insert into @t
    select count (nbr) from TBL
    where time_in <@d and (time_out >@d or time_out is null)
  set @d= dateadd (hour,1,@d)

 end

但是我发现这个解决方案非常非SQL(在SQL中你应该说什么不是HOW)并且远没有效率。 有没有更好的方法呢?

编辑:只是为了清楚说明数据的例子:

container           T-in         T-out
 A                12:00          15:10
 B                12:15          14:00
 C                13:10          14:10
 D                14:01          null

所以你可以看到:

at 12:00  - 1 container (A)
at 13:00  - 2 containers (A+B)
at 14:00  - 2 containers (A+C)
at 15:00  - 2 containers (A+D)
at 16:00  - 1 container (D)
...

2 个答案:

答案 0 :(得分:1)

一种方法是使用table of numberscalendar table。 在下面的代码中,表Numbers有一列Number,其中包含从1开始的整数。generate such table有很多种方法。 您可以动态执行,也可以使用实际表格。我个人在数据库中有这样的表,有100,000行。

第一个CROSS APPLY有效地创建了一个列CurrentHour,因此我不必多次重复调用DATEADD

第二个CROSS APPLY是您希望每小时运行一次的查询。它可以根据需要复杂化,如果需要,它可以返回多行。

DECLARE @StartDate datetime = '2014-02-01T00:00:00';
DECLARE @EndDate datetime = '2014-02-02T00:00:00';

SELECT
    CurrentHour
    , CC
FROM
    Numbers
    CROSS APPLY
    (
        SELECT DATEADD(hour, Numbers.Number-1, @StartDate) AS CurrentHour
    ) AS CA_Hour
    CROSS APPLY
    (
        SELECT COUNT(nbr) AS CC
        FROM TBL
        WHERE
            time_in <= CurrentHour
            and (time_out > CurrentHour or time_out is null)
    ) AS CA
WHERE
    Numbers.Number < DATEDIFF(hour, @StartDate, @EndDate);
;

修改

以下是基于示例数据的示例。您将拥有一个合适的数字表而不是CTE_Numbers。我让CTE_Numbers使这个脚本独立。

DECLARE @TBL TABLE (Container char(1), time_in datetime, time_out datetime);

INSERT INTO @TBL (Container, time_in, time_out) VALUES ('A', '2014-02-01T12:00:00', '2014-02-01T15:10:00');
INSERT INTO @TBL (Container, time_in, time_out) VALUES ('B', '2014-02-01T12:15:00', '2014-02-01T14:00:00');
INSERT INTO @TBL (Container, time_in, time_out) VALUES ('C', '2014-02-01T13:10:00', '2014-02-01T14:10:00');
INSERT INTO @TBL (Container, time_in, time_out) VALUES ('D', '2014-02-01T14:01:00', NULL);

DECLARE @StartDate datetime = '2014-02-01T11:00:00';
DECLARE @EndDate datetime   = '2014-02-01T18:00:00';

WITH
CTE_Numbers
AS
(
    SELECT Number
    FROM (VALUES(1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) AS T(Number)
)
SELECT
    CurrentHour
    ,ContainerCount
FROM
    CTE_Numbers
    CROSS APPLY
    (
        SELECT DATEADD(hour, CTE_Numbers.Number-1, @StartDate) AS CurrentHour
    ) AS CA_Hour
    CROSS APPLY
    (
        SELECT COUNT(*) AS ContainerCount
        FROM @TBL
        WHERE
            time_in <= CurrentHour
            and (time_out > CurrentHour or time_out is null)
    ) AS CA
WHERE
    CTE_Numbers.Number < DATEDIFF(hour, @StartDate, @EndDate) + 1
ORDER BY CTE_Numbers.Number
;

这是结果集:

CurrentHour                ContainerCount
2014-02-01 11:00:00.000    0
2014-02-01 12:00:00.000    1
2014-02-01 13:00:00.000    2
2014-02-01 14:00:00.000    2
2014-02-01 15:00:00.000    2
2014-02-01 16:00:00.000    1
2014-02-01 17:00:00.000    1

您可以看到此方法可以正确计算任何小时的结果。 11:00为零; 16:0017:00以及更远的地方为1。

答案 1 :(得分:1)

你对当前代码的直觉很好;如果可能的话,你应该避免SQL代码中的循环,因为它们执行得非常糟糕。

您可以使用a recursive common table expression (CTE)执行此操作。使用CTE需要SQL Server 2008或更高版本,但由于您已表明使用的是2008r2,因此这不会有问题。

;WITH counts (nbr,time_in,time_out,present_until) AS (
  SELECT nbr
    ,time_in
    ,time_out
    ,DATEADD(hour, 1, '2014-02-02') AS [present_until]
  FROM TBL
  WHERE time_in < '2014-02-02'
    AND (time_out IS NULL OR time_out >= DATEADD(hour, 1, '2014-02-02'))
  UNION ALL
  SELECT nbr
    ,time_in
    ,time_out
    ,DATEADD(hour, 1, present_until) AS [present_until]
  FROM counts
  WHERE time_in < present_until
    AND (time_out IS NULL OR time_out >= DATEADD(hour, 1, present_until))
    AND present_until < '2014-02-03'
)

SELECT count(nbr) as [count],present_until
FROM counts
GROUP BY present_until

Here is a fiddle显示此查询的结果。请注意,使用的日期与您提供的示例略有不同。

观察到UNION的后半部分是从CTE本身中选择的;这个递归的自联接与在SELECT中添加一小时到present_until是创建时间列表的原因。

请注意,present_until表示小时间隔的 end ; 2月2日2:00至3:00将显示为2015-2-2 3:00。此外,结果中将省略任何没有容器的间隔。