我有一个表格,显示有关容器访问的信息:容器的编号,时间和超时。
如果我想知道,我现在能拥有多少个容器:
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)
...
答案 0 :(得分:1)
一种方法是使用table of numbers或calendar 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:00
,17: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。此外,结果中将省略任何没有容器的间隔。