Skip to Main Content

SQL & PL/SQL

Announcement

For appeals, questions and feedback about Oracle Forums, please email oracle-forums-moderators_us@oracle.com. Technical questions should be asked in the appropriate category. Thank you!

Interested in getting your voice heard by members of the Developer Marketing team at Oracle? Check out this post for AppDev or this post for AI focus group information.

SQL Tuning

David BergerJan 21 2014 — edited Jan 22 2014

Hello

I have a query which lasts 2:50 Minutes...

SELECT COUNT(*)

  FROM (SELECT a.c_mandant,

              f.kre_key,

              a.n_finanzprodnummer,

              a.b_rate AS amo_betrag,

              fa.n_freq_in_monaten,

              fa.d_durchf_erste AS amo_erste_faelligkeit,

              fa.d_gueltig_bis AS amo_letzte_ausfuehrung

            , P1.fpr_finanzprodnummer

              FROM

                   T1 F

              JOIN T2 P1  -- 285 T

                ON

                   p1.c_mandant = f.c_mandant

               AND p1.fko_key   = f.fko_key

              JOIN

                   T3 z

                ON

                   z.c_mandant = f.c_mandant

               AND z.n_finanzierungsnummer = f.fin_finanzierungsnummer

              JOIN T4 p  --200 T

                ON p.c_mandant             = z.c_mandant

               AND p.n_finanzierungsnummer = z.n_finanzierungsnummer

              JOIN T5 p3

                ON p.c_mandant          = p3.c_mandant

               AND p.n_finanzprodnummer = p3.n_finanzprodnummer

              JOIN kbu_kf2_kr_nr_amortisation_kon a

                ON a.c_mandant          = p.c_mandant

               AND a.n_finanzprodnummer = p.n_finanzprodnummer

              JOIN T6 fa

                ON a.c_mandant                  = fa.c_mandant

               AND a.id_kf_kr_nr_amort_faelligk = fa.id_kf_kr_nr_amortisation_faell

             WHERE p1.intf_stat = '1'

            --   AND P.N_FINANZPRODNUMMER    = P1.FPR_FINANZPRODNUMMER

              AND z.d_aufloesung IS NULL

              AND (p3.d_bis BETWEEN SYSDATE AND  TO_DATE('31.12.2028', 'DD.MM.YYYY')/*:l_dat*/ OR p3.d_bis IS NULL)

              AND p3.d_von < TO_DATE('31.12.2028', 'DD.MM.YYYY')--:l_dat

              AND fa.d_durchf_erste <= TO_DATE('31.12.2028', 'DD.MM.YYYY')--:l_dat

            )

WHERE n_finanzprodnummer = fpr_finanzprodnummer

;


Connected to:

Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production

With the Partitioning, OLAP, Data Mining and Real Application Testing options

  COUNT(*)

----------

     30129


Elapsed: 00:02:50.26

SQL>

I could tuned it with using the subquery factoring.

See below:

WITH TEMP_TAB

AS (SELECT /*+ MATERIALIZE */

          a.c_mandant,

         f.kre_key,

         a.n_finanzprodnummer,

         a.b_rate AS amo_betrag,

         fa.n_freq_in_monaten,

         fa.d_durchf_erste AS amo_erste_faelligkeit,

         fa.d_gueltig_bis AS amo_letzte_ausfuehrung

       , P1.fpr_finanzprodnummer

         FROM

              T1 F

         JOIN T2 P1  -- 285 T

           ON

              p1.c_mandant = f.c_mandant

          AND p1.fko_key   = f.fko_key

         JOIN

              T3 z

           ON

              z.c_mandant = f.c_mandant

          AND z.n_finanzierungsnummer = f.fin_finanzierungsnummer

         JOIN T4 p  --200 T

           ON p.c_mandant             = z.c_mandant

          AND p.n_finanzierungsnummer = z.n_finanzierungsnummer

         JOIN T5 p3

           ON p.c_mandant          = p3.c_mandant

          AND p.n_finanzprodnummer = p3.n_finanzprodnummer

         JOIN T6 a

           ON a.c_mandant          = p.c_mandant

          AND a.n_finanzprodnummer = p.n_finanzprodnummer

         JOIN T7 fa

           ON a.c_mandant                  = fa.c_mandant

          AND a.id_kf_kr_nr_amort_faelligk = fa.id_kf_kr_nr_amortisation_faell

        WHERE p1.intf_stat = '1'

       --   AND P.N_FINANZPRODNUMMER    = P1.FPR_FINANZPRODNUMMER

         AND z.d_aufloesung IS NULL

         AND (p3.d_bis BETWEEN SYSDATE AND  TO_DATE('31.12.2028', 'DD.MM.YYYY')/*:l_dat*/ OR p3.d_bis IS NULL)

         AND p3.d_von < TO_DATE('31.12.2028', 'DD.MM.YYYY')--:l_dat

         AND fa.d_durchf_erste <= TO_DATE('31.12.2028', 'DD.MM.YYYY')--:l_dat

       )

