Database design PostgreSQL 9中的日历表

Database design PostgreSQL 9中的日历表,database-design,postgresql,data-warehouse,rails-postgresql,Database Design,Postgresql,Data Warehouse,Rails Postgresql,我正在建立一个分析数据库(我对数据和业务目标有着坚定的理解,并且只有基本到中等的数据库技能) 我遇到过一些关于建立类似仓库的参考资料,这些仓库实现了“日历表”的概念。这是有道理的,而且很容易做到。然而,我看到的大多数示例都是日历表,它们将范围限制为“天”。我的数据需要分析到小时水平。可能几分钟 我的问题是:实现小时/分钟级粒度的日历表在空间效率和查询/排序速度方面是否有价值?如果是,您能推荐一个表结构和填充方法/示例吗 我的主数据表在任何给定时间都将包含2000多万行数据,用于分析的典型子集在1

我正在建立一个分析数据库(我对数据和业务目标有着坚定的理解,并且只有基本到中等的数据库技能)

我遇到过一些关于建立类似仓库的参考资料,这些仓库实现了“日历表”的概念。这是有道理的,而且很容易做到。然而,我看到的大多数示例都是日历表,它们将范围限制为“天”。我的数据需要分析到小时水平。可能几分钟

我的问题是:实现小时/分钟级粒度的日历表在空间效率和查询/排序速度方面是否有价值?如果是,您能推荐一个表结构和填充方法/示例吗


我的主数据表在任何给定时间都将包含2000多万行数据,用于分析的典型子集在100万到500万之间。因此,正如您所见,这是大量的时间戳字段。

PostgreSQL
中,您可以动态生成任意长度和粒度的日历表:

SELECT  CAST('2011-01-01' AS DATE) + (n || ' hour')::INTERVAL
FROM    generate_series(0, 23) n

这不需要递归(与其他系统一样),是生成易失性结果集的首选方法。

日历表实现了空间/时间权衡。通过使用更多的空间,某些类型的查询可以在更短的时间内运行,因为它们可以利用索引。只要您小心使用CHECK()约束,并且只要您有管理流程来处理dbms不支持的任何约束,它们都是安全的

如果粒度是一分钟,那么每年需要生成大约50万行。最小的日历表如下所示

2011-01-01 00:00:00
2011-01-01 00:01:00
2011-01-01 00:02:00
2011-01-01 00:03:00
2011-01-01 00:04:00
bucket_start         bucket_end
--
2011-01-01 00:00:00  2011-01-01 00:01:00
2011-01-01 00:01:00  2011-01-01 00:02:00
2011-01-01 00:02:00  2011-01-01 00:03:00
2011-01-01 00:03:00  2011-01-01 00:04:00
2011-01-01 00:04:00  2011-01-01 00:05:00
如果你在做“桶”分析,你最好用这样的方法

2011-01-01 00:00:00
2011-01-01 00:01:00
2011-01-01 00:02:00
2011-01-01 00:03:00
2011-01-01 00:04:00
bucket_start         bucket_end
--
2011-01-01 00:00:00  2011-01-01 00:01:00
2011-01-01 00:01:00  2011-01-01 00:02:00
2011-01-01 00:02:00  2011-01-01 00:03:00
2011-01-01 00:03:00  2011-01-01 00:04:00
2011-01-01 00:04:00  2011-01-01 00:05:00
因为SQL的BETWEEN操作符包含端点,所以通常需要避免使用它。这是因为它包括端点,很难将bucket_end表示为“bucket_start加上一分钟,减去服务器可以识别的最小时间”。(危险值比bucket_end大一微秒,但仍然小于bucket_start的下一个值。)

如果我要建那张桌子,我可能会这样做。(尽管我会更仔细地考虑是否应该称之为“日历”。)

UNIQUE约束在PostgreSQL中创建一个隐式索引

此查询将一次插入一天的行(24小时*60分钟)

insert into calendar
select coalesce(
                (select max(bucket_start) from calendar), 
                 cast('2011-01-01 00:00:00' as timestamp)
               ) 
             + cast((n || 'minute') as interval) as bucket_start, 
       coalesce(
                (select max(bucket_start) from calendar), 
                 cast('2011-01-01 00:00:00' as timestamp)
               ) 
             + cast((n + 1 || ' minute') as interval) as bucket_end
from generate_series(1, (24*60) ) n;
您可以将其包装在函数中,一次生成一年。我可能会尝试一次提交不到50万行

insert into calendar
select coalesce(
                (select max(bucket_start) from calendar), 
                 cast('2011-01-01 00:00:00' as timestamp)
               ) 
             + cast((n || 'minute') as interval) as bucket_start, 
       coalesce(
                (select max(bucket_start) from calendar), 
                 cast('2011-01-01 00:00:00' as timestamp)
               ) 
             + cast((n + 1 || ' minute') as interval) as bucket_end
from generate_series(1, (24*60) ) n;

生成用于测试的2000万行和另外2000万行“日历”分钟应该不会花费太长时间。午餐时间很长。也许是一个下午的阳光。

在我构建的数据仓库中,我使用了单独的日历和时间维度。第一个维度有一天的粒度,第二个维度有一分钟的粒度

在另外两个案例中,我事先就知道,在小于15分钟的粒度下,不需要报告。在这种情况下,为了简单起见,我使用了一个日历维度,每天有96条记录


到目前为止,我在Oracle仓库中使用了这种方法,但今年夏天我可能参与了一个PostgreSQL仓库项目。

是的,但是加入generate_series()对大约2000万行的结果可能会降低性能。日历表上的查询可以利用索引。@Catcall:日历表上的查询通常假定日历表中的所有值与事实表中的某些值之间存在左连接,因此对于没有事实记录的时段返回的记录为
NULL
。您能否提供一个示例查询,将
generate_series
替换为带有数据的实际表,这将使您受益匪浅?很有趣。非常感谢。因此,我的想法是,我可以创建三个这样的表:天、小时、分钟,我的数据集表将具有诸如“day\u id”、“hour\u id”、“minute\u id”之类的键,这些键可以一起使用,也可以独立使用,具体取决于我分析的分辨率?如果是这样,那就太棒了。如果没有,我就遗漏了一些东西。@NJ:为什么,您可以在
generate_series
中生成分钟分辨率。请编写一个您尝试执行的查询,我将告诉您如何将其与
generate_series
@NJ匹配通常您只有一个非规范化的日历表和事实表中的一个键。在本例中为分钟id,因为这是日历的粒度。您的日历表将有一个月列、一天列、一分钟列。要获取一天的所有数据,请执行
选择。。。从日历c左键连接c.id=d.minute\u id上的数据表d,其中c.day='2011-04-28'
。要仅获取一分钟的数据,请选择
。。。从calendar c左键连接c.id=d.minute\u id上的数据表d,其中c.minute='2011-04-28 04:10:00'
您到底在说什么“索引的优势”?