利用ORDERED_PREDICATES优化多个自定函数作为WHERE过滤条件

Posted robinson1988

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了利用ORDERED_PREDICATES优化多个自定函数作为WHERE过滤条件相关的知识,希望对你有一定的参考价值。

经过近1年零零散散的优化,某大型国企的EBS核心系统性能有了巨大提升
EBS系统的优化,一定要资深EBS开发和顶尖的SQL优化专家密切配合,单靠某一方的力量是很难完成优化任务的
最近一年我们大致在做下面内容:
1.业务优化,简化取数逻辑,增加必要的过滤条件,限定数据查询范围
2.PLSQL代码优化,减少游标循环次数,减少游标套游标
3.SQL优化,纠正了很多不合理的写法,做了大量的SQL等价改写
4.执行计划固定,数据倾斜问题处理,绑定变量跑得慢带入具体值跑得快的问题处理
5.添加了一些合适的索引,对部分表进行了分区改造
6.处理了一些19c新特征导致SQL变慢的case
...等等等等...
对我来说比较容易啃的骨头基本上都啃完了,现在剩下的基本上全是带有复杂自定义函数的SQL了
话不多说,我们来看今天要分享的案例吧

---初始化语句
DECLARE
  L_RETURN_STATUS VARCHAR2(3000);
  L_RETURN_MSG    VARCHAR2(3000);
BEGIN
  FND_GLOBAL.APPS_INITIALIZE(USER_ID      => 53276,
                             RESP_ID      => 124501,
                             RESP_APPL_ID => 20003);

  MO_GLOBAL.INIT('M');
  NGL_INIT_DEPT_PKG.INIT_DEPT(P_BU_FROM       => NULL,
                              P_DEPT_FROM     => NULL,
                              P_DEPT_TO       => 'T',
                              X_RETURN_STATUS => L_RETURN_STATUS,
                              X_RETURN_MSG    => L_RETURN_MSG);
END;
/
---临时表数据初始化
INSERT INTO NCM_COMMON_GT_TMP
  (NUM2
  ,CHAR2)
  SELECT HOU.ORGANIZATION_ID
        ,'ORG'
    FROM HR_OPERATING_UNITS HOU
   WHERE XY_COM_DEPT_READ.COM_READ(HOU.ORGANIZATION_ID) = 'Y';
