joining table returns extra rows - oracle

i am stuck in a oracle query
what i am doing is i am joining two tables on there cityid .
whats happening is when i query the first table its returning 486 rows but when i join them no matter which join and join it on the cityid it
returns 570 rows please advice how can i get only the 486 records
the query is as follows
select c.year,c.amount,c.product,g.state
from Accounts c
join Address g
on g.cityid=c.cityid
order by c.year,c.product;
regards

That's perfectly possible.
If you have multiple addresses for a given account, or multiple accounts for a given address, you may wind up with more rows than just what's in the address or account table.
Consider:
Account
id | ... | cityid
4 | ... | 12
5 | ... | 12
6 | ... | 13
7 | ... | 14
Address
id | ... | cityid
2 | ... | 12
3 | ... | 13
4 | ... | 14
With your join you get:
Account Address
id | ... | cityid | id | ... | cityid
4 | ... | 12 | 2 | ... | 12
5 | ... | 12 | 2 | ... | 12
6 | ... | 13 | 3 | ... | 13
7 | ... | 14 | 4 | ... | 14
So, you see there are 4 records returned, even though there are 3 records in Address, with record Address.2 being repeated.
This could go the other way if the foreign key relationships were reversed.
And this is actually the core feature of relational databases, that data entered with foreign key relationships maintained do not need to repeat data entry.
You can limit the rows by selecting only the first (lowest id) value to join on .. this usually involves creating a temporary table, which is an exercise I will leave to an oracle expert, because I think that Sybase's syntax for that is different (and required to be done within a stored procedure, yick).
I find myself wondering if there might be a slightly different interpretation of the schema outside what you've described that might be more likely to resolve your issue.

Related

How to write huge table data to file | Informatica 10.x

I have created a Informatica flow
where I need to read data from table that to only one column which contain empids.
But the column might contain duplicate need to write distinct values to file from below query
Query :
select distinct
emp_id
from
employee
where
empid not in
(
select distinct
custid
from
customer
);
I have added the above query in Source Qualifier
employee table contains : 5 million records and customer table contains : 20 billion records
My Informatica is still running not got completed - 6 hours over till now and nothing is written to file because of huge data in both tables
Following is my query plan
--------------------------------------------------------------------
Id | Operation | Name |
--------------------------------------------------------------------
0 | SELECT STATEMENT | |
1 | AX COORDINATOR | |
2 | AX SEND QC (RANDOM) | :AQ10002 |
3 | HASH UNIQUE | |
4 | AX RECEIVE | |
5 | AX SEND HASH | :AQ10001 |
6 | HASH UNIQUE | |
7 | HASH JOIN ANTI | |
8 | AX RECEIVE | |
9 | AX SEND PARTITION (KEY) | :AQ10000 |
10 | AX SELECTOR | |
11 | INDEX FAST FULL SCAN | PK_EMP_ID |
12 | AX PARTITION RANGE ALL | |
13 | INDEX FAST FULL SCAN | PK_CUST_ID |
--------------------------------------------------------------------
Sample table data :
employee
111
123
145
1345
111
123
145
678
....
customer
111
111
111
1345
111
145
145
145
145
145
145
....
Expected output :
123
678
Any solution is much appreciated !!!
It seems to me the SQL is the problem. If you dont have anything like sorter/aggregator, you dont have to worry about infa.
SQL seems to be having expensive operations. You can try below -
select emp_id
from employee
where not exists
(select 1 from customer where custid =emp_id)
This should be little faster because
you arent doing a subquery to get distinct from a 20billion customer table.
you dont need to use any distinct in first select because you are selecting from emp table where that emp id is unique. And not exist will make sure no duplicates coming out of first select.
You can also use left join +where but i think it will be expensive because of join-induced duplicates.
I would start with partitioning the customer table by hash or range(customer_id) or insert_date, this would speed up your inline select substantially.
Also try this:
select emp_id from employee
minus
select emp_id from employee e, customer c
where e.emp_id=c.custid;

MySQL - Top 5 rank best seller plans or courses

