【问题标题】:Joining multiple tables containing historical data连接多个包含历史数据的表
【发布时间】:2012-11-25 11:32:47
【问题描述】:

我有多个包含历史数据的表,所以 id 之间没有一对一的关系。

我必须加入 id 和指示数据何时处于活动状态的时间戳,如果数据仍然处于活动状态或从未为旧数据设置过,则 TO_TIMESTMP 可以为空。

经过一些分组后我的主表输出如下:

TABLE_A
AID   USER_ID   AMOUNT  FROM_TIMESTMP        TO_TIMESTMP         
1     1         2       11/21/2012 00:00:00  12/04/2012 11:59:00  
1     2         3       11/24/2012 12:00:00  null                 
2     1         2       11/21/2012 01:00:00  null                 

然后我有另一个表,我用它来进一步链接

TABLE_B
AID   CID   FROM_TIMESTMP        TO_TIMESTMP          HIST_ID
1     3     11/01/2012 00:00:00  null                 1
1     3     11/21/2012 00:00:00  12/04/2012 11:59:00  2
1     3     11/24/2012 12:00:00  null                 3
2     4     11/21/2012 00:59:59  null                 4

我的第三张桌子看起来像这样:

TABLE_C
CID   VALUE   FROM_TIMESTMP        TO_TIMESTMP          HIST_ID
3     A       11/01/2012 00:00:00  null                 1
3     B       11/21/2012 00:00:00  11/24/2012 11:59:00  2
3     C       11/24/2012 12:00:00  null                 3
4     D       11/21/2012 01:00:01  null                 4

如果我想将表 A 与表 C 到表 B 的值组合起来,我的预期输出是:

AID   USER_ID  AMOUNT  FROM_TIMESTMP        TO_TIMESTMP          VALUE
1     1        2       11/21/2012 00:00:00  12/04/2012 11:59:00  B
1     2        3       11/24/2012 12:00:00  null                 C
2     1        2       11/21/2012 01:00:00  null                 D

除了表 A 中的 AMOUNT 和表 C 中的 VALUE 之外,所有内容都有索引,我使用以下 SQL 提取数据。

SELECT a.AID, a.USER_ID, a.AMOUNT, a.FROM_TIMESTMP, a.TO_TIMESTMP, c.VALUE from 
(SELECT AID, USER_ID, SUM(AMOUNT), FROM_TIMESTMP, TO_TIMESTMP from TABLE_A GROUP BY AID, USER_ID, FROM_TIMESTMP, TO_TIMESTMP) a
inner join TABLE_B b on b.HIST_ID in (select max(HIST_ID) from TABLE_B
where AID = a.AID and FROM_TIMESTMP <= a.FROM_TIMESTMP+1/2880 and (TO_TIMESTMP>= a.FROM_TIMESTMP or TO_TIMESTMP is null))
inner join TABLE_C c on c.HIST_ID in (select max(HIST_ID) from TABLE_C
where CID = b.CID and FROM_TIMESTMP <= a.FROM_TIMESTMP+1/2880 and (TO_TIMESTMP>= a.FROM_TIMESTMP or TO_TIMESTMP is null));

由于保存数据的时间不一致,我在比较开始时间戳时添加了 30 秒的宽限期,以防它们大约在同一时间创建,有没有办法改进我这样做的方式?

我选择了具有 MAX(HIST_ID) 的那个,因此 TABLE_A 中的 AID=1 和 USER_ID=2 之类的情况只能从其他表中获取与 id/timestamp 匹配的最新行。

在我的真实数据中,我内部加入了 4 个这样的表(而不是只有 2 个),并且它在我的本地测试数据上运行良好(在询问所有数据时在 11 秒内拉出超过 42000 行)。

但是当我尝试在数据量更接近生产的测试环境中运行它时,即使我通过设置 FROM_TIMESTMP 将我在第一个表中查询的行数限制为大约 6000 行,它的运行速度也会变慢。 2 个日期。

有没有办法通过另一种方式来提高我的表连接性能?

【问题讨论】:

  • Oracle 版本在我的本地机器“Oracle Database 10g Express Edition Release 10.2.0.1.0 - Product”上,在测试环境上是“Oracle Database 11g Release 11.2.0.3.0 - 64bit Production "

