3

We just recently moved our DB from 9i to 10G (Yes..better late than never and No - moving to 11g is currently not an option :-))

Details of my Oracle 10G DB are :-

Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Prod
PL/SQL Release 10.2.0.1.0 - Production
CORE    10.2.0.1.0      Production

I am faced with a very weird problem since that move. A query that was and still is working fine with 9i just wont work on 10G.

I did search through other SO questions related to rownum but couldnt really find anything similar.

SQL Query is :-

SELECT * FROM 
( SELECT field1, field2 , field3, field4, field5, field6, field7, to_char(rownum) field8
  FROM
    ( SELECT 
        field1,
        field2,
        field3,
        field4,
        field5,
        field6,
        field7,
        ''
      FROM
      .......REST OF MY COMPLEX INNER QUERY
  )
) 
WHERE field8 BETWEEN 21 AND 30;

Basically, the 21 / 30 are numbers that are the index of the records passed to the query for pagination and in 9i, this query works like expected and returns the specified set of data only.

However in 10G, this same query does not work at all - always returns 0 records.

If i comment the rownum related parts of the query:-

to_char(rownum) field8  and
WHERE field8 BETWEEN 21 AND 30;

then i get the entire result set and thats great. But since my intention is to do pagination using the rownum, the entire purpose is defeated.

Does anyone know of any reason why this query has stopped working with 10G. I tried looking up any updates to the rownum implementation but havent been able to really come across anything that will help.

EDIT :- While doing my debugging, i have come across something that to me, is making no sense. I am putting in the entire query below as i cant explain without it.

SELECT * FROM 
( SELECT field1, field2 , field3, field4, field5, field6, field7, to_char(rownum) field8 from 
 ( SELECT PM.POLICY_NO field1
   ,PM.INSURED_CODE field2
   ,PM.INSURED_NAME field3
   ,TO_CHAR(PM.POLICY_EFFECTIVE_DATE,'DD/MM/YYYY') field4
   ,TO_CHAR(PM.POLICY_EXPIRATION_DATE,'DD/MM/YYYY') field5
   ,'' field6
   ,'' field7
   ,'' field8
   FROM POLICY_MAIN PM
   ,POLICY_ENDORSEMENT_MAIN PEM
   ,MASTER_UW_LOB_CLASS MAS
   WHERE PM.POLICY_NO = PEM.POLICY_NO
   AND PM.POLICY_NO LIKE UPPER('%%')
   AND PM.INSURED_CODE LIKE UPPER('%%')
   AND PM.SOURCE_OF_BUSINESS LIKE UPPER('%%')
   AND PM.POLICY_TYPE IS NULL
   AND PM.POLICY_STATUS = 'POST'
   AND PM.POLICY_LOB = MAS.UW_LOB_CODE
   AND MAS.UW_CLASS_CODE LIKE UPPER('AUTO')
   AND PEM.POLICY_ENDORSEMENT_NO =
    (SELECT MAX(PEM2.POLICY_ENDORSEMENT_NO)
     FROM   POLICY_ENDORSEMENT_MAIN       PEM2
     WHERE  PEM.POLICY_NO                 = PEM2.POLICY_NO
     ***AND    PEM.ENDORSEMENT_STATUS        = 'POST'***
     )
   ***order by 1 ASC***
  )
) 
WHERE field8 BETWEEN 21 AND 40

Refer the lines marked between *** in the innermost subquery.

  1. If i comment this line from my query, the query works fine.

    AND PEM.ENDORSEMENT_STATUS = 'POST'

  2. If i comment this line from my query and everything else remains unchanged from the original, the query works fine too

    order by 1 ASC

The earlier points related to rownum still hold true but commenting these lines individually seems to be making the rownum thing irrelevant and the entire query works fine (except for that fact that the results are logically different now)

I am confused. To say the least!!!

EDIT 2:

