想象一下你有一个工作时间表。(或者自己创建一个。这个没有经过测试,所以可能包含时区和栅木错误。)
create table work_minutes (
work_minute timestamp primary key
);
insert into work_minutes
select work_minute
from
(select generate_series(timestamp '2013-01-01 00:00:00', timestamp '2013-12-31 11:59:00', '1 minute') as work_minute) t
where extract(isodow from work_minute) < 6
and cast(work_minute as time) between time '09:00' and time '17:30'
现在您的查询可以计算分钟,这非常简单。
select count(*)/60.0 as elapsed_hrs
from work_minutes
where work_minute between '2013-01-23 10:47:52' and '2013-02-25 11:18:36'
elapsed_hours
196.4
您可以决定如何处理小数小时。
根据如何处理开始时间等因素,按分钟计算和按小时计算可能存在很大差异。基于小时的计算可能不会考虑超出停止时间的一个小时中的许多分钟。是否重要取决于应用程序。
您可以使用generate_series()动态生成虚拟表,但是像这样的基本表只需要约400万行即可覆盖30年,并且对其进行此类计算非常快速。
稍后...
我看到
Erwin Brandstetter涵盖了现代PostgreSQL中generate_series()的使用;它在8.3版本中不起作用,因为8.3不支持公共表达式或generate_series(timestamp,timestamp)。这是Erwin查询的一个版本,避免了这些问题。这不是完全忠实的翻译;计算相差一个小时。这可能是我的错误,但我现在没有时间深入研究细节。
select count(*) from
(select timestamp '2013-01-23 10:47:52-05' + (n || ' hours')::interval
from generate_series( 0
, (extract(days from timestamp '2013-02-25 11:18:36-05'
- timestamp '2013-01-23 10:47:52-05')::integer * 24) ) n
where extract(isodow from (timestamp '2013-01-23 10:47:52-05' + (n || ' hours')::interval)) < 6
and (timestamp '2013-01-23 10:47:52-05' + (n || ' hours')::interval)::time >= '09:00'::time
and (timestamp '2013-01-23 10:47:52-05' + (n || ' hours')::interval)::time < '17:30'::time
) t
使用基于表的解决方案有一个优点,那就是可以轻松地处理管理的反复无常。"嘿!我们的狗狗生了七只小狗!今天半天休息!" 它还可以良好地扩展,并且在几乎所有平台上都可以使用而不需要修改。
如果您使用generate_series()函数,请将其包装在一个视图中。这样,对规则的任意更改都可以在一个地方进行管理。如果规则变得太复杂,难以在视图中轻松维护,您可以用同样名称的表替换视图,所有应用程序代码、SQL和存储过程和函数都将正常工作。