Slow inner join in Oracle - sql

I have Oracle database with a main table contain 9 000 000 rows and a second with 19 000 000 rows.
When I do :
SELECT *
FROM main m
INNER JOIN second s ON m.id = s.fk_id AND s.cd = 'E' AND s.line = 1
It's take 45 seconds to get the first part of the result, even with all the index below :
CREATE INDEX IDX_1 ON SECOND (LINE, CD, FK_ID, ID);
CREATE INDEX IDX_1 ON SECOND (LINE, CD);
MAIN (ID) AS PRIMARY KEY
Any idea how to do it faster ? I try some index, rebuild but it's always take 45 seconds
Here is the execution plan :
------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 8850631 | 2133002071 | 696494 | 00:00:28 |
| * 1 | HASH JOIN | | 8850631 | 2133002071 | 696494 | 00:00:28 |
| * 2 | TABLE ACCESS FULL | SECOND | 8850631 | 646096063 | 143512 | 00:00:06 |
| 3 | TABLE ACCESS FULL | MAIN | 9227624 | 1550240832 | 153363 | 00:00:06 |
------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
------------------------------------------
* 1 - access("M"."ID"="S"."FK_ID")
* 2 - filter("S"."CD"='D' AND "S"."LINE"=1)
Thanks

If you want to see the first line quickly you have to enable Oracle to use the NESTED LOOP join.
This will required an index on second with the two columns you constraint in your query and an index on main on the join column id
create index second_idx on second(line,cd);
create index main_idx on main(id);
You'll see an execution plan similar to one below
--------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 87 | 8178 | 178 (0)| 00:00:03 |
| 1 | NESTED LOOPS | | | | | |
| 2 | NESTED LOOPS | | 87 | 8178 | 178 (0)| 00:00:03 |
| 3 | TABLE ACCESS BY INDEX ROWID| SECOND | 87 | 2523 | 4 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | SECOND_IDX | 1 | | 3 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | MAIN_IDX | 1 | | 1 (0)| 00:00:01 |
| 6 | TABLE ACCESS BY INDEX ROWID | MAIN | 1 | 65 | 2 (0)| 00:00:01 |
--------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - access("S"."LINE"=1 AND "S"."CD"='E')
5 - access("M"."ID"="S"."FK_ID")
You will access via index all rows in second with requested lineand cd (plan line 4 and 3) and for each such row you'll access via index the main table (lines 5 and 6)
This will provide an instant access to the first few rows and will work fine if there are a low number of rows in second table with the selected line and cd. In other case (when there is a large number of rows with s.cd = 'E' AND s.line = 1 - say 10k+) you will still see the first result rows quickly, but you'll wait ages to see the last row (it will take much more that the 45 seconds to finish the query).
If this is a problem you have to use a HASH JOIN (which you probaly do now).
A hash join typically doesn not use indexes and produced following execution plan
-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10182 | 1153K| 908 (1)| 00:00:11 |
|* 1 | HASH JOIN | | 10182 | 1153K| 908 (1)| 00:00:11 |
|* 2 | TABLE ACCESS FULL| SECOND | 10182 | 99K| 520 (2)| 00:00:07 |
| 3 | TABLE ACCESS FULL| MAIN | 90000 | 9316K| 387 (1)| 00:00:05 |
-----------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("M"."ID"="S"."FK_ID")
2 - filter("S"."LINE"=1 AND "S"."CD"='E')
Summary
To use the nested loops the indexes must be available as described above
The switch between nested loopsand hash join is done by the Oracle database (CBO) - provided that your tables statistics and database configuration are fine.

Related

Improving the query processing performance of SUM and JOIN SQL

