tags:

views:

625

answers:

4

This is three different selects using same subquery. How can I use subquery result instead of doing sub query again.

SELECT  *
FROM    Address
WHERE   address_key IN
        (
        SELECT  address_key
        FROM    person_address
        WHERE   peson_key IN (person_list)
        );   -- person_list := '1,2,3,4'

SELECT  *
FROM    Phone 
WHERE   phone_key IN
        (
        SELECT  address_key
        FROM    person_address
        WHERE   peson_key IN (person_list)
        );

SELECT  *
FROM    Email
WHERE   address_key IN
        (
        SELECT  address_key
        FROM    person_address
        WHERE   peson_key IN (person_list)
        );
+4  A: 

You can create a materialized view for this query:

CREATE MATERIALIZED VIEW v_address
REFRESH FORCE ON COMMIT
AS
SELECT  address_key
FROM    person_address
WHERE   person_key IN (person_list)

, or create a temporary table and populate it:

CREATE GLOBAL TEMPORARY TABLE tt_address (VARCHAR2(50));

INSERT
INTO   tt_address
SELECT  address_key
FROM    person_address
WHERE   person_key IN (person_list)

, but, really, if you index your person_key, it's OK to reuse the subquery.

Since you have 3 separate queries, you need your values to be visible to them one way or another.

That means you need to store these values somewhere, be it memory, temporary tablespace or a permanent tablespace.

But the values you need are already stored in the person_address, all you need is to fetch them.

Using the subquery 3 times will involve 12 index scans to fetch the ROWID's from the index on person_key and 12 table ROWID lookups to fetch address_key from the table. Then most probably a HASH TABLE will be built over them.

This is a matter of microseconds.

Of course, the temporary table or a materialized view would be a little more efficient, but changing the subquery time from 100 microseconds to 50 is hardly worth it, provided that the main queries can take minutes.

Quassnoi
+1 or the GTT idea - especially if the logic to populate it is more complicated or time consuming than in the example here.
Jeffrey Kemp
A: 

This should do it:

select *
  from address,
       phone
       email
 where (address.address_key, phone.phone_key, email.address_key) in (
         select pa.address_key, pa.address_key, pa.address_key
           from person_address pa
          where pa.person_key in (person_list)
       )
;
l0b0
A cartesian product betwen the tables?
David Aldridge
+1  A: 

Use the with clause. I didn't re-create your exact example issue, but any number of repeated sub-queries can be put in the WITH clause and then referenced in the query.

WITH  address_keys as (
        SELECT  address_key
        FROM    person_address
        WHERE   peson_key IN (person_list)
        )
Select * from table1, table2, address_keys
where table1.address_key = address_keys.address_key
and table2.address_key = address_keys.address_key
Brian
With does not return three result sets as I want. The view created with WITH clause can only be used in one query not multiple. I might be missing something.
I think Brian's point is to not run three queries - run one query by UNION ALL'ing them together. It'll be more efficient this way too.
Jeffrey Kemp
Of course, if the three result sets have wildly different patterns, then perhaps this won't help :)
Jeffrey Kemp
+1  A: 

First I think that in most cases this optimization does not bring significant improvements(after the first query the data blocks of PERSON_ADDRESS would be mostly cached in the buffer cache and therefore not read from HDD).

However as a case study or for whatever reason: You need to cache the repeating query results and later reuse them in 3 selects. This can be achieved by a (temp) table or MV or a plsql structure varray.

First two options covers Quassnoi in his answer so I won't mention them. The third one has disadvantage in having to state the maximum count of rows in advance (and I don't know right know what happens when you declare a varray with upper bound of 1M or 1G items even if you need only 1k).

--creating db object to hold the data - maximum of 1000 items allowed. 
--I assume that key is number(10,0).
create type t_address_keys is varray (1000) of number (10,0); 

declare 
  la_address_keys t_address_keys; --declare cache variable
begin

--cache keys
SELECT  address_key 
bulk collect into la_address_keys
        FROM    person_address
        WHERE   peson_key IN (person_list);

SELECT  *
into ...
FROM    Address
WHERE   address_key IN table(la_address_keys);

SELECT  *
into ...
FROM    Phone
WHERE   address_key IN table(la_address_keys);

SELECT  *
into ...
FROM    email
WHERE   address_key IN table(la_address_keys);

end;
/
Michal Pravda