---待优化的SQL,跑42-45秒,为了避免泄密客户代码,我对FROM后的表名字做了处理
INSERT INTO NCM_COMMON_GT_TMP
  (NUM1)
  SELECT AI.INVOICE_ID
    FROM AI
   WHERE AI.INVOICE_TYPE_LOOKUP_CODE = 'PREPAYMENT'
     AND NAP_UNVERIF_PREPAYMENT_PKG1.GET_INVOICE_STATUS(AI.INVOICE_ID,
                                                        AI.INVOICE_AMOUNT,
                                                        AI.PAYMENT_STATUS_FLAG,
                                                        AI.INVOICE_TYPE_LOOKUP_CODE,
                                                        FND_DATE.CANONICAL_TO_DATE('2022-10-31 00:00:00'),
                                                        DECODE(AI.APPROVED_AMOUNT,
                                                               NULL,
                                                               AI.CANCELLED_AMOUNT,
                                                               0,
                                                               AI.CANCELLED_AMOUNT,
                                                               AI.APPROVED_AMOUNT)) = 'Y'
     AND AI.INVOICE_CURRENCY_CODE = NVL(null, AI.INVOICE_CURRENCY_CODE)
     AND AI.VENDOR_ID = NVL(null, AI.VENDOR_ID)
     AND EXISTS (SELECT 1
            FROM NCM_COMMON_GT_TMP NCGT
           WHERE NCGT.CHAR1 = AI.ATTRIBUTE15)
     AND AI.ATTRIBUTE15 BETWEEN NVL(null, AI.ATTRIBUTE15) AND
         NVL(null, AI.ATTRIBUTE15)
     AND AI.ORG_ID = NVL(95, AI.ORG_ID)
     AND EXISTS
   (SELECT 1
            FROM NCM_COMMON_GT_TMP NCGT
           WHERE NCGT.CHAR2 = 'ORG'
             AND NCGT.NUM2 = AI.ORG_ID)
     AND AI.ATTRIBUTE8 = NVL(null, AI.ATTRIBUTE8)
     AND FND_DATE.CANONICAL_TO_DATE('2022-10-31 00:00:00') >=
         (SELECT MIN(AID.ACCOUNTING_DATE)
            FROM AP_INVOICE_DISTRIBUTIONS_ALL AID
           WHERE AID.INVOICE_ID = AI.INVOICE_ID)
     AND NAP_UNVERIF_PREPAYMENT_PKG1.GET_CANCEL(AI.INVOICE_ID,
                                                FND_DATE.CANONICAL_TO_DATE('2022-10-31 00:00:00'),
                                                AI.CANCELLED_DATE) = 'N'
     AND (NOT EXISTS
          (SELECT 1
             FROM XLA.XLA_TRANSACTION_ENTITIES XET
                 ,XLA.XLA_EVENTS               XE
            WHERE XET.ENTITY_ID = XE.ENTITY_ID
              AND XET.ENTITY_CODE = 'AP_INVOICES'
              AND XET.APPLICATION_ID = 200
              AND XE.APPLICATION_ID = 200
              AND (SELECT MIN(XE.EVENT_DATE)
                     FROM XLA.XLA_TRANSACTION_ENTITIES XET
                         ,XLA.XLA_EVENTS               XE
                    WHERE XET.ENTITY_ID = XE.ENTITY_ID
                      AND XET.ENTITY_CODE = 'AP_INVOICES'
                      AND XE.EVENT_TYPE_CODE = 'PREPAYMENT VALIDATED'
                      AND XET.SOURCE_ID_INT_1 = AI.INVOICE_ID
                      AND XET.APPLICATION_ID = 200
                      AND XET.LEDGER_ID = AI.SET_OF_BOOKS_ID
                      AND XE.APPLICATION_ID = 200) >
                  FND_DATE.CANONICAL_TO_DATE('2022-10-31 00:00:00')
              AND XE.EVENT_TYPE_CODE = 'PREPAYMENT VALIDATED'
              AND XET.APPLICATION_ID = 200
              AND XE.APPLICATION_ID = 200
              AND XET.SOURCE_ID_INT_1 = AI.INVOICE_ID) AND NOT EXISTS
          (SELECT 1
             FROM XLA.XLA_TRANSACTION_ENTITIES XET
                 ,XLA.XLA_EVENTS               XE
            WHERE XET.ENTITY_ID = XE.ENTITY_ID
              AND XET.ENTITY_CODE = 'AP_INVOICES'
              AND XE.EVENT_TYPE_CODE = 'PREPAYMENT CANCELLED'
              AND XET.SOURCE_ID_INT_1 = AI.INVOICE_ID
              AND XET.APPLICATION_ID = 200
              AND XET.LEDGER_ID = AI.SET_OF_BOOKS_ID
              AND XE.APPLICATION_ID = 200
              AND XE.EVENT_DATE <=
                  NVL(FND_DATE.CANONICAL_TO_DATE('2022-10-31 00:00:00'), XE.EVENT_DATE)))
     AND (('Y' = 'Y' AND
         DECODE(AI.CANCELLED_DATE,
                  NULL,
                  AI.INVOICE_AMOUNT,
                  NAP_INVOICES_COMMON_PKG.GET_INVOICE_AMOUNT(AI.INVOICE_ID,
                                                             FND_DATE.CANONICAL_TO_DATE('2022-10-31 00:00:00'))) =
         NAP_UNVERIF_PREPAYMENT_PKG1.GET_PAY_AMT(FND_DATE.CANONICAL_TO_DATE('2022-10-31 00:00:00'),
                                                   AI.INVOICE_ID)) OR
         'Y' = 'N')
     AND (DECODE(AI.CANCELLED_DATE,
                 NULL,
                 AI.INVOICE_AMOUNT,
                 NAP_INVOICES_COMMON_PKG.GET_INVOICE_AMOUNT(AI.INVOICE_ID,
                                                            FND_DATE.CANONICAL_TO_DATE('2022-10-31 00:00:00'))) -
         NAP_UNVERIF_PREPAYMENT_PKG1.GET_UNVERIFICATION_AMOUNT(AI.INVOICE_ID,
                                                                AI.VENDOR_ID,
                                                                FND_DATE.CANONICAL_TO_DATE('2022-10-31 00:00:00'),
                                                                AI.INVOICE_CURRENCY_CODE)) <> 0;

执行一下SQL语句:
                                                                
5858 rows inserted
Executed in 42.283 seconds                                                                

一共要插入5858条数据,耗时42秒

