Solved

Optimize Slow-Running Quick Report

  • 15 February 2021
  • 25 replies
  • 274 views

Userlevel 3
Badge +10

We have quick report that takes excessively long to execute and return results. Any ideas on increasing performance?

There’s only 5 rows in the result set (see below).

select 
pm.Part_Main_Group as Trademark,
l1p.contract,
l1p.part_no,
ip.description,
l1f.ms_set as MS_Set,
trunc(l1f.ms_date) as Forecast_Date,
extract( MONTH from to_date(l1f.ms_date)) as Month,
to_char(l1f.ms_date,'yyyymm') as YearMonth,
l1f.forecast_lev0 as Forecast_Qty,

NVL((select sum(rega1app.Customer_Order_API.Get_Ord_Gross_Amount(ORDER_NO))
from rega1app.customer_info_cfv cic
left join rega1app.cust_ord_invo_stat col
on l1p.contract = col.contract and l1p.part_no = col.part_no and cic.customer_id = col.customer_no
and ( extract( YEAR from to_date(col.invoice_date)) = extract( YEAR from to_date(l1f.ms_date)) )
and ( extract( MONTH from to_date(col.invoice_date)) = extract( MONTH from to_date(l1f.ms_date)) )
where ( l1p.contract = 'W' and l1f.ms_set = cic.cf$_ms_set or l1p.contract <> 'W' )
),0) AS Order_Total,

NVL((select sum(invoiced_qty)
from rega1app.customer_info_cfv cic
left join rega1app.cust_ord_invo_stat col
on l1p.contract = col.contract and l1p.part_no = col.part_no and cic.customer_id = col.customer_no
and ( extract( YEAR from to_date(col.invoice_date)) = extract( YEAR from to_date(l1f.ms_date)) )
and ( extract( MONTH from to_date(col.invoice_date)) = extract( MONTH from to_date(l1f.ms_date)) )
where ( l1p.contract = 'W' and l1f.ms_set = cic.cf$_ms_set or l1p.contract <> 'W' )
),0) AS Order_Qty

FROM REGA1APP.LEVEL_1_PART l1p

left join REGA1APP.part_catalog pm
on l1p.part_no = pm.part_no

left join REGA1APP.Inventory_part ip
on l1p.contract = ip.contract and l1p.part_no = ip.part_no

left join REGA1APP.MS_SETS_ORDER_BY_1_DEF msod
on l1p.contract = msod.contract and l1p.part_no = msod.part_no

left join REGA1APP.LEVEL_1_FORECAST l1f
on msod.contract = l1f.contract and msod.part_no = l1f.part_no and msod.ms_set = l1f.ms_set

where l1f.ms_set not in ('3')

and l1f.contract = 'W'
and l1f.part_no = 'CX111-SG'
and l1f.ms_set = '10256'
and l1f.ms_date >= to_date('8/1/2020', 'MM/DD/YYYY')
and l1f.ms_date <= to_date('12/31/2020', 'MM/DD/YYYY')

order by 1, 2, 3, trunc(l1f.ms_date)

 

 

icon

Best answer by KIMKIMANDREW 25 February 2021, 12:05

View original

This topic has been closed for comments

25 replies

Userlevel 1
Badge +1

Try running this segment (from the NVL) by itself and see how long it takes. At a glance it appears that it will do multiple full table traverses.

 

 

select sum(invoiced_qty) from rega1app.customer_info_cfv cic left join rega1app.cust_ord_invo_stat col on l1p.contract = col.contract and l1p.part_no = col.part_no and cic.customer_id = col.customer_no and ( extract( YEAR from to_date(col.invoice_date)) = extract( YEAR from to_date(l1f.ms_date)) ) and ( extract( MONTH from to_date(col.invoice_date)) = extract( MONTH from to_date(l1f.ms_date)) ) where ( l1p.contract = 'W' and l1f.ms_set = cic.cf$_ms_set or l1p.contract <> 'W' )

Userlevel 3
Badge +10

thx. When I run that for just one part#, it runs fine; but w/o it it hangs and doesn’t complete with results.

