如何在SQL中以高性能的方式使用PARTITION BY获取最新记录?

问题描述 投票:0回答:3

在用于获取仪表板和报告信息的规范化Oracle 12.2数据库中,我们发现用户经常需要知道每个分区的最新记录数据。在某些情况下,我们可以将数据过滤到较小的子集,而在其他情况下,需要整个数据集。在大多数情况下,用户希望一次知道多个分区的最新结果。这个的典型模式是在oracle中如下:

select * from (
    select my_table.*,
           row_number() over (partition by fk1, fk2, ... order by my_date desc) rn
    from my_table
    [where fk1 = 1234]
) where rn = 1

最初我们想将它抽象为一个视图以方便使用,因此人们可以只针对视图编写查询。我们尝试过这样的事情:

create view my_table_latest as (
    select * from (
        select my_table.*,
               row_number() over (partition by fk1, fk2, ... order by my_date desc) rn
        from my_table
    ) where rn = 1
)

select * from my_table_latest where fk1 = 1234

不幸的是,这有两个问题。首先,在应用任何过滤器之前,视图内部的分析函数似乎总是计算整个表。因此,无论使用何种过滤器和索引,都会扫描整个表。其次,当在具有数百万条记录的表上使用时,查询花费的时间比我们想要的要长。

鉴于我们希望我们的数据保持相对新鲜(在10分钟内),以一种高效的方式获取某些业务密钥的最新记录的最佳方法是什么?获取数据的方法应隐藏在视图中,以便前端仪表板应用程序可以轻松使用它。

以下是我们的两个想法:

  • 物化视图 - 使用物化视图每10分钟重新计算一次结果。鉴于查询需要几分钟来计算,我们担心这可能不会很好。此外,基于我们在视图中使用分析查询所发现的内容,我们怀疑使用更有效的刷新策略因使用分析函数而无法工作。
  • 随时跟踪 - 鉴于我们可以访问编写数据的代码,并且数据始终处理最早 - >最新,我们可以轻松跟踪最新记录并将其保存到不同的表中。然后可以创建一个视图,该视图使用此信息并连接回原始表以获取其余的记录详细信息。 “自定义索引”表格类似于(fk1,fk2,my_table.pk,date)。不幸的是,这需要更改代码。
sql oracle oracle12c query-performance sqlperformance
3个回答
1
投票

一点头脑风暴:

  1. 创建表示行类型的SQL TYPE;读Oracle docs for further details
create type my_table_t as( /* same fields as my_table */ );
  1. 创建一个PIPELINED函数,它接收您需要的所有参数并返回您需要的行类型。阅读Oracle documentation about pipelined table functions了解更多详情。在最通用的形式中,您将收到包含用户提供的SQL过滤器的varchar2,但由于这可能容易受到注入攻击,我建议使用其他替代方法,例如接受(fk1,...,fkn)作为参数。我们称这个函数为query_my_table。在此查询中,您可以动态生成所需的确切SQL,每行打开一个REF CURSORPIPE。在为每种情况生成特定的SQL时,您可以发出所需的确切查询,而不需要依赖于查看行为。
create or replace function query_my_table(fk1 number, ..., fkn number) return my_table_t pipelined is
    query varchar2;
begin
   query := /* Create a string with the exact SQL you need */
   /* open ref cursor for query using fk1, ..., fkn */
   loop
       /* fetch & exit when not_found */
       /* load data into instance of my_table_t */
       pipe row(my_table_t_instance);
    end loop;
    /* close ref cursor */
    return;
end issue
  1. 然后您可以通过签发SELECTselect * from table(query_my_table(fk1, ..., fkn));

这只是dbms_xplan.display使用的相同功能的另一个应用。我用这种方法可以想到的主要问题是它不能很好地组成:因为Oracle没有关于table(...)位的统计信息,如果你开始将其与其他表连接起来,优化器将无法优化那么多。但如果它是“最后的查询”,它应该工作正常。


0
投票

您可能会发现使用相关子查询更快:

select t.*
from my_table t
where t.my_date = (select max(t2.my_date)
                   from my_table t2
                   where t2.fk1 = t.fk1 and t2.fk2 = t.fk2 and . . .
                  );

在外部查询中使用带过滤的视图时,Oracle可能会发现更容易优化它。对于性能,您需要(fk1, fk2, . . ., my_date)上的索引。

这假设对于给定的键组合不重复日期。


0
投票

您可以使用以下查询并将其实现为视图:

select *
  from my_table
 where ROWID IN (SELECT first_value(ROWID) over (PARTITION BY fk1, fk2, ...
                                                     ORDER BY my_date DESC)
                   FROM my_table)

fk1,fk2,...,my_date上的索引可能有助于加快查询速度。

© www.soinside.com 2019 - 2024. All rights reserved.