利用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的典型写法