Oracle Query 中的子选择太多?
Posted
技术标签:
【中文标题】Oracle Query 中的子选择太多?【英文标题】:Too many subselects in Oracle Query? 【发布时间】:2020-02-24 19:40:00 【问题描述】:我想知道如何提高以下查询的性能,因为它运行时间太长,毕竟它返回数百万行......当谈到SQL...
SELECT CIAM.EXTERNAL_ID,
(SELECT NEW_CHARGES / 100
FROM BI_OWNER.CMF_BALANCE
WHERE ( ACCOUNT_NO, BILL_REF_NO ) = (SELECT ACCOUNT_NO,
MAX(BILL_REF_NO)
FROM BI_OWNER.CMF_BALANCE
WHERE
ACCOUNT_NO = CIAM.ACCOUNT_NO
GROUP BY ACCOUNT_NO))
"AMOUNT LAST BILL",
(SELECT 'ACTIVE DISCOUNT'
|| ' '
|| CCK.AVAIL_PERIODS
|| '/'
|| CC.TOTAL_PERIODS
FROM BI_OWNER.CUSTOMER_CONTRACT_KEY CCK,
BI_OWNER.CUSTOMER_CONTRACT CC
WHERE CC.PARENT_ACCOUNT_NO = CIAM.ACCOUNT_NO
AND CC.END_DT IS NULL
AND EXISTS (SELECT 1
FROM CONTRACT_TYPES
WHERE CONTRACT_TYPE = CC.CONTRACT_TYPE
AND PLAN_ID_DISCOUNT IS NOT NULL
AND DURATION_UNITS = -3)
AND ROWNUM = 1
AND CCK.TRACKING_ID = CC.TRACKING_ID
AND CCK.TRACKING_ID_SERV = CC.TRACKING_ID_SERV) "DISCOUNT",
(SELECT CC.TOTAL_PERIODS
FROM BI_OWNER.CUSTOMER_CONTRACT_KEY CCK,
BI_OWNER.CUSTOMER_CONTRACT CC
WHERE CC.PARENT_ACCOUNT_NO = CIAM.ACCOUNT_NO
AND CC.END_DT IS NULL
AND EXISTS (SELECT 1
FROM CONTRACT_TYPES
WHERE CONTRACT_TYPE = CC.CONTRACT_TYPE
AND PLAN_ID_DISCOUNT IS NOT NULL
AND DURATION_UNITS = -3)
AND ROWNUM = 1
AND CCK.TRACKING_ID = CC.TRACKING_ID
AND CCK.TRACKING_ID_SERV = CC.TRACKING_ID_SERV) "CYCLE"
,
(SELECT SUM(BALANCE_DUE)
FROM BI_OWNER.CMF_BALANCE
WHERE ACCOUNT_NO = CIAM.ACCOUNT_NO
AND PPDD_DATE < TRUNC(SYSDATE))
"DEBT"
FROM BI_OWNER.CUSTOMER_ID_ACCT_MAP CIAM
WHERE EXTERNAL_ID_TYPE = 1
AND EXISTS (SELECT 1
FROM BI_OWNER.CMF
WHERE ACCOUNT_NO = CIAM.ACCOUNT_NO
AND PREV_CUTOFF_DATE > SYSDATE - 30)
【问题讨论】:
我建议您提供示例数据、期望的结果以及您正在实施的逻辑的解释。 如果它返回数百万行,这可能是一个批处理/夜间过程,对吧?人们通常不会浏览一百万行。现在,如果一个批处理过程需要一个小时来运行,那还不错。您的查询需要多长时间? 您使用的是哪个版本的 Oracle?如果您真的要从此查询中获取一百万行,那么这些标量子查询是个坏消息。您的 Oracle 版本将确定 Oracle 优化器将如何自动取消嵌套这些标量子查询。它还将确定您必须重写哪些手动选项。 【参考方案1】:我建议确定查询的 SQL id,然后使用 SQL Monitor Report,因为它会准确地告诉您执行计划是什么以及 SQL 花费大部分时间的位置。
从 SQL*Plus 获取 SQL Monitor Report 的简单方法如下:
spool c:\temp\SQL_Monitor_rpt.html
SET LONG 1000000
SET LONGCHUNKSIZE 1000000
SET LINESIZE 1000
SET PAGESIZE 0
SET TRIM ON
SET TRIMSPOOL ON
SET ECHO OFF
SET FEEDBACK OFF
alter session set "_with_subquery" = optimizer;
SELECT DBMS_SQLTUNE.report_sql_monitor(
sql_id => '&SQLID' ,
type => 'HTML',
report_level => 'ALL') AS report
FROM dual;
spool off
基本上,您需要知道您的表大小以及如何让大型表通过索引(例如,在 where 子句中找到的列上的索引)进行数据访问。
【讨论】:
生成 SQL 监控报告是调查长时间运行的查询的好方法。但大多数开发人员将通过 IDE 而不是命令行访问 Oracle。使用select dbms_sqltune.report_sql_monitor('<SQL_ID>') from dual;
之类的命令将获得更易于访问的报告文本版本,并且不需要 SQL*Plus。
好一个@JonHeller ;)【参考方案2】:
这是一个初步的尝试,可能会带来显着的改进。您的许多查询都是为每条记录执行的相关子查询。相反,我尝试在 select from/join 部分中为每个帐号构建预查询聚合。先查询,再解释逻辑。
SELECT
CIAM.EXTERNAL_ID,
CMF_BALANCE.New_Charges / 100.0 "AMOUNT LAST BILL",
CCKs.Discount,
CCKs.Cycle,
AcntLast30.SumBalance "DEBT"
FROM
(SELECT
CMF.Account_No,
max( Bal.Bill_Ref_No ) MaxBillRef,
sum( case when Bal.PPDD_Date < TRUNC(SYSDATE )
then Bal.Balance_Due else 0 end ) SumBalance
from
BI_OWNER.CMF
JOIN BI_OWNER.CMF_BALANCE BAL
on CMF.Account_No = Bal.Account_No
where
CMF.PREV_CUTOFF_DATE > SYSDATE - 30
group by
CMF.Account_No ) AcntLast30
JOIN BI_OWNER.CUSTOMER_ID_ACCT_MAP CIAM
on AcntLast30.Account_No = CIAM.Account_No
AND CIAM.EXTERNAL_ID_TYPE = 1
JOIN BI_OWNER.CMF_BALANCE
on AcntLast30.Account_No = CMFBalance.Account_No
AND AcntLast30.MaxBillRef = CMFBalance.Bill_Ref_No
JOIN
(select
CC.Parent_Account_No,
CC.TOTAL_PERIODS "CYCLE",
'ACTIVE DISCOUNT' || ' ' || CCK.AVAIL_PERIODS || '/' || CC.TOTAL_PERIODS "DISCOUNT"
FROM
BI_OWNER.CUSTOMER_CONTRACT CC
JOIN BI_OWNER.CUSTOMER_CONTRACT_KEY CCK
ON CC.TRACKING_ID = CCK.TRACKING_ID
AND CC.TRACKING_ID_SERV = CCK.TRACKING_ID_SERV
AND ROWNUM = 1
JOIN ( select distinct Contract_Type
FROM CONTRACT_TYPES
WHERE PLAN_ID_DISCOUNT IS NOT NULL
AND DURATION_UNITS = -3) CT
on CC.Contract_Type = CT.Contract_Type
WHERE
CC.END_DT IS NULL ) CCKs
on AcntLast30.Account_No = CCKs.Parent_Account_No
最初的“FROM”子句,我有一个子查询,因为您似乎只对过去 30 天内的帐户感兴趣。因此,当我在那里时,我将加入您的 CMF_Balance 表并获得每个帐户的最大 Bill_Ref_No 以及当 PPDD_Date 小于 TRUNC(sysdate) 时的余额总和,这是您的“债务”结果列。因此,现在我们有了您对该帐户感兴趣的有限帐户列表、存档的最大账单以及汇总的应付余额。
(SELECT
CMF.Account_No,
max( Bal.Bill_Ref_No ) MaxBillRef,
sum( case when Bal.PPDD_Date < TRUNC(SYSDATE )
then Bal.Balance_Due else 0 end ) SumBalance
from
BI_OWNER.CMF
JOIN BI_OWNER.CMF_BALANCE BAL
on CMF.Account_No = Bal.Account_No
where
CMF.PREV_CUTOFF_DATE > SYSDATE - 30
group by
CMF.Account_No ) AcntLast30
接下来,简单连接到 CIAM 表以仅获取 External_ID_Type = 1 的帐户。这也可以合并到上面的“AcntLast30”别名结果查询中。
JOIN BI_OWNER.CUSTOMER_ID_ACCT_MAP CIAM
on AcntLast30.Account_No = CIAM.Account_No
AND CIAM.EXTERNAL_ID_TYPE = 1
现在,由于“AcntLast30”查询具有帐户和最大账单参考,我们然后加入回到帐户和账单参考的 CMF_Balance # 一次,从而为我们提供 CMF_BALANCE.New_Charges / 100.0 "AMOUNT LAST BILL"
JOIN BI_OWNER.CMF_BALANCE
on AcntLast30.Account_No = CMFBalance.Account_No
AND AcntLast30.MaxBillRef = CMFBalance.Bill_Ref_No
最后是子查询别名结果“CCKs”。由于 Discount 和 Cycle 使用相同的查询/子查询/存在,所以我只运行了一次符合折扣类型的条件并为 JOIN 条件拉出 Account_No。现在我们有了每个帐户的折扣和周期值。
如果您要返回这么多行,我相信通过预先获取这些预查询聚合并由帐户加入所获得的性能将比每次在每一行单独子查询要快得多。
对 ROWNUM 的引用没有任何表/别名引用,因此我不确定该引用对查询的影响。
最后一点。对于可能不适用的折扣之类的事情,您可能需要将其更改为 LEFT JOIN,其中这些值将显示为 NULL。但是在不知道数据范围的情况下,1:给定表中的许多条目的笛卡尔积,我认为这对你来说会很好。在大多数情况下,看起来一切都导致每个帐户只有一条符合条件的记录,其中加入的重要性更高(例如最大账单参考)。
【讨论】:
以上是关于Oracle Query 中的子选择太多?的主要内容,如果未能解决你的问题,请参考以下文章
pl/sql 块中的子选择上的 Oracle 8i 动态 SQL 错误