SELECT COUNT(*)

  FROM TEMP_TAB

WHERE n_finanzprodnummer = fpr_finanzprodnummer

;

Connected to:

Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production

With the Partitioning, OLAP, Data Mining and Real Application Testing options

  COUNT(*)

----------

     30129

Elapsed: 00:00:01.23

SQL>

It is good... BUT... IF I do not want to materialize the query-result how could I give the command to the optimizer that he has to execute the query-block firstly and after that he has to use the filter on the executed result without using the "with" hint?

I tried already the following hints: PUSH_SUBQ, NO_MERGE etc.. but it did not help me..

We can set the execution order of joins with the hints (ORDERED, LEADING..)

-> but how can we set the filter order if we want it or in other words how can we prioritize the execution of a query block as opposed to a filter?

Example:

SELECT COUNT(*)

  FROM (---- Firstly this query has to be executed ----

        SELECT n_finanzprodnummer

             , fpr_finanzprodnummer

           FROM ....

        )

WHERE ---- This Filter can not be merged into the query seen above! ----

      n_finanzprodnummer = fpr_finanzprodnummer

;

Comments

Jonathan Lewis

I'm puzzled that the no_merge hint is not working for you - perhaps it's (yet another) side effect of how Oracle treats ANSI syntax.

Step 1: put the no_merge where you've got the MATERIALIZE in your WITH TEMP_TAB example - this should put the factored subquery inline and then optimize it exactly the way you want.  (There have been cases in the past, though, where "manually inlining" a factored subquery produces a different plan from writing the subquery as a factored subquery and letting the optimizer decide to put it inline).

Can you try it both ways - simply hinting no_merge (and INLINE, if it doesn't move, which it should), and then copy the bracketed query in into place with the alias temp_tab, and tell us if you get two different plans.

Regards

Jonathan Lewis

Jonathan Lewis

On second thoughts - the no_merge() hint is about complex view merging, and this is an example of "simple view merging".

But it can't be stopped either by setting _simple_view_merging to false, or by hinting NO_QUERY_TRANSFORMATION

Regards

Jonathan Lewis

David Berger

Hello Jonathan

I tried the followings:

1.)

WITH TEMP_TAB

AS (SELECT /*+ NO_MERGE */

           n_finanzprodnummer

         , fpr_finanzprodnummer

         , ...

      FROM .....

   )

SELECT COUNT(*)

  FROM TEMP_TAB

WHERE n_finanzprodnummer = fpr_finanzprodnummer

;

Result is the same bad plan -> Here happens a Filter-Push down again.

2.)

SELECT COUNT(*)

  FROM (SELECT /*+ NO_MERGE */

               n_finanzprodnummer

             , fpr_finanzprodnummer

              , ...

          FROM .....

       )

WHERE n_finanzprodnummer = fpr_finanzprodnummer

;

Result is the same bad plan...

Jonathan Lewis

Reading through a 10053 trace, I found that the "problem" feature was "simple filter predicate pushdown".

So I set parameter "_optimizer_filter_pushdown" to false and (at least in my simple join example) bypassed your problem. It still needed the no_merge() hint to "surround" the inline view where you didn't want the predicate to go. Rather than doing "alter session" to set the parameter, you could use the hint:  /*+ opt_param('_optimizer_filter_pushdown','false') */ but this is not one of the parameters listed as working with the hint, and the hint won't get reflected into the outline.


Regards

Jonathan Lewis


Randolf Geist

Jonathan,

I think the "_optimizer_filter_pushdown" parameter is pretty new and was only added in 11.2.0.2 or 11.2.0.3, I can't remember off the top of my head. The potential problem is that it prevents any filter pushdowns I think, so if there are multiple query blocks where some should push filters and some don't I'm not sure this would be feasible with that parameter.

To the OP: I think one commonly used technique to prevent a filter pushdown is adding the ROWNUM pseudo-column (or a similar analytic function ROW_NUMBER() OVER (ORDER BY NULL)) to the query block where the filter shouldn't be pushed into.

Of course, adding ROWNUM adds its own overhead to the query (there will be an additional COUNT operator) and can have other side effects, some of them rather nasty when dealing with Parallel Execution.

Apart from that it usually makes sense to filter the data as early as possible, so possibly the filter pushdown itself is not the problem but as a side effect of the pushdown you get a less efficient execution plan, so it might make sense to understand what possibly goes wrong with the plan when the filter is pushed.

Randolf

David Berger

Hello Jonathan

Super! Your solution works!

SELECT /*+ opt_param('_optimizer_filter_pushdown','false') */

       COUNT(*)

  FROM (SELECT /*+ NO_MERGE */

               n_finanzprodnummer

             , fpr_finanzprodnummer

              , ...

          FROM .....

       )

WHERE n_finanzprodnummer = fpr_finanzprodnummer

;

Thank you and regards,

David

David Berger

Hello Randolf