去掉SQL语句中带有自定义函数的2个WHERE条件(还有其他地方有自定义函数,不过影响不大)


   AND NAP_UNVERIF_PREPAYMENT_PKG1.GET_INVOICE_STATUS(AI.INVOICE_ID,
                                                        AI.INVOICE_AMOUNT,
                                                        AI.PAYMENT_STATUS_FLAG,
                                                        AI.INVOICE_TYPE_LOOKUP_CODE,
                                                        FND_DATE.CANONICAL_TO_DATE('2022-10-31 00:00:00'),
                                                        DECODE(AI.APPROVED_AMOUNT,
                                                               NULL,
                                                               AI.CANCELLED_AMOUNT,
                                                               0,
                                                               AI.CANCELLED_AMOUNT,
                                                               AI.APPROVED_AMOUNT)) = 'Y'
   AND (DECODE(AI.CANCELLED_DATE,
               NULL,
               AI.INVOICE_AMOUNT,
               NAP_INVOICES_COMMON_PKG.GET_INVOICE_AMOUNT(AI.INVOICE_ID,
                                                          FND_DATE.CANONICAL_TO_DATE('2022-10-31 00:00:00'))) -
         NAP_UNVERIF_PREPAYMENT_PKG1.GET_UNVERIFICATION_AMOUNT(AI.INVOICE_ID,
                                                                AI.VENDOR_ID,
                                                                FND_DATE.CANONICAL_TO_DATE('2022-10-31 00:00:00'),
                                                                AI.INVOICE_CURRENCY_CODE)) <> 0		

执行一下SQL语句:

79181 rows inserted
Executed in 4.797 seconds

由此可见,上面2个自定义函数的WHERE条件对SQL性能有严重影响,现在要做的是定位哪个自定义函数的WHERE条件最影响性能

将第一个自定义函数的WHERE条件带入到SQL语句中

  AND NAP_UNVERIF_PREPAYMENT_PKG1.GET_INVOICE_STATUS(AI.INVOICE_ID,
                                                        AI.INVOICE_AMOUNT,
                                                        AI.PAYMENT_STATUS_FLAG,
                                                        AI.INVOICE_TYPE_LOOKUP_CODE,
                                                        FND_DATE.CANONICAL_TO_DATE('2022-10-31 00:00:00'),
                                                        DECODE(AI.APPROVED_AMOUNT,
                                                               NULL,
                                                               AI.CANCELLED_AMOUNT,
                                                               0,
                                                               AI.CANCELLED_AMOUNT,
                                                               AI.APPROVED_AMOUNT)) = 'Y'

6219 rows inserted
Executed in 51.339 seconds

现在将这个WHERE条件注释掉,将第二个自定义函数的WHERE条件带入SQL语句中

   AND (DECODE(AI.CANCELLED_DATE,
               NULL,
               AI.INVOICE_AMOUNT,
               NAP_INVOICES_COMMON_PKG.GET_INVOICE_AMOUNT(AI.INVOICE_ID,
                                                          FND_DATE.CANONICAL_TO_DATE('2022-10-31 00:00:00'))) -
         NAP_UNVERIF_PREPAYMENT_PKG1.GET_UNVERIFICATION_AMOUNT(AI.INVOICE_ID,
                                                                AI.VENDOR_ID,
                                                                FND_DATE.CANONICAL_TO_DATE('2022-10-31 00:00:00'),
                                                                AI.INVOICE_CURRENCY_CODE)) <> 0	

5858 rows inserted
Executed in 24.372 seconds

通过上面的信息,得到结论:

  AND (DECODE(AI.CANCELLED_DATE,
               NULL,
               AI.INVOICE_AMOUNT,
               NAP_INVOICES_COMMON_PKG.GET_INVOICE_AMOUNT(AI.INVOICE_ID,
                                                          FND_DATE.CANONICAL_TO_DATE('2022-10-31 00:00:00'))) -
         NAP_UNVERIF_PREPAYMENT_PKG1.GET_UNVERIFICATION_AMOUNT(AI.INVOICE_ID,
                                                                AI.VENDOR_ID,
                                                                FND_DATE.CANONICAL_TO_DATE('2022-10-31 00:00:00'),
                                                                AI.INVOICE_CURRENCY_CODE)) <> 0        

                                                                

   AND NAP_UNVERIF_PREPAYMENT_PKG1.GET_INVOICE_STATUS(AI.INVOICE_ID,
                                                        AI.INVOICE_AMOUNT,
                                                        AI.PAYMENT_STATUS_FLAG,
                                                        AI.INVOICE_TYPE_LOOKUP_CODE,
                                                        FND_DATE.CANONICAL_TO_DATE('2022-10-31 00:00:00'),
                                                        DECODE(AI.APPROVED_AMOUNT,
                                                               NULL,
                                                               AI.CANCELLED_AMOUNT,
                                                               0,
                                                               AI.CANCELLED_AMOUNT,
                                                               AI.APPROVED_AMOUNT)) = 'Y'