SELECT SUM(C_QUANTITY)
FROM CARS JOIN ORDERS
ON C_ORDERKEY = O_ORDERKEY;
I have this query that aggregate sum of L_QUANTITY from the JOIN tables. The query cost, by using EXPLAIN PLAN is 12147. The objective is to improve this SELECT statement by implementing a more efficient SELECT statement that will get the same result.
I have tried
SELECT SUM(C_QUANTITY)
FROM CARS
It returned the same result but the query cost is exactly the same as the original. I thought that by removing the JOIN, the SELECT query will improve.
Is there a way to reduce the cost by simply modify the SELECT statement only?
Edit:
Original query plan
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 2287326370
-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 3 | 12147 (1)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 3 | | |
| 2 | TABLE ACCESS FULL| CARS | 1800K| 5273K| 12147 (1)| 00:00:01 |
-------------------------------------------------------------------------------
9 rows selected.
With the second query
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 2287326370
-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 3 | 12147 (1)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 3 | | |
| 2 | TABLE ACCESS FULL| CARS | 1800K| 5273K| 12147 (1)| 00:00:01 |
-------------------------------------------------------------------------------
9 rows selected.
If you have two table cars and ordersthat are not connected, you will get and ordinary join execution plan as follows.
--------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
--------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 15 | | 297 (2)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 15 | | | |
|* 2 | HASH JOIN | | 100K| 1464K| 1664K| 297 (2)| 00:00:01 |
| 3 | TABLE ACCESS FULL| ORDERS | 100K| 488K| | 47 (3)| 00:00:01 |
| 4 | TABLE ACCESS FULL| CARS | 100K| 976K| | 62 (2)| 00:00:01 |
--------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("C_ORDERKEY"="O_ORDERKEY")
The table cars is apparently a child table or the orders, i.e. you have this constraints
alter table orders add primary key (O_ORDERKEY);
alter table cars add constraint cars_fk foreign key(C_ORDERKEY) references orders(O_ORDERKEY);
Oracle is smart enough to know it does not need to access the orders table to get the sum
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 10 | 63 (4)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 10 | | |
|* 2 | TABLE ACCESS FULL| CARS | 100K| 976K| 63 (4)| 00:00:01 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("C_ORDERKEY" IS NOT NULL)
Note the filter C_ORDERKEY IS NOT NULL which is still required to get the right sum if the column C_ORDERKEY is nullable. (Those rows would be eliminated in the join).
In case it is not, which may be meaningfull
alter table cars modify C_ORDERKEY not null;
you only need to define an index on the C_QUANTITY column to get the optimal plan
create index car_idx on cars(C_QUANTITY);
---------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 5 | 63 (2)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 5 | | |
| 2 | INDEX FAST FULL SCAN| CAR_IDX | 100K| 488K| 63 (2)| 00:00:01 |
---------------------------------------------------------------------------------
Note that the INDEX FAST FULL SCAN uses the index in a kind as a table full scan access (i.e. without direct accessing the index block using the pointers) so it is (in case that the index is smaller than the table) much faster that the table full scan access.
I suggest adding the following index:
CREATE INDEX idx ON ORDERS (O_ORDERKEY, C_QUANTITY);
Presumably, the ORDERS table would be much larger than CARS. If so, Oracle would likely satisfy the query by scanning CARS and then would be able to use the above index to lookup in the ORDERS table. I add the C_QUANTITY column to the end of the index, to cover the summation in the select clause.

Two similar tables but different join performances