标签: sql oracle optimization join timestamp


【解决方案1】:

您的查询在一种环境中运行速度更快而在另一种环境中运行速度较慢的原因可能有很多。很可能是因为优化器定义了两个不同的计划,一个运行得更快。可能是因为统计数据略有不同。

您当然可以优化查询以使用索引,但我认为您的主要问题在于数据和/或数据模型。如果数据不好,您会一次又一次地遇到此类问题。

将数据归档到同一个表中是很常见的,它可以用来表示需要历史查询的临时数据。但是,归档数据不应该让您忘记有关数据库设计的基本规则。

在您的情况下,您似乎有三个相关的表:它们将在您的实体关系模型中链接。但是,在设计过程中的某个地方,他们丢失了这个链接,所以现在您无法可靠地确定哪一行依赖于哪一行。

我建议如下:

  • 如果您的 ER 模型中有两个表相关,请添加外键。这将确保您可以在需要时随时加入他们。外键只会在 DML 操作中增加一点成本(并且只会插入、删除和更新主键(?!))。如果您的数据插入一次并多次查询,对性能的影响可以忽略不计。

    在您的情况下,如果 (AID, FROM_TIMESTAMP) 是您在 TABLE_A 中的主键,那么在 TABLE_B 中具有相同的列引用 TABLE_A 的主键列。如果AC(看起来不相关)有不同的更新方案,您可能需要FROM_TIMESTAMP_AFROM_TIMESTAMP_C

  • 如果您不遵循此逻辑,您将不得不以不同的方式构建查询。如果 A、B 和 C 均在历史上存档但未完全引用,您将只能使用单个时间点参考来回答问题,例如“TS 时 DB 的状态是什么”之类的问题:

    SELECT *
      FROM A
      JOIN B on A.aid = B.aid
      JOIN C on C.cid = B.cid
     WHERE a.timestamp_from <= :TS
       AND nvl(a.timestamp_to, DATE '9999-12-31') > :TS 
       AND b.timestamp_from <= :TS
       AND nvl(b.timestamp_to, DATE '9999-12-31') > :TS
       AND c.timestamp_from <= :TS
       AND nvl(c.timestamp_to, DATE '9999-12-31') > :TS
    

【讨论】:

  • 感谢您的输入,我认为与另一种环境相比,它在一种环境中速度较慢的主要原因是数据量(如果要求计算我需要获取的所有行数,我有 42.000 行在本地数据中,在测试环境中为 19.000.000)。这些表有非历史版本的外键,但没有历史数据的外键,这里它们只有 HIST_ID 的主键。我是该项目的新手,我认为现在不是请求将外键添加到历史数据中的最佳时机:这就是我尝试做你建议的最后一件事的原因。
【解决方案2】:

避免 max() 重复子查询的一个简单更改是:

select a.aid,a.user_id,a.amount,a.from_timestmp,a.to_timestmp,a.value
  from (select a.aid,a.user_id,a.amount,a.from_timestmp,a.to_timestmp,c.value, 
       row_number() over (partition by a.aid,a.user_id order by b.hist_id desc, c.hist_id desc) rn
  from (select aid,user_id,sum(amount) amount,from_timestmp,to_timestmp
          from table_a
         group by aid,user_id,from_timestmp,to_timestmp) a
       inner join table_b b
               on b.aid = a.aid
              and b.from_timestmp <= a.from_timestmp + (1 / 2880)
              and ( b.to_timestmp >= a.from_timestmp or b.to_timestmp is null)
       inner join table_c c
               on c.cid = b.cid
              and c.from_timestmp <= a.from_timestmp + (1 / 2880)
              and ( c.to_timestmp >= a.from_timestmp or c.to_timestmp is null)) a
 where rn = 1
  order by a.aid, a.user_id;

【讨论】:

  • 花了一段时间将我的完整 SQL 调用更改为此,但它看起来很有前途。它在我的本地机器上运行有点慢,但是在测试环境中询问解释计划时的成本从 18.446.744.073.709.551.615 减少到 631.385(当不限制按日期的初始调用时),所以它看起来很有希望跨度>
猜你喜欢
  • 2017-04-30
  • 2015-06-29
  • 2014-02-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多