能更有效的过滤数据且耗费的资源更少,原始的SQL写法是

   AND NAP_UNVERIF_PREPAYMENT_PKG1.GET_INVOICE_STATUS(AI.INVOICE_ID,
                                                        AI.INVOICE_AMOUNT,
                                                        AI.PAYMENT_STATUS_FLAG,
                                                        AI.INVOICE_TYPE_LOOKUP_CODE,
                                                        FND_DATE.CANONICAL_TO_DATE('2022-10-31 00:00:00'),
                                                        DECODE(AI.APPROVED_AMOUNT,
                                                               NULL,
                                                               AI.CANCELLED_AMOUNT,
                                                               0,
                                                               AI.CANCELLED_AMOUNT,
                                                               AI.APPROVED_AMOUNT)) = 'Y'

在前面过滤,所以,现在将这个WHERE过滤条件放到最后,并且对SQL添加HINT /*+ ORDERED_PREDICATES */

INSERT INTO NCM_COMMON_GT_TMP
  (NUM2
  ,CHAR2)
  SELECT HOU.ORGANIZATION_ID
        ,'ORG'
    FROM HR_OPERATING_UNITS HOU
   WHERE XY_COM_DEPT_READ.COM_READ(HOU.ORGANIZATION_ID) = 'Y';
    
INSERT INTO NCM_COMMON_GT_TMP
  (NUM1)
  SELECT  /*+ ORDERED_PREDICATES */ AI.INVOICE_ID
    FROM AI
   WHERE AI.INVOICE_TYPE_LOOKUP_CODE = 'PREPAYMENT'
     AND AI.INVOICE_CURRENCY_CODE = NVL(null, AI.INVOICE_CURRENCY_CODE)
     AND AI.VENDOR_ID = NVL(null, AI.VENDOR_ID)
     AND EXISTS (SELECT 1
            FROM NCM_COMMON_GT_TMP NCGT
           WHERE NCGT.CHAR1 = AI.ATTRIBUTE15)
     AND AI.ATTRIBUTE15 BETWEEN NVL(null, AI.ATTRIBUTE15) AND
         NVL(null, AI.ATTRIBUTE15)
     AND AI.ORG_ID = NVL(95, AI.ORG_ID)
     AND EXISTS
   (SELECT 1
            FROM NCM_COMMON_GT_TMP NCGT
           WHERE NCGT.CHAR2 = 'ORG'
             AND NCGT.NUM2 = AI.ORG_ID)
     AND AI.ATTRIBUTE8 = NVL(null, AI.ATTRIBUTE8)
     AND FND_DATE.CANONICAL_TO_DATE('2022-10-31 00:00:00') >=
         (SELECT MIN(AID.ACCOUNTING_DATE)
            FROM AP_INVOICE_DISTRIBUTIONS_ALL AID
           WHERE AID.INVOICE_ID = AI.INVOICE_ID)
     AND NAP_UNVERIF_PREPAYMENT_PKG1.GET_CANCEL(AI.INVOICE_ID,
                                                FND_DATE.CANONICAL_TO_DATE('2022-10-31 00:00:00'),
                                                AI.CANCELLED_DATE) = 'N'
     AND (NOT EXISTS
          (SELECT 1
             FROM XLA.XLA_TRANSACTION_ENTITIES XET
                 ,XLA.XLA_EVENTS               XE
            WHERE XET.ENTITY_ID = XE.ENTITY_ID
              AND XET.ENTITY_CODE = 'AP_INVOICES'
              AND XET.APPLICATION_ID = 200
              AND XE.APPLICATION_ID = 200
              AND (SELECT MIN(XE.EVENT_DATE)
                     FROM XLA.XLA_TRANSACTION_ENTITIES XET
                         ,XLA.XLA_EVENTS               XE
                    WHERE XET.ENTITY_ID = XE.ENTITY_ID
                      AND XET.ENTITY_CODE = 'AP_INVOICES'
                      AND XE.EVENT_TYPE_CODE = 'PREPAYMENT VALIDATED'
                      AND XET.SOURCE_ID_INT_1 = AI.INVOICE_ID
                      AND XET.APPLICATION_ID = 200
                      AND XET.LEDGER_ID = AI.SET_OF_BOOKS_ID
                      AND XE.APPLICATION_ID = 200) >
                  FND_DATE.CANONICAL_TO_DATE('2022-10-31 00:00:00')
              AND XE.EVENT_TYPE_CODE = 'PREPAYMENT VALIDATED'
              AND XET.APPLICATION_ID = 200
              AND XE.APPLICATION_ID = 200
              AND XET.SOURCE_ID_INT_1 = AI.INVOICE_ID) AND NOT EXISTS
          (SELECT 1
             FROM XLA.XLA_TRANSACTION_ENTITIES XET
                 ,XLA.XLA_EVENTS               XE
            WHERE XET.ENTITY_ID = XE.ENTITY_ID
              AND XET.ENTITY_CODE = 'AP_INVOICES'
              AND XE.EVENT_TYPE_CODE = 'PREPAYMENT CANCELLED'
              AND XET.SOURCE_ID_INT_1 = AI.INVOICE_ID
              AND XET.APPLICATION_ID = 200
              AND XET.LEDGER_ID = AI.SET_OF_BOOKS_ID
              AND XE.APPLICATION_ID = 200
              AND XE.EVENT_DATE <=
                  NVL(FND_DATE.CANONICAL_TO_DATE('2022-10-31 00:00:00'), XE.EVENT_DATE)))
     AND (('Y' = 'Y' AND
         DECODE(AI.CANCELLED_DATE,
                  NULL,
                  AI.INVOICE_AMOUNT,
                  NAP_INVOICES_COMMON_PKG.GET_INVOICE_AMOUNT(AI.INVOICE_ID,
                                                             FND_DATE.CANONICAL_TO_DATE('2022-10-31 00:00:00'))) =
         NAP_UNVERIF_PREPAYMENT_PKG1.GET_PAY_AMT(FND_DATE.CANONICAL_TO_DATE('2022-10-31 00:00:00'),
                                                   AI.INVOICE_ID)) OR
         'Y' = 'N')
     AND (DECODE(AI.CANCELLED_DATE,
                 NULL,
                 AI.INVOICE_AMOUNT,
                 NAP_INVOICES_COMMON_PKG.GET_INVOICE_AMOUNT(AI.INVOICE_ID,
                                                            FND_DATE.CANONICAL_TO_DATE('2022-10-31 00:00:00'))) -
         NAP_UNVERIF_PREPAYMENT_PKG1.GET_UNVERIFICATION_AMOUNT(AI.INVOICE_ID,
                                                                AI.VENDOR_ID,
                                                                FND_DATE.CANONICAL_TO_DATE('2022-10-31 00:00:00'),
                                                                AI.INVOICE_CURRENCY_CODE)) <> 0
     AND NAP_UNVERIF_PREPAYMENT_PKG1.GET_INVOICE_STATUS(AI.INVOICE_ID,
                                                        AI.INVOICE_AMOUNT,
                                                        AI.PAYMENT_STATUS_FLAG,
                                                        AI.INVOICE_TYPE_LOOKUP_CODE,
                                                        FND_DATE.CANONICAL_TO_DATE('2022-10-31 00:00:00'),
                                                        DECODE(AI.APPROVED_AMOUNT,
                                                               NULL,
                                                               AI.CANCELLED_AMOUNT,
                                                               0,
                                                               AI.CANCELLED_AMOUNT,
                                                               AI.APPROVED_AMOUNT)) = 'Y'		