Adding the execution plan for the above query

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=ALL_ROWS (Cost=19 Card=1 Bytes=114)

   1    0   VIEW (Cost=19 Card=1 Bytes=114)
   2    1     COUNT
   3    2       FILTER
   4    3         VIEW (Cost=17 Card=1 Bytes=128)
   5    4           SORT (ORDER BY) (Cost=17 Card=1 Bytes=130)
   6    5             TABLE ACCESS (BY INDEX ROWID) OF 'POLICY_ENDORSEMENT_MAIN' (TABLE) (Cost=2 Card=1 Bytes=39)
   7    6               NESTED LOOPS (Cost=16 Card=1 Bytes=130)
   8    7                 NESTED LOOPS (Cost=14 Card=1 Bytes=91)
   9    8                   TABLE ACCESS (FULL) OF 'POLICY_MAIN' (TABLE) (Cost=14 Card=1 Bytes=82)
  10    8                   INDEX (UNIQUE SCAN) OF 'PK_MASTER_UW_LOB_CLASS' (INDEX (UNIQUE)) (Cost=0 Card=1 Bytes=9)
  11    7                 INDEX (RANGE SCAN) OF 'PK_POLICY_ENDORSEMENT_MAIN' (INDEX (UNIQUE)) (Cost=1 Card=1)
  12    3         SORT (AGGREGATE)
  13   12           FILTER
  14   13             INDEX (RANGE SCAN) OF 'PK_POLICY_ENDORSEMENT_MAIN' (INDEX (UNIQUE)) (Cost=2 Card=2 Bytes=68)

EDIT 3:

Exact same query as above but if i remove the

ORDER BY 1 ASC

clause, then the results are retrieved as expected. The PLAN for this query without the order by is below

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=ALL_ROWS (Cost=18 Card=1 Bytes=114)
   1    0   VIEW (Cost=18 Card=1 Bytes=114)
   2    1     COUNT
   3    2       FILTER
   4    3         TABLE ACCESS (BY INDEX ROWID) OF 'POLICY_ENDORSEMENT_MAIN' (TABLE) (Cost=2 Card=1 Bytes=39)
   5    4           NESTED LOOPS (Cost=16 Card=1 Bytes=130)
   6    5             NESTED LOOPS (Cost=14 Card=1 Bytes=91)
   7    6               TABLE ACCESS (FULL) OF 'POLICY_MAIN' (TABLE) (Cost=14 Card=1 Bytes=82)
   8    6               INDEX (UNIQUE SCAN) OF 'PK_MASTER_UW_LOB_CLASS' (INDEX (UNIQUE)) (Cost=0 Card=1 Bytes=9)
   9    5             INDEX (RANGE SCAN) OF 'PK_POLICY_ENDORSEMENT_MAIN' (INDEX (UNIQUE)) (Cost=1 Card=1)
  10    3         SORT (AGGREGATE)
  11   10           FILTER
  12   11             INDEX (RANGE SCAN) OF 'PK_POLICY_ENDORSEMENT_MAIN' (INDEX (UNIQUE)) (Cost=2 Card=2 Bytes=68)

Note that the only real difference between the two plans is that the one that is not working has the following two additional steps after step 3 where as these steps are not present in the query without the order by - which is working fine.

As expected, step 5 is the step where the ordering of the data is being done.

   4    3         VIEW (Cost=17 Card=1 Bytes=128)
   5    4           SORT (ORDER BY) (Cost=17 Card=1 Bytes=130)

It seems that step 4 is maybe an additional view being created due to the ordering.

WHY this should prevent the rownum logic from working is what i am still trying to grasp.

Any help appreciated!!

EDIT 4 - Original Query plan from 9i environment

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   VIEW
   2    1     COUNT
   3    2       VIEW
   4    3         SORT (ORDER BY)
   5    4           FILTER
   6    5             TABLE ACCESS (BY INDEX ROWID) OF 'POLICY_MAIN'
   7    6               NESTED LOOPS
   8    7                 NESTED LOOPS
   9    8                   TABLE ACCESS (FULL) OF 'POLICY_ENDORSEMENT_MAIN'
  10    8                   INDEX (RANGE SCAN) OF 'PK_MASTER_UW_LOB_CLASS' (UNIQUE)
  11    7                 INDEX (RANGE SCAN) OF 'PK_POLICY_MAIN' (UNIQUE)
  12    5             SORT (AGGREGATE)
  13   12               FILTER
  14   13                 INDEX (RANGE SCAN) OF 'PK_POLICY_ENDORSEMENT_MAIN' (UNIQUE)