I am running the exact same join query using two different tables, but the first one (table A) times out whereas the second (table B) does not.
SELECT * FROM table_X
INNER JOIN table_A
ON table_A.point_origin = table_X.item_id
WHERE ROWNUM < 10;
SELECT * FROM table_X
INNER JOIN table_B
ON table_B.point_origin = table_X.item_id
WHERE ROWNUM < 10;
As far as I know, table A is a subset of table B. Neither table A nor table B have point_origin indexed.
(Edit for clarification: table A is a only a subset of table B in terms of row identifiers, not in terms of exact column data.)
For what it's worth, I'm dealing with very large tables and item_id is indexed.
Is there anything else that would affect performance here or am I definitely wrong about some information provided?
Edit: Additional information per a comment below
table_A:
---------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Pstart| Pstop |
---------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 9 | 4743 | 12 (0)| | |
|* 1 | COUNT STOPKEY | | | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID| table_X | 1 | 227 | 1 (0)| | |
| 3 | NESTED LOOPS | | 11 | 5797 | 12 (0)| | |
| 4 | PARTITION RANGE ALL | | 10M| 2969M| 2 (0)| 1 | 4 |
| 5 | TABLE ACCESS FULL | table_A | 10M| 2969M| 2 (0)| 1 | 4 |
|* 6 | INDEX RANGE SCAN | table_X_IP_PK | 1 | | 1 (0)| | |
---------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(ROWNUM<10)
6 - access("table_A"."POINT_ORIGIN"="table_X"."ITEM_ID")
Note
-----
- 'PLAN_TABLE' is old version
table_B:
-----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
-----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 9 | 3879 | 11 (0)|
|* 1 | COUNT STOPKEY | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID| table_X | 1 | 227 | 1 (0)|
| 3 | NESTED LOOPS | | 10 | 4310 | 11 (0)|
| 4 | TABLE ACCESS FULL | table_B | 118M| 22G| 2 (0)|
|* 5 | INDEX RANGE SCAN | table_X_IP_PK | 1 | | 1 (0)|
-----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(ROWNUM<10)
5 - access("table_B"."POINT_ORIGIN"="table_X"."ITEM_ID")
Note
-----
- 'PLAN_TABLE' is old version
It appears that table_a is partitioned and that the query only needs to scan 4 partitions while table_b is not partitioned and must be read in its entirety. The optimizer estimates that 4 partitions of table_a have 10 million rows while table_b has 118 million rows. You're using a nested loop so you'd expect O(n) performance so based on the statistics, it would make sense that the second query would take ~11.8 times as long as the first query.
Are the optimizer's estimates accurate? The optimizer is only as good as the statistics you've given it and it is possible that one or both tables have stale statistics.

An oracle performance issue on COUNT()

I'm using Oracle 11g, the main table has about 10m records. Here is my query:
SELECT COUNT (*)
FROM CONTACT c INNER JOIN STATUS S ON C.STATUS = S.STATUS
WHERE C.USER = 1 AND S.REQUIRE = 1 AND ROWNUM = 1;
The Cost is 3736, but when I changed it to this form:
SELECT COUNT (*) FROM
(SELECT 1 FROM CONTACT c INNER JOIN STATUS S ON C.STATUS = S.STATUS
WHERE C.USER = 1 AND S.REQUIRE = 1 AND ROWNUM = 1);
The Cost became 5! What's the difference between these 2 queries?
Here are the explain plan for both query:
The first query:
----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 10 | 3736 (1)| 00:00:45 |
| 1 | SORT AGGREGATE | | 1 | 10 | | |
|* 2 | COUNT STOPKEY | | | | | |
| 3 | NESTED LOOPS | | 4627 | 46270 | 3736 (1)| 00:00:45 |
| 4 | TABLE ACCESS BY INDEX ROWID| CONTACT | 6610 | 33050 | 3736 (1)| 00:00:45 |
|* 5 | INDEX RANGE SCAN | IX_CONTACT_USR | 6610 | | 20 (0)| 00:00:01 |
|* 6 | INDEX RANGE SCAN | IX_CONTACT_STATUS | 1 | 5 | 0 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter(ROWNUM=1)
5 - access("C"."USER"=1)
6 - access("C"."STATUS"="S"."STATUS" AND "S"."REQUIRE"=1)
The second query:
-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 5 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | | | |
| 2 | VIEW | | 1 | | 5 (0)| 00:00:01 |
|* 3 | COUNT STOPKEY | | | | | |
| 4 | NESTED LOOPS | | 2 | 20 | 5 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID| CONTACT | 3 | 15 | 5 (0)| 00:00:01 |
|* 6 | INDEX RANGE SCAN | IX_CONTACT_USR | 6610 | | 3 (0)| 00:00:01 |
|* 7 | INDEX RANGE SCAN | IX_CONTACT_STATUS | 1 | 5 | 0 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - filter(ROWNUM=1)
6 - access("C"."USER"=1)
7 - access("C"."STATUS"="S"."STATUS" AND "S"."REQUIRE"=1)
I executed 2 queries, the first one sometimes cost 45s+ (e.g. first run or change the user id), otherwise it will cost <1s. I totally don't know why it's such different, maybe db cache?
When I executed the second query, I can always get result in 1s. So I think the second one is better, but I don't the reason why it improves a lot.
You can see where the difference comes in by comparing the line in the execution plans that access the CONTACT table (looks at the rows column, the first one).
First:
| 4 | TABLE ACCESS BY INDEX ROWID| CONTACT | 6610 | 33050 | 3736 (1)| 00:00:45 |
Second:
| 5 | TABLE ACCESS BY INDEX ROWID| CONTACT | 3 | 15 | 5 (0)| 00:00:01 |
In the first example, the ROWNUM = 1 predicate isn't applied until after the CONTACT table has been accessed, so you're getting 6610 rows returned from this table. Whereas in your second query optimizer has only returned 3. This is many orders of magnitude less, which is why you're seeing the second query complete quicker.
As to why the second execution of the "slow" query is "fast", you're thinking is correct - the data has been loaded from disk into the buffer cache so access is much quicker.
Most likely that's just estimation difference and they will have same execution statistics. Trace both + tkprof to get real data.
Also if you want some more details behind optimizer logic - do hard parse with event 10053.
Cost is not only the factor for the queries, some times it depends on the server also, which u r showing is it a CPU cost or I/O Cost, some times cost my vary, because of the Column Cardinality, Conditions of the query. if u wanna see the much clarification on the queries, get the explain plan or TKPROOF, so that u 'll get to know , it's going for full table scan or which index is picking up and execution time.

