Sql oracle中时间序列表的非规范化与稠密化

Sql oracle中时间序列表的非规范化与稠密化,sql,oracle,Sql,Oracle,我遇到了一个巨大的问题:如何将带有时间戳的状态表转换为可以快速查询的平面表 我基本上有一张这样的桌子: ╔══════════╦═══════════╦══════════╦══════════╦═══════════╦══════════╦ ║ PersonID ║ Firstname ║ Lastname ║ status ║ startdate ║ endate ║ ║ 10233 ║ stacy ║ adamns ║ active ║ 12-23-13

我遇到了一个巨大的问题:如何将带有时间戳的状态表转换为可以快速查询的平面表

我基本上有一张这样的桌子:

╔══════════╦═══════════╦══════════╦══════════╦═══════════╦══════════╦
║ PersonID ║ Firstname ║ Lastname ║ status   ║ startdate ║ endate   ║  
║ 10233    ║ stacy     ║ adamns   ║ active   ║ 12-23-13  ║ 02-11-14 ║  
║ 10233    ║ stacy     ║ adamns   ║ pending  ║ 02-11-14  ║ 03-09-14 ║  
║ 10233    ║ stacy     ║ adamns   ║ inactive ║ 03-09-14  ║ 12-31-99 ║  
║ 10244    ║ steve     ║ smith    ║ active   ║ 01-07-14  ║ 12-31-99 ║  
╚══════════╩═══════════╩══════════╩══════════╩═══════════╩══════════╩
并将其转化为:

╔══════════╦══════════╦═══════════╦══════════╦════════╗
║ Date     ║ PersonID ║ Firstname ║ Lastname ║ status ║
║ 12-23-13 ║ 10233    ║ stacy     ║ adamns   ║ active ║
║ 12-24-13 ║ 10233    ║ stacy     ║ adamns   ║ active ║
║ 12-25-13 ║ 10233    ║ stacy     ║ adamns   ║ active ║
║ 12-26-13 ║ 10233    ║ stacy     ║ adamns   ║ active ║
║          ║          ║           ║          ║        ║
╚══════════╩══════════╩═══════════╩══════════╩════════╝
此表有28个附加列,其中包含描述员工的各种内容,这些内容是静态的,不会改变,例如高度,它有4800万行长

我需要知道过去两年中每天有多少员工处于活跃状态。现在,对于较小的日期范围或数据集,这是非常容易的,我只需将它与一个类似于以下内容的日历表连接起来:

Create Table People_history as
    Select Day_id,Firstname,Lastname,status
    from People
    Join Time_calendar on day_id between startdate and endate;
我已经计算出结果表将变成78亿行,超过3万亿字节;但是,我的数据库甚至无法完成查询,因为它的临时内存不足。用光标我可以绕过内存问题,但它需要24小时才能运行。。。我只需要做一次,也许这就是我要做的,但我想我会先问你们

我应该看一个不同的数据库来做这种分析,还是一个更有效的方法? 我研究了Cassandra,它建议为时间间隔或MongoDB创建列,您可以将时间间隔和状态放入每个人自己的散列中。这些是好的选择吗?

上的答案可能会有所帮助

在这些答案的帮助下,我得出了以下结论:

WITH date_ranges AS
         (    SELECT DISTINCT personid,
                              firstname,
                              lastname,
                              startdate + LEVEL - 1 AS date_i
                FROM myTable
          CONNECT BY LEVEL <= CEIL (endate - startdate) + 1)
  SELECT dr.date_i,
         dr.personid,
         dr.firstname,
         dr.lastname,
         (SELECT mt.status
            FROM myTable mt
           WHERE     mt.personid = dr.personid
                 AND dr.date_i BETWEEN mt.startdate AND mt.endate)
             AS status
    FROM date_ranges dr;
请进行必要的更改并相应地使用

我需要知道过去两年中每天有多少员工处于活跃状态

