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

Posted

技术标签:

【中文标题】如何在 SQL 中以高性能的方式使用 PARTITION BY 获取最新记录?【英文标题】:How to obtain the latest record using PARTITION BY in a performant way in SQL? 【发布时间】:2018-09-13 21:37:10 【问题描述】:

在我们用于获取仪表板和报告信息的标准化 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)。不幸的是,这需要更改代码。

【问题讨论】:

“人们可以写一个查询”?您的用户自己编写查询? 附注:对所有记录进行排序以便为它们提供行号只是为了选择第一个记录是很多工作。只需找到最大日期 (max(my_date) over (...)) 并将其与记录的日期进行比较,工作量要少得多。 (但是,好吧,也许优化器无论如何都会在内部执行此操作。) 数据库前面有一个带有用户友好查询构建器的 UI。提供编写原始 SQL 的选项。 【参考方案1】:

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

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) 上建立索引。

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

【讨论】:

如果两个碰巧共享相同的日期,这会不会导致重复的行? @MarkRx 。 . .如果您的表有主键,这很容易解决。 我的意思是,如果同一键组合中的多条记录具有相同的日期,我只想查看一条记录。 我想我可以按照相同的模式通过 pk 进行决胜局以进行更多计算。 @MarkRx 。 . .您将使用order byfetch first 1 row only【参考方案2】:

头脑风暴:

    创建一个 SQL TYPE 代表您的行类型;阅读Oracle docs for further details
create type my_table_t as( /* 与 my_table 相同的字段 */ );
    创建一个PIPELINED 函数,它接收您需要的所有参数并返回您需要的行类型。阅读Oracle documentation about pipelined table functions 了解更多详情。在其最通用的形式中,您将收到一个包含用户提供的 SQL 过滤器的 varchar2,但由于它容易受到注入攻击,我建议使用其他替代方法,例如接受 (fk1, ..., fkn) 作为参数.我们称这个函数为query_my_table。在此查询中,您动态地 生成所需的确切 SQL,每行打开一个 REF CURSORPIPE。当您为每种情况生成特定的 SQL 时,您可以发出所需的确切查询,而无需依赖视图行为。
创建或替换函数 query_my_table(fk1 number, ..., fkn number) return my_table_t pipelined is 查询 varchar2; 开始 query := /* 使用您需要的确切 SQL 创建一个字符串 */ /* 打开引用游标以使用 fk1, ..., fkn 进行查询 */ 环形 /* 未找到时获取并退出 */ /* 将数据加载到 my_table_t 的实例中 */ 管道行(my_table_t_instance); 结束循环; /* 关闭引用光标 */ 返回; 结束问题

    然后您可以通过以下方式SELECT

    select * from table(query_my_table(fk1, ..., fkn));

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

【讨论】:

【参考方案3】:

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

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 上的索引可能有助于加快查询速度。

【讨论】:

我们希望能够同时查看多个 fk 组合。例如,使用 (fk1, fk2) 组​​合查找最新记录,其中 fk1 = 某个值。 我修改了我的查询以适合您的条件 - 有点误解了这个问题...... 不幸的是,通过使用 first_value 分析函数,它无法进入视图,因为它会导致全表扫描。在对整个表计算函数之前,不会应用过滤器。

以上是关于如何在 SQL 中以高性能的方式使用 PARTITION BY 获取最新记录?的主要内容,如果未能解决你的问题,请参考以下文章

以高写入负载在 MySQL 中存储分层数据

ds6000com由以高性能AMQP消息服务器 RabbitMQ199O8836661

如何合理的使用Redis内存

如何在 iPhone 中以高质量调整图像大小

VC 如何使鼠标悬停在按钮上时,按钮以高亮显示。代码不要用MFC,一定用SDK。

如何提高DataGridView的绘画性能?