SQL tuning issue

I have a query:
select count(1) CNT
from file_load_params a
where a.doc_type = (select b.doc_type
from file_load_header b
where b.indicator = 'XELFASI')
order by a.line_no
Which explain plan is:
-----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 7 | 3 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 7 | | |
|* 2 | TABLE ACCESS FULL | FILE_LOAD_PARAMS | 15 | 105 | 2 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID| FILE_LOAD_HEADER | 1 | 12 | 1 (0)| 00:00:01 |
|* 4 | INDEX UNIQUE SCAN | FILE_LOAD_HEADER_UK | 1 | | 0 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------
I thought that I could optimize this query and write this one:
select count(1) CNT
from file_load_params a,file_load_header b
where b.indicator = 'XELFASI'
and a.doc_type = b.doc_type
order by a.line_no
Its explain plan is:
-----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 19 | 3 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 19 | | |
| 2 | NESTED LOOPS | | 15 | 285 | 3 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID| FILE_LOAD_HEADER | 1 | 12 | 1 (0)| 00:00:01 |
|* 4 | INDEX UNIQUE SCAN | FILE_LOAD_HEADER_UK | 1 | | 0 (0)| 00:00:01 |
|* 5 | TABLE ACCESS FULL | FILE_LOAD_PARAMS | 15 | 105 | 2 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------
Is it good? I think not,but I expected better result...Do you have any idea?
From the explain plans, these appear to be tiny tables and the cost of the query is negligible. How long do they take to run and how quickly do you need them to run ?
But remove the ORDER BY. Since you are selecting a single row COUNT aggregate it is pointless.
One of the possible optimizations i see from your explain plan is
TABLE ACCESS FULL | FILE_LOAD_PARAMS
This seems to indicate that table file_load_params possibly does not have any index on doc_type
If that is the case, can you add an index for doc_type. If you already have indexes, can you post your table schema for file_load_params
The result is not the same for the two queries. The IN operator automatically also applies a DISTINCT to the inner query. And in this case it is probably not a key you are joining on (if it is, then make it an unique key), so it cannot be optimized away.
As for optimizing the query, then do as InSane says, add an index on Doc_Type in FILE_LOAD_PARAMS

(Oracle Performance) Will a query based on a view limit the view using the where clause?

