【发布时间】:2014-05-14 18:33:58
【问题描述】:
我有一个复杂的选择 - 简化后 - 看起来像这样:
select m.ID,
(select sum(AMOUNT) from A where M_ID = m.ID) sumA,
(select sum(AMOUNT) from B where M_ID = m.ID) sumB,
.....
from M;
表 A,B,... 有一个外键 M_ID 指向表 M。 问题是这个选择非常慢。我想用表连接重写它,但我不知道怎么做,因为
select m.ID
sum(a.AMOUNT),
sum(b.AMOUNT),
.....
from M
join A on a.M_ID = m.ID
join B on b.M_ID = m.ID
....
group by m.ID;
给出不正确(更高)的总和结果,因为 A 或 B 中的每一行都可以计算多次。
有没有办法如何使用例如最佳方式编写该选择?分析函数还是其他方法?
编辑: 原始(未简化)选择的解释计划如下所示:
| 0 | SELECT STATEMENT | |
| 1 | SORT AGGREGATE | |
|* 2 | FILTER | |
|* 3 | TABLE ACCESS BY INDEX ROWID| WORKITEM |
|* 4 | INDEX SKIP SCAN | WORKITEM_U01 |
|* 5 | FILTER | |
|* 6 | TABLE ACCESS FULL | RPRODUCT_INVENTORY_MASTER |
.....
| 31 | SORT AGGREGATE | |
|* 32 | FILTER | |
|* 33 | TABLE ACCESS BY INDEX ROWID| WORKITEM |
|* 34 | INDEX SKIP SCAN | WORKITEM_U01 |
|* 35 | FILTER | |
|* 36 | TABLE ACCESS FULL | RPRODUCT_INVENTORY_MASTER |
| 37 | SORT GROUP BY | |
| 38 | TABLE ACCESS FULL | RPRODUCT |
这就是我想要优化它的原因。此外,AWR 报告显示此选择有 50000 次获取/执行。
编辑2,3: 整个选择看起来像这样:
SELECT rprd.ID,
rprd.NAME,
(select sum(AMOUNT) from WORKITEM
where ACTION='REMOVE'
and trunc(CREATED_DATE) = to_date(:1,'DDMMYYYY')
and PAYEE_ID in
(select rim.RPRODUCT_ID from RPRODUCT_INVENTORY_MASTER rim
where rprd.ID = rim.RPRODUCT_ID
and rim.INVENTORY_DATE = to_date(:2,'DDMMYYYY')),
.....
(select sum(AMOUNT) from WORKITEM
where ACTION='COLLECT'
and trunc(CREATED_DATE) < to_date(:11,'DDMMYYYY')
and PAYEE_ID in
(select rim.RPRODUCT_ID from RPRODUCT_INVENTORY_MASTER rim
where rprd.ID = rim.RPRODUCT_ID
and rim.INVENTORY_DATE < to_date(:12,'DDMMYYYY'))
FROM RPRODUCT rprd
GROUP BY rprd.ID, rprd.NAME
ORDER BY rprd.ID
;
不是我写的 :-),我要重新写了。请注意,比较运算符、ACTION 值和用于比较 INVENTORY_DATE 的日期存在差异。
编辑4: 我尝试像这样重写查询(并且执行计划看起来更好),但遇到了上述“行多重性”问题:
with RPRODUCT_INVENTORY_MASTER# as (
select RPRODUCT_ID, min(INVENTORY_DATE) INVENTORY_DATE
from RPRODUCT_INVENTORY_MASTER
group by RPRODUCT_ID),
WORKITEM# as (
select AMOUNT, PAYEE_ID, ACTION, trunc(CREATED_DATE) CREATED_DATE
from WORKITEM
where ACTION in ('REMOVE','ADD','COLLECT')
)
select rprd.ID,
rprd.NAME,
-- sum(wip2.AMOUNT), -- this is singular because of '=' in inventory_date comparison
sum(abs(wip4.AMOUNT)),
.....
sum(wip12.AMOUNT)
from RPRODUCT rprd
left join RPRODUCT_INVENTORY_MASTER# rim4 on rim4.RPRODUCT_ID = rprd.ID
and rim4.INVENTORY_DATE <= to_date(:4 ,'DDMMYYYY')
left join WORKITEM# wip4 on wip4.PAYEE_ID = rim4.RPRODUCT_ID
and wip4.ACTION='REMOVE'
and wip4.CREATED_DATE = to_date(:3 ,'DDMMYYYY')
.....
left join RPRODUCT_INVENTORY_MASTER# rim12 on rim12.RPRODUCT_ID = rprd.ID
and rim12.INVENTORY_DATE < to_date(:12 ,'DDMMYYYY')
left join WORKITEM# wip12 on wip12.PAYEE_ID = rim12.RPRODUCT_ID
and wip12.ACTION='COLLECT'
and wip12.CREATED_DATE < to_date(:11 ,'DDMMYYYY')
group by rprd.ID, rprd.NAME
order by rprd.ID
;
RPRODUCT_INVENTORY_MASTER# 总是为每个 rprd.ID 提供最多一行。对于每个 RPRODUCT_ID = rprd.ID,WORKITEM# 可以有任意数量的行。
【问题讨论】:
-
在重写查询之前,也许您应该确保您拥有的查询已经过优化。你有查询计划吗?你检查索引了吗?这些桌子有多大?如果您拥有正确的索引和所有索引,我不太确定其他查询的性能会比这个更好。
-
我在我原来的问题中添加了对您的(部分)问题的回答。我不希望索引对这种类型的查询有任何用处,我希望哈希连接。
-
好的。该信息很有用,因为它向我们展示了查询比您最初建议的要复杂得多。但由于这是一个优化问题,我认为它不一定与查询的形式(JOINs vs subSELECTs)有关。只需对大表进行 1 或 2 次未索引表访问即可破坏查询性能。我建议您发布整个查询和整个解释计划。您可能只是遇到了一些复杂的 WHERE 或 subSELECTs 的问题。
-
总的来说,你是对的。但是表 RPRODUCT (M) 目前大约有 100 行,跨越 5 个块,RPRODUCT_INVENTORY_MASTER 大约有 550 行和 5 个块,WORKITEM 有 4500 行和 88 个块。仅此一项并不能导致 50000 次获取/执行,这一定是由于执行计划不好。
-
至于 EDIT4:我必须承认,我更喜欢原始查询。到目前为止,我知道优化器在连接上比在子查询上做得更好,但是我发现将所有这些东西连接在一起并最终将它们全部压缩以获得所需的结果并不是很容易理解。在您的情况下,您必须这样划分:四个表外部连接 a、b、c 和 d:nvl(sum(a.AMOUNT), 0) / best(count(distinct b.id), 1) / best (count(distinct c.id), 1) / 最大(count(distinct d.id), 1)。