I sell subscriptions of my online course, as well as the courses in retail.
I would bring the "top 5" of best selling plans / courses. For this, I have a table called "subscriptionPlan", which stores the purchased plan ID, or in the case of a course, the course ID, and the amount spent on this transaction. Example:
table subscriptionPlan
sbpId | subId | plaId | couId | sbpAmount
1 | 1 | 1 | 1 | 499.99
2 | 2 | 1 | 2 | 499.99
3 | 3 | 2 | 0 | 899.99
4 | 4 | 1 | 1 | 499.99
Just for educational purposes, plaId = 1 is a plan called "Single Sale" that I created, to maintain the integrity of the DB. When the couId isn't empty, you also have bought a separate course, not a plan where you can attend any course.
My need is: List the top 5 sales. If it is a plan, display the plan name (plan table, column plaTitle). If it is a course, display its name (table course, colna couTitle). This logic that I can't code. I was able to rank a top 5 of PLANS, but it groups the courses, since the GROUP BY is by the ID of the plan. I believe the prank is here, maybe creating an IF / ELSE in this GROUPBY, but I don't know how to do this.
The query that i code, to rank my top 5 plans is:
SELECT sp.plaId, sp.couId, p.plaTitle, p.plaPermanent, c.couTitle, SUM(sbpAmount) AS sbpTotalAmount
FROM subscriptionPlan sp
LEFT JOIN plan p ON sp.plaId = p.plaId
LEFT JOIN course c ON sp.couId = c.couId
GROUP BY sp.plaId
ORDER BY sbpTotalAmount DESC
LIMIT 5
The result that i expected was:
plaId | couId | plaTitle | couTitle | plaPermanent | sbpTotalAmount
1 | 1 | Venda avulsa | Curso 01 | true | 999.98
2 | 0 | Acesso total | null | false | 899.99
3 | 2 | Venda avulsa | Curso 02 | true | 499.99
How could I get into this query formula?
When grouping you can use:
Simple columns, or
Any [complex] expression.
In your case, it seems you need to group by an expression, such as:
GROUP BY CASE WHEN sp.plaId = 1 THEN -1 ELSE sp.couId END
In this case I chose -1 as the grouping for the "Single Plan". You can replace the value for any other that doesn't match any couId.

ORACLE Performance: (Hash) Join vs Subselect With Order By's in Huge Tables

I've got the following (abstract) problem in ORACLE 11g:
There are two tables called STRONG and WEAK.
The primary key of WEAK is a compound consisting of the primary key of STRONG (called pk) plus an additional column (called fk).
Thus we have a generic 1:N relationship.
However, in the real world application, there is always exactly one row within the N WEAK rows related to an entry of STRONG that has a special relationship.
This is why there is an additional 1:1 relationship realised by adding the column fk to STRONG as well.
Furthermore, it might be worth noting that both tables are huge but well indexed.
The overall picture looks like this:
Now, I have to define a view showing rows of STRONG along with some additional columns linked by that 1:1 relationship. I tried two basic approaches:
Subselects
SELECT
(SELECT some_data1 FROM weak WHERE weak.pk = strong.pk AND weak.fk = strong.fk) some_data1,
(SELECT some_data2 FROM weak WHERE weak.pk = strong.pk AND weak.fk = strong.fk) some_data2
FROM strong
Left Outer Join
SELECT
weak.some_data1,
weak.some_data2
FROM strong
LEFT OUTER JOIN weak ON weak.pk = strong.pk AND weak.fk = strong.fk
I first thought that the "Left Outer Join"-way has to be better and I still think that this is true as long as there is no WHERE/ORDER_BY-clause. However, in the real world application, user query dialog inputs are dynamically
translated into extensions of the above statements. Typically, the user knows the primary key of STRONG resulting in queries like this:
SELECT *
FROM the_view
WHERE the_view.pk LIKE '123%' --Or even the exact key
ORDER BY the_view.pk
Using the "Left Outer Join"-way, we encountered some very serious performance problems, even though most of these SELECTs only return a few rows. I think what happened is that the hash table did not fit into the
memory resulting in way too many I/O-events. Thus, we went back to the Subselects.
Now, i have a few questions:
Q1
Does Oracle have to compute the entire hash table for every SELECT (with ORDER_BY)?
Q2
Why is the "Subselect"-way faster? Here, it might be worth noting that these columns can appear in the WHERE-clause as well.
Q3
Does it somehow matter that joining the two tables might potentially increase the number of selcted rows? If so: Can we somehow tell Oracle that this can never happen from a logical perspective?
Q4
In case that the "Left Outer Join"-Way is not a well-performing option: The "Subselect"-way does seem somewhat redundant. Is there a better way?
Thanks a lot!
EDIT
Due to request, I will add an explanation plan of the actual case. However, there are a few important things here:
In the above description, I tried to simplify the actual problem. In the real world, the view is a lot more complex.
One thing I left out due to simplification is that the performance issues mainly occur when using the STRONG => WEAK-Join in a nested join (see below). The actual situation looks like this:
ZV is the name of our target view - the explanation plan below refers to that view.
Z (~3M rows) join T (~1M rows)
T joins CCPP (~1M rows)
TV is a view based on T. Come to think of it... this might be critical. The front end application sort of restricts us in the way we define these views: In ZV, we have to join TV instead of T and we can not implement that T => CCPP-join in TV, forcing us to define the join TV => CCPP as a nested join.
We only encountered the performance problems in our productive environment with lots of user. Obviously, we had to get rid of these problems. Thus, it can not be reproduced right now. The response time of the statements below are totally fine.
The Execution Plan
---------------------------------------------------------------------------- ----------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)|
---------------------------------------------------------------------------- ----------------------------------
| 0 | SELECT STATEMENT | | 717K| 73M| | 13340 (2)|
| 1 | HASH JOIN OUTER | | 717K| 73M| 66M| 13340 (2)|
| 2 | VIEW | | 687K| 59M| | 5 (0)|
| 3 | NESTED LOOPS OUTER | | 687K| 94M| | 5 (0)|
| 4 | NESTED LOOPS OUTER | | 1 | 118 | | 4 (0)|
| 5 | TABLE ACCESS BY INDEX ROWID | Z | 1 | 103 | | 3 (0)|
| 6 | INDEX UNIQUE SCAN | SYS_C00245876 | 1 | | | 2 (0)|
| 7 | INDEX UNIQUE SCAN | SYS_C00245876 | 1798K| 25M| | 1 (0)|
| 8 | VIEW PUSHED PREDICATE | TV | 687K| 17M| | 1 (0)|
| 9 | NESTED LOOPS OUTER | | 1 | 67 | | 2 (0)|
| 10 | TABLE ACCESS BY INDEX ROWID| T | 1 | 48 | | 2 (0)|
| 11 | INDEX UNIQUE SCAN | SYS_C00245609 | 1 | | | 1 (0)|
| 12 | INDEX UNIQUE SCAN | SYS_C00254613 | 1 | 19 | | 0 (0)|
| 13 | TABLE ACCESS FULL | CCPP | 5165K| 88M| | 4105 (3)|
--------------------------------------------------------------------------------------------------------------
The real question is - how many records does your query return?
10 records only or 10.000 (or 10M) and you expect to see the first 10 rows quickly?
For the letter case the subquery solution works indeed better as you need no sort and you lookup the WEAK table only small number of times.
For the former case (i.e. the number of selected rows in both table is small) I'd expect execution plan as follows:
--------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 4 | 336 | 100 (1)| 00:00:02 |
| 1 | SORT ORDER BY | | 4 | 336 | 100 (1)| 00:00:02 |
| 2 | NESTED LOOPS OUTER | | 4 | 336 | 99 (0)| 00:00:02 |
| 3 | TABLE ACCESS BY INDEX ROWID| STRONG | 4 | 168 | 94 (0)| 00:00:02 |
|* 4 | INDEX RANGE SCAN | STRONG_IDX | 997 | | 4 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID| WEAK | 1 | 42 | 2 (0)| 00:00:01 |
|* 6 | INDEX UNIQUE SCAN | WEAK_IDX | 1 | | 1 (0)| 00:00:01 |
--------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - access("STRONG"."PK" LIKE '1234%')
filter("STRONG"."PK" LIKE '1234%')
6 - access("WEAK"."PK"(+)="STRONG"."PK" AND "WEAK"."FK"(+)="STRONG"."FK")
filter("WEAK"."PK"(+) LIKE '1234%')
If you see FULL TABLE SCAN on one or other table - the optimizes impression could be that the predicate pk LIKE '123%' will return too much records and the index access will be slower.
This could be a good or bad guess, so you may need to check your table statistics and cardinality estimation.
Some additional information follows
Q1
If Oracle performs a HASH JOIN the whole data source (typically the smaller one) must be read in memory
in the hash table. This is the whole table or the part of it as filtered by the WHERE/ON clause.
(In your case only records with pk LIKE '123%' )
Q2
This may be only an impression, as you see quickly first records. The subquery is performed
only for the first few fetched rows.
To know the exact answer you must examine (or post) the execution plans.
Q3
No, sorry, joining of the two tables NEVER potentially increase the number of selcted rows but returns exact the number of rows
as defined in the SQL standard.
It is your responsibility to define the join on a unique / primary key to avoid duplication.
Q4
You may of course select something like some_data1 ||'#'||some_data2 in the subquery, but it is in your responsibility
to decide if it is safe..

Improving SQL Exists scalability

Say we have two tables, TEST and TEST_CHILDS in the following way:
creat TABLE TEST(id1 number PRIMARY KEY, word VARCHAR(50),numero number);
creat TABLE TEST_CHILD (id2 number references test(id), word2 VARCHAR(50));
CREATE INDEX TEST_IDX ON TEST_CHILD(word2);
CREATE INDEX TEST_JOIN_IDX ON TEST_CHILD(id);
insert into TEST SELECT ROWNUM,U1.USERNAME||U2.TABLE_NAME, LENGTH(U1.USERNAME) FROM ALL_USERS U1,ALL_TABLES U2;
INSERT INTO TEST_CHILD SELECT MOD(ROWNUM,15000)+1,U1.USER_ID||U2.TABLE_NAME FROM ALL_USERS U1,ALL_TABLES U2;
We would like to query to get rows from TEST table that satisfy some criteria in the child table, so we go for:
SELECT /*+FIRST_ROWS(10)*/* FROM TEST T WHERE EXISTS (SELECT NULL FROM TEST_CHILD TC WHERE word2 like 'string%' AND TC.id = T.id ) AND ROWNUM < 10;
We always want just the first 10 results, not any more at all. Therefore, we would like to get the same response time to read 10 results whether table has 10 matching values or 1,000,000; since it could get 10 distinct results from the child table and get the values on the parent table (or at least that is the plan that we would like). But when checking the actual execution plan we see:
-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 54 | 5 (20)| 00:00:01 |
|* 1 | COUNT STOPKEY | | | | | |
| 2 | NESTED LOOPS | | | | | |
| 3 | NESTED LOOPS | | 1 | 54 | 5 (20)| 00:00:01 |
| 4 | SORT UNIQUE | | 1 | 23 | 3 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID| TEST_CHILD | 1 | 23 | 3 (0)| 00:00:01 |
|* 6 | INDEX RANGE SCAN | TEST_IDX | 1 | | 2 (0)| 00:00:01 |
|* 7 | INDEX UNIQUE SCAN | SYS_C005145 | 1 | | 0 (0)| 00:00:01 |
| 8 | TABLE ACCESS BY INDEX ROWID | TEST | 1 | 31 | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(ROWNUM<10)
6 - access("WORD2" LIKE 'string%')
filter("WORD2" LIKE 'string%')
7 - access("TC"."ID"="T"."ID")
SORT UNIQUE under the STOPKEY, what afaik means that it is reading all results from the child table, making the distinct to finally select only the first 10, making the query not as scalable as we would like it to be.
Is there any mistake in my example?
Is it possible to improve this execution plan so it scales better?
The SORT UNIQUE is going to find and sort all of the records from TEST_CHILD that matched 'string%' - it is NOT going to read all results from child table. Your logic requires this. IF you only picked the first 10 rows from TEST_CHILD that matched 'string%', and those 10 rows all had the same ID, then your final results from TEST would only have 1 row.
Anyway, your performance should be fine as long as 'string%' matches a relatively low number of rows in TEST_CHILD. IF your situation is such that 'string%' often matches a HUGE record count on TEST_CHILD, there's not much you can do to make the SQL more performant given the current tables. In such a case, if this is a mission-critical SQL, with performance tied to your annual bonus, there's probably some fancy footwork you could do with MATERIALIZED VIEWs to, e.g. pre-compute 10 TEST rows for high-cardinality WORD2 values in TEST_CHILD.
One final thought - a "risky" solution, but one which should work if you don't have thousands of TEST_CHILD rows matching the same TEST row, would be the following:
SELECT *
FROM TEST
WHERE ID1 IN
(SELECT ID2
FROM TEST_CHILD
WHERE word2 like 'string%'
AND ROWNUM < 1000)
AND ROWNUM <10;
You can adjust 1000 up or down, of course, but if it's too low, you risk finding less than 10 distinct ID values, which would give you final results with less than 10 rows.

Make Oracle 9i use indexes

I'm having a performance issue when deploying an app developed on 10g XE in a client's 9i server. The same query produces completely different query plans depending on the server:
SELECT DISTINCT FOO.FOO_ID AS C0,
GEE.GEE_CODE AS C1,
TO_CHAR(FOO.SOME_DATE, 'DD/MM/YYYY') AS C2,
TMP_FOO.SORT_ORDER AS SORT_ORDER_
FROM TMP_FOO
INNER JOIN FOO ON TMP_FOO.FOO_ID=FOO.FOO_ID
LEFT JOIN BAR ON FOO.FOO_ID=BAR.FOO_ID
LEFT JOIN GEE ON FOO.GEE_ID=GEE.GEE_ID
ORDER BY SORT_ORDER_;
Oracle Database 10g Express Edition Release 10.2.0.1.0 - Production:
-------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 67 | 10 (30)| 00:00:01 |
| 1 | SORT UNIQUE | | 1 | 67 | 9 (23)| 00:00:01 |
| 2 | NESTED LOOPS OUTER | | 1 | 67 | 8 (13)| 00:00:01 |
|* 3 | HASH JOIN OUTER | | 1 | 48 | 7 (15)| 00:00:01 |
| 4 | NESTED LOOPS | | 1 | 44 | 3 (0)| 00:00:01 |
| 5 | TABLE ACCESS FULL | TMP_FOO | 1 | 26 | 2 (0)| 00:00:01 |
| 6 | TABLE ACCESS BY INDEX ROWID| FOO | 1 | 18 | 1 (0)| 00:00:01 |
|* 7 | INDEX UNIQUE SCAN | FOO_PK | 1 | | 0 (0)| 00:00:01 |
| 8 | TABLE ACCESS FULL | BAR | 1 | 4 | 3 (0)| 00:00:01 |
| 9 | TABLE ACCESS BY INDEX ROWID | GEE | 1 | 19 | 1 (0)| 00:00:01 |
|* 10 | INDEX UNIQUE SCAN | GEE_PK | 1 | | 0 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------
Oracle9i Release 9.2.0.1.0 - 64bit Production:
----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost |
----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 98M| 6546M| | 3382K|
| 1 | SORT UNIQUE | | 98M| 6546M| 14G| 1692K|
|* 2 | HASH JOIN OUTER | | 98M| 6546M| 137M| 2874 |
| 3 | VIEW | | 2401K| 109M| | 677 |
|* 4 | HASH JOIN OUTER | | 2401K| 169M| 40M| 677 |
| 5 | VIEW | | 587K| 34M| | 24 |
|* 6 | HASH JOIN | | 587K| 34M| | 24 |
| 7 | TABLE ACCESS FULL| TMP_FOO | 8168 | 207K| | 10 |
| 8 | TABLE ACCESS FULL| FOO | 7188 | 245K| | 9 |
| 9 | TABLE ACCESS FULL | BAR | 409 | 5317 | | 1 |
| 10 | TABLE ACCESS FULL | GEE | 4084 | 89848 | | 5 |
----------------------------------------------------------------------------
As far as I can tell, indexes exist and are correct. What are my options to make Oracle 9i use them?
Update #1: TMP_FOO is a temporary table and it has no rows in this test. FOO is a regular table with 13,035 rows in my local XE; not sure why the query plan shows 1, perhaps it's realising that an INNER JOIN against an empty table won't require a full table scan :-?
Update #2: I've spent a couple of weeks trying everything and nothing provided a real enhancement: query rewriting, optimizer hints, changes in DB design, getting rid of temp tables... Finally, I got a copy of the same 9.2.0.1.0 unpatched Oracle version the customer has (with obvious architecture difference), installed it at my site and... surprise! In my 9i, all execution plans come instantly and queries take from 1 to 10 seconds to complete.
At this point, I'm almost convinced that the customer has a serious misconfiguration issue.
it looks like either you don't have data on your 10g express database, or your statistics are not collected properly. In either case it looks to Oracle like there aren't many rows, and therefore an index-range scan is appropriate.
In your 9i database, the statistics look like they are collected properly and Oracle sees a 4-table join with lots of rows and without a where clause. In that case since you haven't supplied an hint, Oracle builds an explain plan with the default ALL_ROWS optimizer behaviour: Oracle will find the plan that is the most performant to return all rows to the last. In that case the HASH JOIN with full table scans is brutally efficient, it will return big sets of rows faster that with an index NESTED LOOP join.
Maybe you want to use an index because you are only interested in the first few rows of the query. In that case use the hint /*+ FIRST_ROWS*/ that will help Oracle understand that you are more interested in the first row response time than overall total query time.
Maybe you want to use an index because you think this would result in a faster total query time. You can force an explain plan through the use of hints like USE_NL and USE_HASH but most of the time you will see that if the statistics are up-to-date the optimizer will have picked the most efficient plan.
Update: I saw your update about TMP_FOO being a temporary table having no row. The problem with temporary table is that they have no stats so my above answer doesn't apply perfectly to temporary tables. Since the temp table has no stats, Oracle has to make a guess (here it chooses quite arbitrarly 8168 rows) which results in an inefficient plan.
This would be a case where it could be appropriate to use hints. You have several options:
A mix of LEADING, USE_NL and USE_HASH hints can force a specific plan (LEADING to set the order of the joins and USE* to set the join method).
You could use the undocumented CARDINALITY hint to give additional information to the optimizer as described in an AskTom article. While the hint is undocumented, it is arguably safe to use. Note: on 10g+ the DYNAMIC_SAMPLING could be the documented alternative.
You can also set the statistics on the temporary table beforehand with the DBMS_STATS.set_table_stats procedure. This last option would be quite radical since it would potentially modify the plan of all queries against this temp table.
It could be that 9i is doing it exactly right. According to the stats posted, the Oracle 9i database believes it is dealing with a statement returning 98 million rows, whereas the 10G database thinks it will return 1 row. It could be that both are correct, i.e the amount of data in the 2 databases is very very different. Or it could be that you need to gather stats in either or both databases to get a more accurate query plan.
In general it is hard to tune queries when the target version is older and a different edition. You have no chance of tuning a query without realistic volumes of data, or at least realistic statistics.
If you have a good relationship with your client you could ask them to export their statistics using DBMS_STATS.EXPORT_SCHEMA_STATS(). Then you can import the stats using the matching IMPORT_SCHEMA_STATS procedure.
Otherwise you'll have to fake the numbers yourself using the DBMS_STATS.SET_TABLE_STATISTICS() procedure. Find out more.
You could add the following hints which would "force" Oracle to use your indexes (if possible):
Select /*+ index (FOO FOO_PK) */
/*+ index (GEE GEE_PK) */
From ...
Or try to use the FIRST_ROWS hint to indicate you're not going to fetch all these estimated 98 Million rows... Otherwise I doubt the indexes would make a huge difference because you have no Where clause so Oracle would have to read these tables anyways.
The customer had changed a default setting in order to support a very old third-party legacy application: the static parameter OPTIMIZER_FEATURES_ENABLE had been changed from the default value in 9i (9.2.0) to 8.1.7.
I made the same change in a local copy of 9i and I got the same problems: explain plans that take hours to be calculated and so on.
(Knowing this, I've asked a related question at ServerFault, but I believe this solves the original question.)

Resources