Yes. You are right, in this case the optimizer hint operates on the whole query.

I tried your solution and it works too!

SELECT COUNT(*)

  FROM (SELECT /*+ NO_MERGE */

               n_finanzprodnummer

             , fpr_finanzprodnummer

              , ...

          FROM .....

          WHERE ...

           AND ROWNUM >= 0

       )

WHERE n_finanzprodnummer = fpr_finanzprodnummer

;

Thank you very much!

-> In this case the solution is the preventing of using the filter push-down. Reason: There are Views in the joins which have DB-links and the cost is not estimated good because of the remote objects.

This query is very complicated.

But anyway, I have learnt something again! Thank You all!

Regards, David

Randolf Geist

DavidBerger wrote:

Hello Randolf

Yes. You are right, in this case the optimizer hint operates on the whole query.

I tried your solution and it works too!

Hi David,

actually you don't need to reference ROWNUM in the WHERE clause, it should be sufficient to add ROWNUM to the list of projected columns. This should minimize the overhead - of course you might need to add then another layer of projection if you don't want to have that ROWNUM information shown in the result set.

Testing a query's performance by using COUNT(*) can be quite misleading, by the way, since the optimizer tries to be clever and avoid any work that's unnecessary. In your particular case it might not make a difference, but in general I would use a different technique, either SET AUTOTRACE TRACEONLY STATISTICS in SQL*Plus or wrapping the original query like this:

SELECT /*+ NO_MERGE(x) */ * FROM (

original query goes here

) x

WHERE ROWNUM > 1

;

The SQL*Plus approach needs to fetch all data to the client but doesn't spend any time in displaying/formatting it, the latter approach discards all data on the database server already, but needs to run the full projection logic due to the NO_MERGE hint. For very large data sets with many rows the FILTER operator that discards all rows on the server side can consume significant CPU time, but for smaller result sets this gives a pretty good and realistic indication of the actual runtime.

Randolf

Randolf Geist

DavidBerger wrote:

Hello Randolf

Yes. You are right, in this case the optimizer hint operates on the whole query.

I tried your solution and it works too!

One other thing: When using ROWNUM in current releases you don't need to add an explicit NO_MERGE hint as the query block is marked as non-mergeable due to the ROWNUM anyway, but it probably makes sense to use an explicit NO_MERGE hint nevertheless to make the intention clear and add another comment to the SQL stating why this all was done.

Randolf

David Berger

Hello Randolf

Ok. Thank you very much for your suggestions! They are really good!

Jonathan Lewis

DavidBerger wrote:

Hello Jonathan

Super! Your solution works!

SELECT /*+ opt_param('_optimizer_filter_pushdown','false') */

       COUNT(*)

  FROM (SELECT /*+ NO_MERGE */

               n_finanzprodnummer

             , fpr_finanzprodnummer

              , ...

          FROM .....

       )

WHERE n_finanzprodnummer = fpr_finanzprodnummer

;

Thank you and regards,

David

David,

As Randolf suggests, though, I shouldn't really have mentioned it - but I got a bit curious and decided to see if I could find a method when I realised that the no_merge wasn't supposed to work.

As Randolf also indicates - there's bound to be a way of getting the data faster while having the predicate operating earlier - but there's probably an optimizer "glitch" that's causing a change of plan because an early appearance of that predicate reduces the cardinality of some join to a point where a change of join order or method looks good.  ANSI SQL is much harder to unpick when this happens, though, so the rownum trick is a reasonable compromise between complexity and efficiency.


Regards

Jonathan Lewis

David Berger

Thank You Jonathan very much!

Jonathan Lewis

Randolf Geist wrote:

Jonathan,

I think the "_optimizer_filter_pushdown" parameter is pretty new and was only added in 11.2.0.2 or 11.2.0.3, I can't remember off the top of my head. The potential problem is that it prevents any filter pushdowns I think, so if there are multiple query blocks where some should push filters and some don't I'm not sure this would be feasible with that parameter.

To the OP: I think one commonly used technique to prevent a filter pushdown is adding the ROWNUM pseudo-column (or a similar analytic function ROW_NUMBER() OVER (ORDER BY NULL)) to the query block where the filter shouldn't be pushed into.

Of course, adding ROWNUM adds its own overhead to the query (there will be an additional COUNT operator) and can have other side effects, some of them rather nasty when dealing with Parallel Execution.

Apart from that it usually makes sense to filter the data as early as possible, so possibly the filter pushdown itself is not the problem but as a side effect of the pushdown you get a less efficient execution plan, so it might make sense to understand what possibly goes wrong with the plan when the filter is pushed.

Randolf

Randolf,

I just did a quick check on lists of parameters, and you're right; this one appeared very recently - 11.2.0.3

And you're also right that the OP shouldn't really make use of it.

Regards

Jonathan Lewis

1 - 13
Locked Post
New comments cannot be posted to this locked post.

Post Details

Locked on Feb 19 2014
Added on Jan 21 2014
13 comments
3,161 views