5858 rows inserted
Executed in 28.261 seconds

SQL由之前的42秒优化到28秒,也许有人会说,只把过滤条件放最后,不加HINT行不行,在本案例里面,不行,CBO很SB的自动将耗时的WHERE过滤条件放在前面去了

优化到这里没完,之前写文章的时候,我经常说要将自定义函数里面的SQL拿出来,直接与原始SQL进行关联
现在来看一下NAP_UNVERIF_PREPAYMENT_PKG1.GET_INVOICE_STATUS的代码(为了避免泄密,我去掉了注释)

FUNCTION get_invoice_status(l_invoice_id                 IN NUMBER
                             ,l_invoice_amount           IN NUMBER
                             ,l_payment_status_flag      IN VARCHAR2
                             ,l_invoice_type_lookup_code IN VARCHAR2
                             ,p_date                     DATE
                             ,l_cancel_amount            IN NUMBER) RETURN VARCHAR2  IS
    v_temp_str VARCHAR2(50);
    v_temp_type VARCHAR2(50);
  BEGIN

    v_temp_str := nvl(ap_invoices_pkg.get_approval_status(l_invoice_id
                                                         ,l_invoice_amount
                                                         ,l_payment_status_flag
                                                         ,l_invoice_type_lookup_code)
                     ,'N');

    IF (v_temp_str = 'AVAILABLE') THEN
      RETURN 'Y';
    ELSIF (v_temp_str = 'FULL') THEN
      SELECT apha.transaction_type
        INTO v_temp_type
        FROM ap_prepay_history_all apha
       WHERE apha.prepay_history_id = (SELECT MAX(aph.prepay_history_id)
                                         FROM ap_prepay_history_all aph
                                        WHERE aph.prepay_invoice_id = l_invoice_id
                                          AND aph.accounting_date <= p_date)
         AND apha.transaction_type <> 'PREPAYMENT APPLICATION ADJ';
      IF (v_temp_type = 'PREPAYMENT UNAPPLIED' OR v_temp_type IS NULL) THEN
        RETURN 'Y';
   
      ELSIF v_temp_type = 'PREPAYMENT APPLIED'
            AND adj_applited_sutats(l_invoice_amount, l_invoice_id, p_date, l_cancel_amount)
            OR prepayment_application_adj(l_invoice_id, p_date) THEN
        RETURN 'Y';
      ELSE
        RETURN 'N';
      END IF;
    ELSIF 
     v_temp_str IN ('NEVER APPROVED', 'NEEDS REAPPROVAL', 'UNAPPROVED') THEN
      RETURN 'N';
    ELSE
      RETURN 'Y';
    END IF;

  EXCEPTION
    WHEN OTHERS THEN
      RETURN 'Y';
  END;

