在用于获取仪表板和报告信息的规范化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分钟内),以一种高效的方式获取某些业务密钥的最新记录的最佳方法是什么?获取数据的方法应隐藏在视图中,以便前端仪表板应用程序可以轻松使用它。
以下是我们的两个想法:
一点头脑风暴:
TYPE
;读Oracle docs for further detailscreate type my_table_t as( /* same fields as my_table */ );
PIPELINED
函数,它接收您需要的所有参数并返回您需要的行类型。阅读Oracle documentation about pipelined table functions了解更多详情。在最通用的形式中,您将收到包含用户提供的SQL过滤器的varchar2
,但由于这可能容易受到注入攻击,我建议使用其他替代方法,例如接受(fk1,...,fkn)作为参数。我们称这个函数为query_my_table
。在此查询中,您可以动态生成所需的确切SQL,每行打开一个REF CURSOR
和PIPE
。在为每种情况生成特定的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
SELECT
:
select * from table(query_my_table(fk1, ..., fkn));
这只是dbms_xplan.display
使用的相同功能的另一个应用。我用这种方法可以想到的主要问题是它不能很好地组成:因为Oracle没有关于table(...)
位的统计信息,如果你开始将其与其他表连接起来,优化器将无法优化那么多。但如果它是“最后的查询”,它应该工作正常。
您可能会发现使用相关子查询更快:
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)
上的索引。
这假设对于给定的键组合不重复日期。
您可以使用以下查询并将其实现为视图:
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上的索引可能有助于加快查询速度。