Jagmag
  • 10,283
  • 1
  • 34
  • 58
  • When you edit the query, Oracle re-parses the query and will usually come up with new plans that may or may not exhibit the problem you're experiencing. Can you post the query plans? – Jeffrey Kemp Aug 05 '10 at 01:25
  • @Jeffrey - Edited the question and added the query plan. Must explain that i am very much a newbie when it comes to Oracle and it is a bit of greek and latin to me. Though looking up ASK TOM now to try and make sense of it :-) – Jagmag Aug 05 '10 at 02:43
  • 1
    Looking at that plan, Oracle appears to be unnesting the filtering subquery (against PEM2) -- note the `SORT (ORDER BY) ` is at a deeper level than the filter. That could explain why the query isn't working; ROWNUM is getting assigned before rows are being filtered out. Try using the NO_UNNEST hint using Jeffery Kemp's example below where he demonstrates NO_MERGE. – Adam Musch Aug 05 '10 at 13:43
  • Also, consider using the PUSH_SUBQ hint inside the subquery against PEM2. – Adam Musch Aug 05 '10 at 13:48
  • @Adam - tried both versions - but that did not make any difference to the results either. Just to confirm, this execution plans is to be read from bottom to top - is that right? BTW, my query with the rownum itself i.e SELECT field1, field2 , field3, field4, field5, field6, field7, to_char(rownum) field8.... is just a select * from table - where table is the inner query. Is it possible that the optimizer does not always evaluate the inner query first? – Jagmag Aug 06 '10 at 08:59
  • @Everyone - i have marked Jeffreys CTE suggestion as the correct answer as it provides me with a least resistance solution to my problem as of now. I am keeping the bounty open in case someone can help me figure out WHY 10G behaviour is differing from 9i as per my comments to both Jeffrey / Bill below. If I am unable to get any more information / easier solution involving lesser changes to my queries in next 2 days, then to Jeffrey go the spoils (not that 50 makes any difference to him :-P he's got thousands)!! – Jagmag Aug 11 '10 at 10:01
  • @Everyone - Thanks for all your inputs!! I am going with Jeffrey's CTE suggestion for now. Never managed to figure out the WHY but with only limited time to investigate, that will do for now. – Jagmag Aug 12 '10 at 08:37

4 Answers4

2

As Adam has suggested, the subquery is filtering the results after the sort and ROWNUM are applied.

I think you need to force that subquery to be filtered earlier, by using the PUSH_SUBQ hint:

SELECT * FROM 
( SELECT field1, field2 , field3, field4, field5, field6, field7,
         ROWNUM field8 from 
 ( SELECT PM.POLICY_NO field1
   ,PM.INSURED_CODE field2
   ,PM.INSURED_NAME field3
   ,TO_CHAR(PM.POLICY_EFFECTIVE_DATE,'DD/MM/YYYY') field4
   ,TO_CHAR(PM.POLICY_EXPIRATION_DATE,'DD/MM/YYYY') field5
   ,'' field6
   ,'' field7
   ,'' field8
   FROM POLICY_MAIN PM
   ,POLICY_ENDORSEMENT_MAIN PEM
   ,MASTER_UW_LOB_CLASS MAS
   WHERE PM.POLICY_NO = PEM.POLICY_NO
   AND PM.POLICY_NO LIKE UPPER('%%')
   AND PM.INSURED_CODE LIKE UPPER('%%')
   AND PM.SOURCE_OF_BUSINESS LIKE UPPER('%%')
   AND PM.POLICY_TYPE IS NULL
   AND PM.POLICY_STATUS = 'POST'
   AND PM.POLICY_LOB = MAS.UW_LOB_CODE
   AND MAS.UW_CLASS_CODE LIKE UPPER('AUTO')
   AND PEM.POLICY_ENDORSEMENT_NO =
    (SELECT /*+ PUSH_SUBQ*/
            MAX(PEM2.POLICY_ENDORSEMENT_NO)
     FROM   POLICY_ENDORSEMENT_MAIN       PEM2
     WHERE  PEM.POLICY_NO                 = PEM2.POLICY_NO
     AND    PEM.ENDORSEMENT_STATUS        = 'POST'
     )
   order by 1 ASC
  )
) 
WHERE field8 BETWEEN 21 AND 40

I've also removed the TO_CHAR from the ROWNUM - you want to use numbers for that range comparison.

EDIT

Try #2 - use CTE instead:

WITH q AS
( SELECT /*+MATERIALIZE*/
         field1, field2 , field3, field4, field5, field6, field7,
         ROWNUM field8 from 
 ( SELECT PM.POLICY_NO field1
   ,PM.INSURED_CODE field2
   ,PM.INSURED_NAME field3
   ,TO_CHAR(PM.POLICY_EFFECTIVE_DATE,'DD/MM/YYYY') field4
   ,TO_CHAR(PM.POLICY_EXPIRATION_DATE,'DD/MM/YYYY') field5
   ,'' field6
   ,'' field7
   ,'' field8
   FROM POLICY_MAIN PM
   ,POLICY_ENDORSEMENT_MAIN PEM
   ,MASTER_UW_LOB_CLASS MAS
   WHERE PM.POLICY_NO = PEM.POLICY_NO
   AND PM.POLICY_NO LIKE UPPER('%%')
   AND PM.INSURED_CODE LIKE UPPER('%%')
   AND PM.SOURCE_OF_BUSINESS LIKE UPPER('%%')
   AND PM.POLICY_TYPE IS NULL
   AND PM.POLICY_STATUS = 'POST'
   AND PM.POLICY_LOB = MAS.UW_LOB_CODE
   AND MAS.UW_CLASS_CODE LIKE UPPER('AUTO')
   AND PEM.POLICY_ENDORSEMENT_NO =
    (SELECT MAX(PEM2.POLICY_ENDORSEMENT_NO)
     FROM   POLICY_ENDORSEMENT_MAIN       PEM2
     WHERE  PEM.POLICY_NO                 = PEM2.POLICY_NO
     AND    PEM.ENDORSEMENT_STATUS        = 'POST'
     )
   order by 1 ASC
  )
) 
SELECT * from q
WHERE field8 BETWEEN 21 AND 40
Jeffrey Kemp
  • 59,135
  • 14
  • 106
  • 158
  • @Jeffrey - i had tried out the push_subq as suggested earlier but it didnt work either. Just to make sure, i tried out your query as well - but it doesnt work either. I cant paste it below but the query plan for your query is almost exactly same as the query plan i have posted in EDIT 2 except for the row "3 2 FILTER" which is the only step missing. The additional view being created in step 4 - "4 3 VIEW (Cost=17 Card=1 Bytes=128)" is the one that is messing up the rownum. Are there some DB settings that may cause the hint PUSH_SUBQ to be ignored? – Jagmag Aug 10 '10 at 04:15
  • 1
    BRILLIANT - the CTE version works exactly as expected!! In fact, i tried by even removing the MATERIALIZE hint and it still works exactly as expected i.e the change in the query syntax to use the CTE is making the subquery to evaluate first hence providing rownum with values - hence making the where clause work as expected!! When i compared the 2 query plans - my original query plan i.e. EDIT2 v/s the CTE's plan, the only difference is "FILTER" step from Step 3 in my plan is now occuring at Step 5 which makes all the difference. Can u throw some light on why? – Jagmag Aug 11 '10 at 03:00
  • Note, you may find that without the MATERIALIZE hint, Oracle may choose later on (e.g. in a later version) to not materialize the query. – Jeffrey Kemp Aug 11 '10 at 04:39
  • Is using the query hints a good idea in Oracle? I come from a SQL server background and have usually considered it not such a great idea to use the DB hints much...due to hints being interpreted differently across versions or hints resulting in non optimal query plans - In SQL Server, i used to go with the understanding that the optimizer knows best. In the oracle world, is using hints common practice? – Jagmag Aug 11 '10 at 11:51
  • No, generally it's a good **rule of thumb** to avoid hints for the reasons you give. However, this is a case where a hint is being used exactly for the purpose it was made - we *don't* want the optimizer to choose another plan in the future - so we add the hint to make sure. – Jeffrey Kemp Aug 12 '10 at 02:30
1

It sounds like Oracle is mergeing the inline view into the main query so that field8 (based on ROWNUM) is calculated too late. I haven't seen that happen myself, but if that is what is happening you could try adding a NO_MERGE hint like this:

SELECT /*+ NO_MERGE(vw) */ * FROM 
( SELECT field1, field2 , field3, field4, field5, field6, field7, to_char(rownum) field8
  FROM
    ( SELECT 
        field1,
        field2,
        field3,
        field4,
        field5,
        field6,
        field7,
        ''
      FROM
      .......REST OF MY COMPLEX INNER QUERY
  )
) vw
WHERE field8 BETWEEN 21 AND 30;

(Incidentally, why the TO_CHAR on ROWNMUM when you are treating it as a number in the WHERE clause anyway?)

Tony Andrews
  • 129,880
  • 21
  • 220
  • 259
  • Will try the nomerge hint and update further. About the to_char, this query is actually part of a bigger query with a union all clause where this last column is expected to be a string - hence the to_char - i cut out the irrevelant part of the query just to make it simpler to understand. – Jagmag Aug 03 '10 at 10:05
  • Your answer definitely made sense and i tried out the nomerge hint but no dice :-( :-( – Jagmag Aug 03 '10 at 10:35
  • @InSane, Try the `/*+MATERIALIZE*/` hint instead - and put the hint in the inner select, not the outer one. – Jeffrey Kemp Aug 04 '10 at 04:58
  • @Jeffrey - Thanks for the inputs! Tried out your suggestion as well. No luck. I have found out something more (weird!) in the course of my debugging. Adding that info as "Edit" to the original post. – Jagmag Aug 04 '10 at 06:10
  • The sort (ORDER BY) in the inner query may require the optimiser to not merge the inner query, and may be the reason why it works in one version; however, Oracle can avoid a sort operation in some scenarios (e.g. if a suitable index is used in the plan to access the rows in the right order), in which case Oracle may find a plan where merging is allowed. – Jeffrey Kemp Aug 05 '10 at 01:24
1

Try this:

SELECT field1, field2 , field3, field4, field5, field6, field7, to_char(rn) field8 from  
 (SELECT PM.POLICY_NO field1 
         ,PM.INSURED_CODE field2 
         ,PM.INSURED_NAME field3 
         ,TO_CHAR(PM.POLICY_EFFECTIVE_DATE,'DD/MM/YYYY') field4 
         ,TO_CHAR(PM.POLICY_EXPIRATION_DATE,'DD/MM/YYYY') field5 
         ,'' field6 
         ,'' field7 
         ,rownum as rn
   FROM POLICY_MAIN PM 
        inner join POLICY_ENDORSEMENT_MAIN PEM 
           on PM.POLICY_NO = PEM.POLICY_NO 
        inner join MASTER_UW_LOB_CLASS MAS 
           on PM.POLICY_LOB = MAS.UW_LOB_CODE 
  WHERE PM.POLICY_NO LIKE UPPER('%%') 
    AND PM.INSURED_CODE LIKE UPPER('%%') 
    AND PM.SOURCE_OF_BUSINESS LIKE UPPER('%%') 
    AND PM.POLICY_TYPE IS NULL 
    AND PM.POLICY_STATUS = 'POST' 
    AND MAS.UW_CLASS_CODE = 'AUTO'
    AND PEM.ENDORSEMENT_STATUS = 'POST'
    AND PEM.POLICY_ENDORSEMENT_NO = 
         (SELECT MAX(PEM2.POLICY_ENDORSEMENT_NO) 
            FROM POLICY_ENDORSEMENT_MAIN PEM2 
           WHERE PEM.POLICY_NO           = PEM2.POLICY_NO 
         ) 
  order by pm.policy_no ASC) 
WHERE rn BETWEEN 21 AND 40 

Changes:

  1. Restructured joins to use ANSI syntax to differentiate joins from filters.
  2. Changed LIKE UPPER('AUTO') to = 'AUTO'
  3. Removed unnecessary level of nesting.
  4. Changed order by to use expression vs. positional notaion
  5. Moved filtering criteria PEM.ENDORSEMENT_STATUS = 'POST' from correlated subquery to main query, which may correct wrong results issue.
  6. Changed pagination condition to use a numeric expression rather than a character one, because:

    select * from dual where '211' between '21' and '40';

    select * from dual where 211 between 21 and 40;

Do not return the same results.

Adam Musch
  • 13,286
  • 2
  • 28
  • 32
  • Don't forget, ROWNUM is applied before sorting - so this might not return the results expected; you probably do need the additional level of nesting. – Jeffrey Kemp Aug 05 '10 at 01:22
  • Thanks for the changes - though your queries might work - my immediate concern is to try and figure out why the query i posted is working differently from 9i to 10g. As its an existing application with a few hundred queries similar to the one i posted, i ideally do not want to make major changes like the ones you suggested to all of them at this time as it will involve a huge amount of retesting – Jagmag Aug 05 '10 at 02:10
0

Explain plan should help you identify the problem. As Tony has stated, any merging of the inner query into the outer query will break your query. Any queries where the condition RONUM > 1 unconditionally applies will fail.

Queries such as you are building may require building the entire result set and then filtering out the rows for the page. You may want to consider building a key set for the desired rows in then inner query and then adding additional columns in the outer query. A CARDINALITY hint on the query selecting on rownum may help.

Try using "rownum() over (order by 1) rn" to generate the order. (I assume order is different than 1 at times.) Add a "/*+ FIRST_ROWS(20) */" to the first inner query. http://www.oracle.com/technology/oramag/oracle/07-jan/o17asktom.html for more help.

BillThor
  • 7,306
  • 1
  • 26
  • 19
  • @Bill - My inner query "SELECT PM.POLICY_NO field1 PM......" is an attempt at first building the entire result set...The outer select is just an extra select query in order to filter out the rows from that resultset using the rownum in the outer query" - Is that different from what you are suggesting? – Jagmag Aug 10 '10 at 04:22
  • Also, You are right in that Explain Plan has helped by indicating that a additional view is being created which seems to be messing up my rownum logic. What i am really trying to get at is why? This is because this is an existing application migrated from 9i where the same queries work fine and i dont want to change all these queries due to the testing effort involved. I am not sure if some of the settings are causing the optimizer to work differently and maybe i can get by changing those settings instead! I hope that explains my predicament a bit better!! – Jagmag Aug 10 '10 at 04:23
  • 10g automatically generates statistics. These needed to be manually generated in 9i. If you weren't generating statistics, the resulting plan may have been significantly different than you get with statistics generated. In general, good statistics generate better plans. However, there are exceptions and your case may be one. If you can still run the query in a 9i environment, it would be interesting to compare the plans. – BillThor Aug 10 '10 at 18:59
  • @Bill - Added the original query plan from the 9i environment for comparision as Edit 4 in original post. The 10g query plan for the same query is Edit 2. If you compare the 2, the only key difference seems to be that the "FILTER" step from Step 5 in my 9i plan is now occuring at Step 3 in my 10g plan which makes all the difference to the rownum. Can u possibly throw any light on why this would be happening? – Jagmag Aug 11 '10 at 03:19
  • 1
    The view that is being dropped separates the houre and middle select statements. This separation is what permits the rownum range select to work. Without it uou end up selecting on "rownum >= 20 and rownum <= 40. As rownum is generated on the selected rows the first candidate is always 1 and is rejected. – BillThor Aug 12 '10 at 18:30