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('&lt;SQL_ID&gt;') 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 中的子选择太多?的主要内容,如果未能解决你的问题,请参考以下文章

Oracle 12c 中的子选择性能不佳

pl/sql 块中的子选择上的 Oracle 8i 动态 SQL 错误

PHP中的子查询

Oracle通过带有内部连接和返回n行的子查询的桥表选择查询?

使用 Oracle 子选择替换 CASE 语句

Oracle导入外部文件