In Oracle (10g), when I use a View (not Materialized View), does Oracle take into account the where clause when it executes the view?
Let's say I have:
MY_VIEW =
SELECT *
FROM PERSON P, ORDERS O
WHERE P.P_ID = O.P_ID
And I then execute the following:
SELECT *
FROM MY_VIEW
WHERE MY_VIEW.P_ID = '1234'
When this executes, does oracle first execute the query for the view and THEN filter it based on my where clause (where MY_VIEW.P_ID = '1234') or does it do this filtering as part of the execution of the view? If it does not do the latter, and P_ID had an index, would I also lose out on the indexing capability since Oracle would be executing my query against the view which doesn't have the index rather than the base table which has the index?
It will not execute the query first. If you have a index on P_ID, it will be used.
Execution plan is the same as if you would merge both view-code and WHERE-clause into a single select statement.
You can try this for yourself:
EXPLAIN PLAN FOR
SELECT *
FROM MY_VIEW
WHERE MY_VIEW.P_ID = '1234'
followed by
SELECT * FROM TABLE( dbms_xplan.display );
---------------------------------------------------------------------------------
|Id | Operation | Name |Rows| Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 52 | 2 (0)| 00:00:01|
| 1 | NESTED LOOPS | | 1 | 52 | 2 (0)| 00:00:01|
| 2 | TABLE ACCESS BY INDEX ROWID| PERSON | 1 | 26 | 2 (0)| 00:00:01|
| 3 | INDEX UNIQUE SCAN | PK_P | 1 | | 1 (0)| 00:00:01|
| 4 | TABLE ACCESS BY INDEX ROWID| ORDERS | 1 | 26 | 0 (0)| 00:00:01|
| 5 | INDEX RANGE SCAN | IDX_O | 1 | | 0 (0)| 00:00:01|
---------------------------------------------------------------------------------
WOW!! This is interesting.. I have two different explain plan depends on different data volumn & query inside logical view(This is my assumption)
The original question case : It is definitely doing filtering first.
I have small number of data(<10 in total) in this test table.
`
| 0 | SELECT STATEMENT | | 2 | 132 | 2 (0)|
00:00:01 |
| 1 | NESTED LOOPS | | 2 | 132 | 2 (0)|
00:00:01 |
| 2 | TABLE ACCESS BY INDEX ROWID| PERSON | 1 | 40 | 1 (0)|
00:00:01 |
|* 3 | INDEX UNIQUE SCAN | PERSON_PK | 1 | | 0 (0)|
00:00:01 |
|* 4 | INDEX RANGE SCAN | ORDERS_PK | 2 | 52 | 1 (0)|
00:00:01 |
Predicate Information (identified by operation id)
3 - access("P"."P_ID"=1)
4 - access("O"."P_ID"=1)
Note
dynamic sampling used for this statement
`
However, when the data become larger(just several hundreads though, 300 ~ 400) and the view query become complex(using "connect by" etc..), the plan changed, I think...
| 0 | SELECT STATEMENT | | 1 | 29 | 2
(0)| 00:00:01 |
| 1 | NESTED LOOPS | | 1 | 29 | 2
(0)| 00:00:01 |
| 2 | TABLE ACCESS BY INDEX ROWID| RP_TRANSACTION | 1 | 12 | 1
(0)| 00:00:01 |
|* 3 | INDEX UNIQUE SCAN | RP_TRANSACTION_PK | 1 | | 0
(0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID| RP_REQUEST | 279 | 4743 | 1
(0)| 00:00:01 |
|* 5 | INDEX UNIQUE SCAN | RP_REQUEST_PK | 1 | | 0
(0)| 00:00:01 |
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("TRANSACTION_ID"=18516648)
5 - access("REQ"."REQUEST_ID"="TRANS"."REQUEST_ID")
---- Below is my original post
In my knowledge, the oracle first execute the view(logical view) using temporary space and then do the filter.. So your query is basically same as
SELECT *
FROM ( SELECT *
FROM PERSON P, ORDERS O
WHERE P.P_ID = O.P_ID
) where P_ID='1234'
I don't think you can create index on logical view(Materialized view uses index)
Also, you should be aware, you would execute the query for MY_VIEW, everytime you using
select *
from MY_VIEW
where P_ID = '1234'.
I mean every single time. Naturally, it is not a good idea for the performance matter