Also, the date range is always 12 months from the starting point.

 

Do I need to further break it out using IAL’s, with one containing just Order_Total and Order_Qty?

Userlevel 1
Badge +1

two things:

  1. run the whole query without the NVL statements and see if you get a performance improvement.
  2. Then add one NVL statement back in and see what happens
  3. The where clause in the NVL statements may be causing you some issues. where ( l1p.contract = 'W' and l1f.ms_set = cic.cf$_ms_set or l1p.contract <> 'W' ) 

In the where clause you are saying where contract =’W’ or contract <> ‘W’?  Is that correct?

 

 

 

 

Userlevel 3
Badge +10

I did that and know definitely that it is due to the both NVL() statements. I’m not sure why the where clause would be a performance hit in IFS? For each Part# in the main result set it’s pulling/totaling all sales for just that parts, customer, contract, and selected month.

I’ve had other quick reports where using an API causes delays.

 

Userlevel 1
Badge +1

Hi DevBob,

 

yes - an API will cause repeated database access so can slow down the execution.

You where clause has “where contract =’W’ or contract <> ‘W’. That suggests any value for W is acceptable for the query (this will cause full table reads: everything where contract= “W”  + everything where contract is anything else) .

Which contract do you want to be selecting for? Only ‘W’?

I am going into a meeting for an hour and will have a look at it 

 

N

 

 

 

Userlevel 3
Badge +10

I’m reading forecasted parts and trying to retrieve historical sales.

The link between the two is Forecast.MS Set = Historical_Sales_cfv.customfield(MS Set). but this business rule applies only for Contract = ‘W’.  If it’s run for a non-W contract part then don’t link on the Ms Set custom field (just pull all sales for that contract/part/ and selected MONTH).

 

Example:

user selects-

Site: S20

Part: SA-807

Forecast MS Set: 1331

StartingMonth: 3/1/2021

 

...for each forecast () part found, pull monthly HistoricalSales (we’re using CUST_ORD_INVO_STAT) for S20, SA-807 starting in March, so the results could be:

March: 300

April: 200

May: 400

etc. ...

 

partial code:

...
FROM REGA1APP.LEVEL_1_PART l1p

left join REGA1APP.MS_SETS_ORDER_BY_1_DEF msod
on l1p.contract = msod.contract and l1p.part_no = msod.part_no

left join REGA1APP.LEVEL_1_FORECAST l1f
on msod.contract = l1f.contract and msod.part_no = l1f.part_no and msod.ms_set = l1f.ms_set
...

 

Userlevel 3
Badge +10

fyi, I tried replacing the two NVL() statements with an ILA, but still get the performance issue:

IAL:

select
col.contract as Contract,
col.part_no as Part_No,
col.customer_no as Customer_No,
to_char(col.invoice_date,'yyyymm') as Year_Month,

ROUND( SUM( NULLIF(invoiced_qty * sale_unit_price,0) ), 2) AS Order_Total,
ROUND( SUM( NULLIF(invoiced_qty ,0)), 2) AS Order_Qty

from rega1app.cust_ord_invo_stat col

left join rega1app.customer_info_cfv cic
on cic.customer_id = col.customer_no

where invoice_date >= to_date('1/1/2018', 'MM/DD/YYYY')
and invoiced_qty > 0
and invoiced_qty IS NOT NULL

group by Contract, Part_No, Customer_No, to_char(col.invoice_date,'yyyymm')

order by 1, 2, 3, 4, 5

 

Userlevel 7
Badge +15

I always recommend putting parentheses around clauses when you mix AND and OR, but that piece of this query is right. OR has a higher precedence than AND. If you ever forget, you can ask the database by running an experiment.

SELECT * FROM (
SELECT 'A' AS col1, 'Z' AS col2 FROM DUAL UNION ALL
SELECT 'A' AS col1, 'Y' AS col2 FROM DUAL UNION ALL
SELECT 'B' AS col1, 'X' AS col2 FROM DUAL UNION ALL
SELECT 'B' AS col1, 'W' AS col2 FROM DUAL
)
WHERE col1 = 'A' AND col2 = 'Z' OR col1 != 'A';