函数里面又套了一个函数,而且还有一堆IF ELSEIF,放弃改写...(注:这段函数代码我没有单独优化)

因为这个函数放弃了改写,那另外一个WHERE条件中的函数也放弃改写了
现在来看一下NAP_INVOICES_COMMON_PKG.GET_INVOICE_AMOUNT的代码

FUNCTION get_invoice_amount(p_invoice_id IN NUMBER, p_date IN DATE)
    RETURN NUMBER IS
    l_amount NUMBER;
  BEGIN
    SELECT
           decode(ap.cancelled_amount,
                  NULL,
                  ap.approved_amount,
                  0,
                  ap.approved_amount,
                  ap.cancelled_amount)
      INTO l_amount
      FROM ap_invoices_all ap
     WHERE ap.invoice_id = p_invoice_id
       AND ap.cancelled_date IS NOT NULL
       AND ap.cancelled_date > p_date;
    RETURN l_amount;
  EXCEPTION
    WHEN OTHERS THEN
      RETURN 0;
  END;

这段代码很简单,简单得都找不到可优化的地方(事实上它的确不太影响性能)
现在来看一下NAP_UNVERIF_PREPAYMENT_PKG1.GET_UNVERIFICATION_AMOUNT的代码
 

FUNCTION get_unverification_amount(p_invoice_id       IN NUMBER
                                    ,p_vendor_id      IN NUMBER
                                    ,p_date           DATE
                                    ,p_currency_code  IN VARCHAR2
                                    ,p_nap_invoice_id IN NUMBER DEFAULT NULL) RETURN NUMBER IS
    v_amount          NUMBER;
    l_adj_amount      NUMBER;
    l_amount          NUMBER;
    l_ap_amount       NUMBER;
    v_ledger_currency VARCHAR2(10);
  BEGIN
    SELECT nvl(SUM(-apd.amount), 0)
      INTO l_amount
      FROM ap_prepay_history_all        aph
          ,ap_invoice_distributions_all apd
     WHERE aph.prepay_invoice_id = p_invoice_id
       AND aph.accounting_date <= p_date
       AND aph.invoice_id = apd.invoice_id
       AND apd.accounting_date <= p_date
       AND aph.invoice_line_number = apd.invoice_line_number
       AND apd.line_type_lookup_code = 'PREPAY'
       AND (apd.attribute12 = to_char(p_nap_invoice_id) OR p_nap_invoice_id IS NULL)
       AND nvl(apd.match_status_flag, 'N') IN ('A', 'T');
    SELECT nvl(SUM(nvl(aell.accounted_dr, 0) - nvl(aell.accounted_cr, 0)), 0)
      INTO l_adj_amount
      FROM xla.xla_ae_headers           aehl
          ,xla.xla_ae_lines             aell
          ,ap_prepay_history_all        apha
          ,ap_invoice_distributions_all apd 
     WHERE apha.transaction_type = 'PREPAYMENT APPLICATION ADJ'
       AND apha.prepay_invoice_id = p_invoice_id
       AND apha.accounting_event_id = aehl.event_id
       AND aehl.ae_header_id = aell.ae_header_id
       AND aell.accounting_class_code = 'PREPAID_EXPENSE'
       AND aehl.application_id = 200
       AND aell.accounting_date <= p_date
       AND apd.invoice_id = apha.invoice_id
       AND apha.invoice_line_number = apd.invoice_line_number
       AND (apd.attribute12 = to_char(p_nap_invoice_id) OR p_nap_invoice_id IS NULL)
    SELECT nvl(SUM(nvl(aell.accounted_cr, 0) - nvl(aell.accounted_dr, 0)), 0)
      INTO l_ap_amount
      FROM xla.xla_ae_headers           aehl
          ,xla.xla_ae_lines             aell
          ,ap_prepay_history_all        apha
          ,ap_invoice_distributions_all apd 
     WHERE apha.transaction_type IN ('PREPAYMENT APPLIED', 'PREPAYMENT UNAPPLIED')
       AND apha.prepay_invoice_id = p_invoice_id
       AND apha.accounting_event_id = aehl.event_id
       AND aehl.ae_header_id = aell.ae_header_id
       AND aell.accounting_class_code = 'PREPAID_EXPENSE'
       AND aell.accounting_date <= p_date
       AND aehl.application_id = 200
       AND apd.invoice_id = apha.invoice_id
       AND apha.invoice_line_number = apd.invoice_line_number
       AND (apd.attribute12 = to_char(p_nap_invoice_id) OR p_nap_invoice_id IS NULL);
    SELECT l.currency_code
      INTO v_ledger_currency
      FROM gl_ledgers         l
       ,  ap_invoices_all a
     WHERE 
       a.invoice_id = p_invoice_id
       AND a.set_of_books_id = l.ledger_id;
    IF v_ledger_currency = p_currency_code 
       AND
       l_amount > l_ap_amount THEN
      v_amount := l_amount - (l_amount - l_ap_amount) - l_adj_amount;
    ELSE
      v_amount := l_amount - l_adj_amount;
    END IF;
    RETURN v_amount;
  EXCEPTION
    WHEN OTHERS THEN
      RETURN 0;
  END;

