2

我有多个包含历史数据的表,因此 id 之间没有 1 对 1 的关系。

我必须加入 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 行,它的运行速度也会变慢。

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

4

2 回答 2

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;
于 2012-12-07T11:34:20.883 回答
1

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

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

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

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

我建议如下:

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

    在您的情况下,如果 ( AID, FROM_TIMESTAMP) 是您的主键,则在reference的主键列TABLE_A中具有相同的列。您可能需要并且if和(似乎无关)具有不同的更新方案。TABLE_BTABLE_AFROM_TIMESTAMP_AFROM_TIMESTAMP_CAC

  • 如果你不遵循这个逻辑,你将不得不以不同的方式构建你的查询。如果 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
    
于 2012-12-07T13:16:51.787 回答