/*
Returns:
A Z
B X
B W
*/

 

Userlevel 3
Badge +10

I always recommend putting parentheses around clauses when you mix AND and OR, but that piece of this query is right. OR has a higher precedence than AND. If you ever forget, you can ask the database by running an experiment.

SELECT * FROM (
SELECT 'A' AS col1, 'Z' AS col2 FROM DUAL UNION ALL
SELECT 'A' AS col1, 'Y' AS col2 FROM DUAL UNION ALL
SELECT 'B' AS col1, 'X' AS col2 FROM DUAL UNION ALL
SELECT 'B' AS col1, 'W' AS col2 FROM DUAL
)
WHERE col1 = 'A' AND col2 = 'Z' OR col1 != 'A';

/*
Returns:
A Z
B X
B W
*/

 


good idea- thx!

Userlevel 7
Badge +15

If you’re going to extract the YEAR and MONTH from a field that’s of DATE type, there’s no need to wrap the DATE field in TO_DATE first.

If you need BOTH the year and month, you might be better off truncating to the month to include both the year and month in the clause at the same time.

and TRUNC(col.invoice_date, 'MM') = TRUNC(l1f.ms_date, 'MM')

 

After you change the query like that, try these function-based indexes (in a nonproduction environment first):

CREATE INDEX c_cust_ord_inv_stat_invdate_ix ON cust_ord_invo_stat_tab (TRUNC(invoice_date, 'MM')) TABLESPACE ifsapp_index;

CREATE INDEX c_level_1_fcast_ms_date_ix ON level_1_forecast_tab (TRUNC(ms_date, 'MM')) TABLESPACE ifsapp_index;

 

Userlevel 7
Badge +15
       NVL((select sum(rega1app.Customer_Order_API.Get_Ord_Gross_Amount(ORDER_NO))
from rega1app.customer_info_cfv cic
left join rega1app.cust_ord_invo_stat col
on l1p.contract = col.contract
and l1p.part_no = col.part_no
and cic.customer_id = col.customer_no
-- changed:
and TRUNC(col.invoice_date, 'MM') = TRUNC(l1f.ms_date, 'MM')
--
where l1p.contract = 'W'
and l1f.ms_set = cic.cf$_ms_set
or l1p.contract <> 'W'), 0) AS Order_Total

 

Userlevel 3
Badge +10

Thx. I removed the to_date().

We don’t have PL/SQL access to do CREATE INDEX statements. We do have Oracle SQL Developer.

Userlevel 7
Badge +15

You can create indexes with Oracle SQL Developer or with SQL*Plus if you have access to log in as your application owner (REGA1APP). You don’t need proprietary tools for this.

You can also log in as your application owner if you have access to log in as IFSSYS, since your app owner is going to be a Foundation user, too.

sqlplus ifssys[ifsapp]/ifssys_password@tns_name

 

Userlevel 3
Badge +10

ok, thx, I’ll try that.

Userlevel 3
Badge +7

As general, if result sets is as small as 5 rows, I’m rather using API to fetch some single values than joining additional view. I meant pm.Part_Main_Group, ip.description. Please also take into account that using left join and then in where clause to look for values in left-joined view it actually become INNER JOIN.

In case of using two nested select statement which take data from the same data source, I’d rather join single select statement.

Userlevel 3
Badge +8

The base query will run quicker if you make LEVEL_1_FORECAST the first table in your query and then join your way down the rest of the tables because your WHERE clause is referencing this table. I know that oracle can be smart enough to optimise queries but better to give it a fighting change :grin: 

Apply the same approach to the sub queries i.e. Filter the data from the primary table first if possible then join other tables to it.

So in your sub queries

SELECT blah from INVOSTAT INNER JOIN CUSTOMER_INFO where <INVOSTAT field filters>

INNER JOINS vs LEFT JOINS - INNER is ‘better’ if you know the records will always link and may be slightly faster depending on the structure of your query.

 

Userlevel 3
Badge +10