这段函数代码可以进行优化,具体的思路限于篇幅就不写了,有兴趣的同学COPY两段代码,仔细对比就知道改了哪些地方
 

create or replace FUNCTION test_get_unverification_amount(p_invoice_id     IN NUMBER,
                                                          p_vendor_id      IN NUMBER,
                                                          p_date           DATE,
                                                          p_currency_code  IN VARCHAR2,
                                                          p_nap_invoice_id IN NUMBER DEFAULT NULL)
  RETURN NUMBER IS
  v_amount          NUMBER;
  l_adj_amount      NUMBER;
  l_amount          NUMBER;
  l_ap_amount       NUMBER;
  v_ledger_currency VARCHAR2(10);
BEGIN
  if p_nap_invoice_id is not null then
    SELECT /*+ first_rows */
     nvl(SUM(-apd.amount), 0)
      INTO l_amount
      FROM ap_prepay_history_all aph, ap_invoice_distributions_all apd
     WHERE aph.prepay_invoice_id = p_invoice_id
       AND aph.accounting_date <= p_date
       AND aph.invoice_id = apd.invoice_id
       AND apd.accounting_date <= p_date
       AND aph.invoice_line_number = apd.invoice_line_number
       AND apd.line_type_lookup_code = 'PREPAY'
       AND (apd.attribute12 = to_char(p_nap_invoice_id))
       AND nvl(apd.match_status_flag, 'N') IN ('A', 'T');
    SELECT /*+ first_rows */
     nvl(SUM(case
               when apha.transaction_type = 'PREPAYMENT APPLICATION ADJ' then
                nvl(aell.accounted_dr, 0) - nvl(aell.accounted_cr, 0)
             end),
         0),
     nvl(SUM(case
               when apha.transaction_type IN
                    ('PREPAYMENT APPLIED', 'PREPAYMENT UNAPPLIED') then
                nvl(aell.accounted_cr, 0) - nvl(aell.accounted_dr, 0)
             end),
         0)
      INTO l_adj_amount, l_ap_amount
      FROM xla.xla_ae_headers           aehl,
           xla.xla_ae_lines             aell,
           ap_prepay_history_all        apha,
           ap_invoice_distributions_all apd
     WHERE apha.prepay_invoice_id = p_invoice_id
       AND apha.accounting_event_id = aehl.event_id
       AND aehl.ae_header_id = aell.ae_header_id
       AND aell.accounting_class_code = 'PREPAID_EXPENSE'
       AND aell.accounting_date <= p_date
       AND aehl.application_id = 200
       AND apd.invoice_id = apha.invoice_id
       AND apha.invoice_line_number = apd.invoice_line_number
       AND (apd.attribute12 = to_char(p_nap_invoice_id));
  else
    SELECT /*+ first_rows */
     nvl(SUM(-apd.amount), 0)
      INTO l_amount
      FROM ap_prepay_history_all aph, ap_invoice_distributions_all apd
     WHERE aph.prepay_invoice_id = p_invoice_id
       AND aph.accounting_date <= p_date
       AND aph.invoice_id = apd.invoice_id
       AND apd.accounting_date <= p_date
       AND aph.invoice_line_number = apd.invoice_line_number
       AND apd.line_type_lookup_code = 'PREPAY'
       AND nvl(apd.match_status_flag, 'N') IN ('A', 'T');
  
    SELECT /*+ first_rows */
     nvl(SUM(case
               when apha.transaction_type = 'PREPAYMENT APPLICATION ADJ' then
                nvl(aell.accounted_dr, 0) - nvl(aell.accounted_cr, 0)
             end),
         0),
     nvl(SUM(case
               when apha.transaction_type IN
                    ('PREPAYMENT APPLIED', 'PREPAYMENT UNAPPLIED') then
                nvl(aell.accounted_cr, 0) - nvl(aell.accounted_dr, 0)
             end),
         0)
      INTO l_adj_amount, l_ap_amount
      FROM xla.xla_ae_headers           aehl,
           xla.xla_ae_lines             aell,
           ap_prepay_history_all        apha,
           ap_invoice_distributions_all apd
     WHERE apha.prepay_invoice_id = p_invoice_id
       AND apha.accounting_event_id = aehl.event_id
       AND aehl.ae_header_id = aell.ae_header_id
       AND aell.accounting_class_code = 'PREPAID_EXPENSE'
       AND aell.accounting_date <= p_date
       AND aehl.application_id = 200
       AND apd.invoice_id = apha.invoice_id
       AND apha.invoice_line_number = apd.invoice_line_number;
  end if;

  SELECT /*+ first_rows */
   l.currency_code
    INTO v_ledger_currency
    FROM gl_ledgers l
        ,
         ap_invoices_all a
   WHERE 
   a.invoice_id = p_invoice_id
   AND a.set_of_books_id = l.ledger_id
  ;
  IF v_ledger_currency = p_currency_code 
     AND 
     l_amount > l_ap_amount THEN
    v_amount := l_amount - (l_amount - l_ap_amount) - l_adj_amount;
  ELSE
    v_amount := l_amount - l_adj_amount;
  END IF;
  RETURN v_amount;
EXCEPTION
  WHEN OTHERS THEN
    RETURN 0;
END;

再次运行改写后的SQL看看速度(注意,要使用test_get_unverification_amount代替NAP_UNVERIF_PREPAYMENT_PKG1.GET_UNVERIFICATION_AMOUNT)

5858 rows inserted
Executed in 20.717 seconds                                        
            
SQL由28秒进一步优化到20秒,到这里其实就差不多了,剩下的就是对函数里面的SQL添加索引进一步优化(请忽略我索引命名不规范)
 

CREATE INDEX IDX_N1 ON AP_INVOICE_DISTRIBUTIONS_ALL(INVOICE_ID,INVOICE_LINE_NUMBER,LINE_TYPE_LOOKUP_CODE,MATCH_STATUS_FLAG,ACCOUNTING_DATE,AMOUNT);
CREATE INDEX IDX_N2 ON XLA_AE_LINES(AE_HEADER_ID,ACCOUNTING_CLASS_CODE,ACCOUNTING_DATE,ACCOUNTED_DR,ACCOUNTED_CR); 

SQL> CREATE INDEX IDX_N1 ON AP_INVOICE_DISTRIBUTIONS_ALL(INVOICE_ID,INVOICE_LINE_NUMBER,LINE_TYPE_LOOKUP_CODE,MATCH_STATUS_FLAG,ACCOUNTING_DATE,AMOUNT);
Index created
Executed in 20.586 seconds
SQL> CREATE INDEX IDX_N2 ON XLA_AE_LINES(AE_HEADER_ID,ACCOUNTING_CLASS_CODE,ACCOUNTING_DATE,ACCOUNTED_DR,ACCOUNTED_CR);
Index created
Executed in 76.581 seconds

再跑一下SQL看看速度

5858 rows inserted
Executed in 17.205 seconds

经过一系列折腾,SQL由最原始的42秒优化到17秒,不过我认为,最后这2个索引没必要创建了,搞了半天才节约3秒,所以最终SQL从42秒优化到20秒差不多了

TMD,后面要做的优化全是SQL套自定义函数的,真的是FUCK
其实本案例应该从表设计入手,提前将WHERE条件中的过滤条件算好,放入2个列中,这样基本上就可以做到秒杀,想知道具体怎么做,来报个班学习一下吧。
 

以上是关于利用ORDERED_PREDICATES优化多个自定函数作为WHERE过滤条件的主要内容,如果未能解决你的问题,请参考以下文章

lightdb22.4-新增优化器提示cardinality 和ordered_predicates

lightdb22.4-新增优化器提示cardinality 和ordered_predicates

Android中利用ViewHolder优化自定义Adapter的典型写法

利用 MySQL自定义变量高效查询

net core天马行空系列: 一个接口多个实现类,利用mixin技术通过自定义服务名,实现精准属性注入

之十三-利用自定义函数进行批量数据内容提取