为了达到这个目标,您不需要创建一个78亿行的表。只需使用原始表格。我使用的算法可以计算平均值,按日期或月份求和,只需使用完整的表格扫描。你的要求很简单

a从日期开始的时间为添加日期为“2014-08-05”的月份-24,到日期为“2014-08-05”的月份。 试试这个

然后过滤期限等于2年的期限,达到目标

having 
sum(case when enddate < date'2014-08-05' 
      then enddate 
      else date'2014-08-05' 
    end
  - case when startdate > add_months(date'2014-08-05', -24) 
      then startdate 
      else add_months(date'2014-08-05', -24) 
    end) = date'2014-08-05' - add_months(date'2014-08-05', -24) 
据我所知,这是最有效的方法。希望有帮助


注意那些日期比较条件。我构建了一个用于帮助您进行测试的工具。

既然您只需要执行一次,为什么不将其拆分为多个执行?ie:按月份或季度,按时间限制记录_calendar@ah_hau:如果OP必须在不久的将来反复做这件事怎么办?这里的问题是这样的:我建议将其存储到一个表中,通过将其分为多个时间段多次运行作业。我们无法在需要时动态生成78亿行。@user3586892,也许您需要添加另一个筛选器。请参阅帮助。创建表格People_history,选择Day_id、Firstname、Lastname、status from People Join Time_calendar on Day_id,介于startdate和EndDate之间,startdate>您的开始日期;也许会有帮助你需要这张桌子吗?您尝试查询目标表的速度可能会因为太大而减慢。您计划在数据上运行哪些查询—您是否只需要简单的聚合?在这种情况下,原始表似乎完全适合该查询。。。哦,你的截止日期应该是专门查询的。首先,在SELECT中具有子查询的大型行集在查询其他信息时,往往通过折磨行来获取RBAR行。然后是不同的和顺序的。。。我有一种感觉,通过更仔细地应用日期数学,可以消除明显的差异。ORDER BY应该是完全不必要的-它被转储到表中,而不是显示为结果集;在给定的行数下,您希望在添加行之前禁用索引的创建,并可能重新组织表以启动。@Clockwork Muse:同意。ORDERBY在这里不起作用,但由于此查询只执行一次,因此似乎没有必要进行进一步的调优。我认为一种独特的方式在这里不会有太大的危害。删除ORDER BY。您知道需要对行进行散列/排序才能删除重复的行,对吗?您在整个行集上执行的操作-数十亿计数、多TB存储集?你认为它是如何工作的?事实上,我可能希望这比简单的日历表连接更快地运行服务器……是的,这也是一个很好的方法。现在我还没能找到一种方法来消除这个明显的缺陷。也许OP可以在这部分工作。
select distinct t1.personid,...
from t1
where ( (startdate <= date'2014-08-05' and enddate > date'2014-08-05')
      or (startdate <=  and enddate > add_months(date'2014-08-05', -24))
      or (startdate >= add_months(date'2014-08-05', -24) and enddate < date'2014-08-05' ) )
     and status = 'active'
with temp as (select t1.*
from t1
where ( (startdate <= date'2014-08-05' and enddate > date'2014-08-05')
      or (startdate <= add_months(date'2014-08-05', -24) and enddate > add_months(date'2014-08-05', -24))
      or (startdate >= add_months(date'2014-08-05', -24) and enddate < date'2014-08-05' ) )
  and status = 2)
select temp.id,status,
sum(case when enddate < date'2014-08-05' 
      then enddate 
      else date'2014-08-05' 
    end
  - case when startdate > add_months(date'2014-08-05', -24) 
      then startdate 
      else add_months(date'2014-08-05', -24) 
    end) as duration
from temp
group by temp.id,status
having 
sum(case when enddate < date'2014-08-05' 
      then enddate 
      else date'2014-08-05' 
    end
  - case when startdate > add_months(date'2014-08-05', -24) 
      then startdate 
      else add_months(date'2014-08-05', -24) 
    end) = date'2014-08-05' - add_months(date'2014-08-05', -24)