If you’re going to extract the YEAR and MONTH from a field that’s of DATE type, there’s no need to wrap the DATE field in TO_DATE first.

If you need BOTH the year and month, you might be better off truncating to the month to include both the year and month in the clause at the same time.

and TRUNC(col.invoice_date, 'MM') = TRUNC(l1f.ms_date, 'MM')

 

After you change the query like that, try these function-based indexes (in a nonproduction environment first):

CREATE INDEX c_cust_ord_inv_stat_invdate_ix ON cust_ord_invo_stat_tab (TRUNC(invoice_date, 'MM')) TABLESPACE ifsapp_index;

CREATE INDEX c_level_1_fcast_ms_date_ix ON level_1_forecast_tab (TRUNC(ms_date, 'MM')) TABLESPACE ifsapp_index;

 


Hmm, I must be missing something; I have two questions on this:

1.) How does TRUNC(date,’MM’) include both year and month? Wouldn’t it just return ‘01’, ‘02’, … ‘11’, ‘12’? Why wouldn’t it be TRUNC(date,’YYYYMM’)?

2.) I put TRUNC(Date,’MM’) in a SQL and it returned the entire date, including day and year when I thought it would just return the month; what am I not understanding?

 

 

 

...I get it now; it truncates down to just that portion- YYYY or MM. Nice!   :)

Userlevel 3
Badge +10

If you’re going to extract the YEAR and MONTH from a field that’s of DATE type, there’s no need to wrap the DATE field in TO_DATE first.

If you need BOTH the year and month, you might be better off truncating to the month to include both the year and month in the clause at the same time.

and TRUNC(col.invoice_date, 'MM') = TRUNC(l1f.ms_date, 'MM')

 

After you change the query like that, try these function-based indexes (in a nonproduction environment first):

CREATE INDEX c_cust_ord_inv_stat_invdate_ix ON cust_ord_invo_stat_tab (TRUNC(invoice_date, 'MM')) TABLESPACE ifsapp_index;

CREATE INDEX c_level_1_fcast_ms_date_ix ON level_1_forecast_tab (TRUNC(ms_date, 'MM')) TABLESPACE ifsapp_index;

 


I created the indexes; now will try a query using them...

Userlevel 3
Badge +10

When I try to query off of that new index I get this error:

I also tried the prefix ifsinfo..

 

 

Userlevel 3
Badge +10

When I try to query the new indexes I do see them:

 

Userlevel 3
Badge +10

Tried to grant access to the INDEX:

 

Userlevel 3
Badge +10

If I use the _TAB table name in my SQL that it finds it but now it doesn’t have sufficient priviledges:

 

 

Userlevel 7
Badge +15

The IFS views are stored queries against base tables.

When you create an index, you create the index against the table, not against views.

When you query a view, Oracle rewrites your query so it looks against the base tables.

 

So that means: You shouldn’t have to change your query to take advantage of an index. If your query against the views didn’t speed up, then it likely didn’t do anything for you.

 

There’s a chance you need to gather table statistics, but in newer versions of Oracle, it generally does that for you when you create an index.

Userlevel 3
Badge +10

Thx, Durette; that fills out my understanding.

Userlevel 3
Badge +10

The base query will run quicker if you make LEVEL_1_FORECAST the first table in your query and then join your way down the rest of the tables because your WHERE clause is referencing this table. I know that oracle can be smart enough to optimise queries but better to give it a fighting change :grin: 

Apply the same approach to the sub queries i.e. Filter the data from the primary table first if possible then join other tables to it.

So in your sub queries

SELECT blah from INVOSTAT INNER JOIN CUSTOMER_INFO where <INVOSTAT field filters>

INNER JOINS vs LEFT JOINS - INNER is ‘better’ if you know the records will always link and may be slightly faster depending on the structure of your query.

 


Re-writing the query as suggested above took care of the performance issue, along with Durette’s reply:

and TRUNC(col.invoice_date, 'MM') = TRUNC(l1f.ms_date, 'MM')

Thx, everyone for your input! Many good ideas! :grinning: