I would say that your problem is that you are doing many LEFT JOINs and the final resultset gets too big after applying all those JOINs. Also indexes cannot be used this way to calculate MIN or MAX in the fastest possible way. With good use of indexes you should be able to calculate MIN or MAX very quickly.
I would write the query rather like this:
SELECT t1.id,
(SELECT MIN(t5.date) FROM t5 JOIN t4 ON t5.p_id = t4.p_id WHERE t4.id = t1.id) AS first_pri_date,
(SELECT MIN(date) FROM t3 WHERE t3.id = t1.id) AS first_pub_date,
(SELECT MAX(date) FROM t3 WHERE t3.id = t1.id) AS last_publ_date,
(SELECT MIN(date) FROM t2 WHERE t2.id = t1.id) AS first_exp_date
FROM t1
ORDER BY t1.id;
For better performace create indexes on (id, date)
or (p_id, date)
.
So your indexes would be like this:
CREATE INDEX ix2 ON T2 (id,date);
CREATE INDEX ix3 ON T3 (id,date);
CREATE INDEX ix5 ON T5 (p_id,date);
CREATE INDEX ix4 ON T4 (id);
But there still remains a problem with the join between t4
and t5
.
In case there is 1:1 relation between t1
and t4
, it could be even better to write something like this on the second line:
(SELECT MIN(t5.date) FROM t5 WHERE t5.p_id = (SELECT p_id FROM t4 WHERE t4.id=t1.id)) AS first_pri_date,
If it is 1:N and also if CROSS APPLY and OUTER APPLY work on your Oracle version, you can rewrite the second line like this:
(SELECT MIN(t5min.PartialMinimum)
FROM t4
CROSS APPLY
(
SELECT PartialMinimum = MIN(t5.date)
FROM t5
WHERE t5.p_id = t4.p_id
) AS t5min
WHERE t4.id = t1.id)
AS first_pri_date
All this is aimed at the best possible use of indexes during calculation of MIN or MAX.
So the whole SELECT could be rewritten like this:
SELECT t1.id,
(SELECT MIN(t5min.PartialMinimum)
FROM t4
CROSS APPLY
(
SELECT TOP 1 PartialMinimum = date
FROM t5
WHERE t5.p_id = t4.p_id
ORDER BY 1 ASC
) AS t5min
WHERE t4.id = t1.id) AS first_pri_date,
(SELECT TOP 1 date FROM t2 WHERE t2.id = t1.id ORDER BY 1 ASC) AS first_exp_date,
(SELECT TOP 1 date FROM t3 WHERE t3.id = t1.id ORDER BY 1 ASC) AS first_pub_date,
(SELECT TOP 1 date FROM t3 WHERE t3.id = t1.id ORDER BY 1 DESC) AS last_publ_date
FROM t1
ORDER BY 1;
This is as I believe most optimal way how to get MIN or MAX from historical data table.
The point is, that using MIN with a lot of non indexed values makes server load all the data into the memory and then calculate MIN or MAX from the non-indexed data, which takes long time because it has high demands on I/O operations. Bad usage of indexes when using MIN or MAX can lead to the situation, where you have all your historical table data cached in memory without needing it for anything else except MIN or MAX calculation.
Without the CROSS APPLY part of the query the server would need to load to memory all individual dates from t5 and calculate MAX from the whole loaded resultset.
Mark that MIN function on properly indexed table behaves like TOP 1 ORDER BY, which is very fast. In this way you can get your results instantly.
CROSS APPLY is available in Oracle 12C, otherwise you can use pipelined functions.
Check this SQL Fiddle, especially the differences in execution plans.