有一种常见的情况是,我们需要在一个表中查找,并获取一个键的记录,其中另一个字段具有最高值、最新日期或其他值。我们可以使用一个选择key / max值的子查询,然后将其连接回原始表,或者我们可以使用OLAP函数(RANK()或ROW_NUMBER())。
我正在调优一个查询,它正在为一个大型数据集(4000万条记录)做这件事。Oracle的EXPLAIN并不总是反映真实的性能,所以我不太愿意依赖它。
下面是一个简单的例子。我想得到订单号和杂项其他领域的每一个记录,订单日期匹配的最大的一个订单。
-- Self-join version:
with ords as (
select 'AAA1' as ord_no, to_date('2023-01-01','yyyy-mm-dd') as ord_date, 'A' field1, 'B' field2 from dual union all
select 'AAA1' as ord_no, to_date('2023-02-01','yyyy-mm-dd') as ord_date, 'C' field1, 'D' field2 from dual union all
select 'AAA1' as ord_no, to_date('2023-03-01','yyyy-mm-dd') as ord_date, 'E' field1, 'F' field2 from dual union all
select 'AAA1' as ord_no, to_date('2023-03-01','yyyy-mm-dd') as ord_date, 'E1' field1, 'F1' field2 from dual union all
select 'BBB1' as ord_no, to_date('2023-01-01','yyyy-mm-dd') as ord_date, 'G' field1, 'H' field2 from dual union all
select 'BBB1' as ord_no, to_date('2023-02-01','yyyy-mm-dd') as ord_date, 'I' field1, 'J' field2 from dual union all
select 'BBB1' as ord_no, to_date('2023-03-01','yyyy-mm-dd') as ord_date, 'K' field1, 'L' field2 from dual
),
max_ord as (
select ord_no, max(ord_date) max_ord_date from ords group by ord_no
)
select mo.ord_no, mo.max_ord_date, o.field1, o.field2
from max_ord mo join ords o on mo.ord_no = o.ord_no and mo.max_ord_date = o.ord_Date order by mo.ord_no;
-- OLAP version
-- same data setup as above, omitted for brevity
select ord_no, ord_date, field1, field2 from
(select ord_no, ord_date, field1, field2, rank() over (partition by ord_no order by ord_date desc) maxdt
from ords
)
where maxdt = 1 order by ord_no;
在这两种情况下,预期的结果都是这样的:
"ORD_NO" "ORD_DATE" "FIELD1" "FIELD2"
"AAA1" 3/1/2023 "E" "F"
"AAA1" 3/1/2023 "E1" "F1"
"BBB1" 3/1/2023 "K" "L"
显然,这两种方法都有效;我只是很难找到任何好的城市,其中(一般)将更有效。我们用甲骨文显然,其他数据库的性能可能完全不同。
在我看来,OLAP版本读起来更清晰一些--让你所做的事情更明显。
任何指导或个人经验将受到欢迎!
1条答案
按热度按时间kpbpu0081#
哪个更好完全取决于你想跳过多少历史版本。
如果在你的例子中,大多数
ORD_NO
值都有 * 数百 * 或 * 数千 * 个ORD_DATE
,特别是如果表 * 非常宽 *(每行很多字节),那么想要最后一个将使你转向子查询+索引方法,这样你就不必扫描整个表。在这种情况下,你只想要一小部分的table。**在这种情况下,两列(ORD_NO,ORD_DATE)
**上的索引可以通过并行快速全扫描和嵌套循环来满足GROUP BY
查询。过度暗示主要是为了传达预期的计划:然而,如果只有 * 少量 * 历史版本(每个
ORD_NO
少于100个左右的ORD_DATE
值)或者表非常 * 窄 *(索引不会明显更小),那么索引访问将伤害你,因为你会发现太多的索引条目需要表访问(每个索引条目都是一个低效的整个块读取,只需要1行;低效率在高容量时迅速增加)。在这种情况下,您希望进行全表扫描,这将使窗口选项具有吸引力,因为它避免了第二次扫描,并且不需要索引。您已经正确地编写了SQL(第二个示例),尽管您应该/可以提示它的并行性。窗口函数特别受益于并行性,因为它们不仅由多个从机处理,而且还为每个从机分配最大允许的每进程PGA内存1G,这将比序列化查询可以使用的内存多得多,从而减少了对这些函数所需的排序工作区的慢速I/O临时空间的需求。因为这是一个门槛的问题,没有硬性的规则,只有一般的指导方针。最终,只有通过实际尝试两种方式并查看哪种方式在您的环境中最适合您的数据,才能回答这样的问题。编写SQL没有“最快的方法”--在一种情况下不好的东西在另一种情况下很好。这一切都是关于每一步的相对行数以及数据库在访问、连接和排序方法中可用的资源,数据库必须在每一步中选择这些资源来处理这些行。这意味着它是关于你的数据量、基数、连接关系、索引、分区、压缩、查询 predicate 、缓存、数据库配置、硬件等。最终,你可以预测某些东西可能会如何行动,但你永远不会真正知道,直到你在你的环境中对你的数据进行实际尝试。