Select individual ... inner join versus select ... where id in (...)
I am trying to create a subset of a table (as a materialized view) defined as those records that have a corresponding record in a different materialized view.
For example, let's say I have a Users table with user_id and name columns, and a log table with entry_id, user_id, activity, and timestamp columns.
First I create a materialized view of the log table by selecting only those rows with a timestamp> some_date. Now I want to look at Materliazed for the users mentioned in my snapshot of the Log table. I can create it like
select * from Users where user_id in (select user_id from Log_mview)
or i can do
select distinct u.* from Users u inner join Log_mview l on u.user_id = l.user_id
(needed to avoid multiple hits from users with multiple log entries).
The former seems cleaner and more elegant, but takes much longer. Am I missing something? Is there a better way to do this?
Edit: The suggestion where exists
helped a lot, except in the case where the condition uses OR
. For example, let's say the log table above also had a user_name column, and the correct way to map a log entry to a "Users" entry is when one of the columns (user id or username) matches. I find that
select distinct u.* from Users u
inner join Log_mview l
on u.user_id = l.user_id or u.name = l.user_name
much faster than
select * from Users u where exists
(select id from Log_mview l
where l.user_id = u.user_id or l.user_name = u.name)
Any help?
(As for the outline of the explanation ... Lemme is working on disinfecting them, or rather ... I'll post them in a while.)
Edit: explain plans: For an inner join query:
Plan hash value: 436698422
-------------------------------------------------- -------------------------------------------------- -----------
| Id | Operation | Name | Rows | Bytes | TempSpc | Cost (% CPU) | Time |
-------------------------------------------------- -------------------------------------------------- -----------
| 0 | SELECT STATEMENT | | 4539K | 606M | | 637K (3) | 02:07:25 |
| 1 | HASH UNIQUE | | 4539K | 606M | 3201M | 637K (3) | 02:07:25 |
| 2 | CONCATENATION | | | | | | |
| * 3 | HASH JOIN | | 4206K | 561M | 33M | 181K (4) | 00:36:14 |
| 4 | BITMAP CONVERSION TO ROWIDS | | 926K | 22M | | 2279 (1) | 00:00:28 |
| 5 | BITMAP INDEX FAST FULL SCAN | I_M_LOG_MVIEW_4 | | | | | |
| * 6 | TABLE ACCESS FULL | USERS | 15M | 1630M | | 86638 (6) | 00:17:20 |
| * 7 | HASH JOIN | | 7646K | 1020M | 33M | 231K (4) | 00:46:13 |
| 8 | BITMAP CONVERSION TO ROWIDS | | 926K | 22M | | 2279 (1) | 00:00:28 |
| 9 | BITMAP INDEX FAST FULL SCAN | I_M_LOG_MVIEW_4 | | | | | |
| 10 | TABLE ACCESS FULL | USERS | 23M | 2515M | | 87546 (7) | 00:17:31 |
-------------------------------------------------- -------------------------------------------------- -----------
Predicate Information (identified by operation id):
-------------------------------------------------- -
3 - access ("U". "NAME" = "L". "USER_NAME")
6 - filter ("U". "NAME" IS NOT NULL)
7 - access ("U". "USER_ID" = TO_NUMBER ("L". "USER_ID"))
filter (LNNVL ("U". "NAME" = "L". "USER_NAME") OR LNNVL ("U". "NAME" IS NOT NULL))
Note
-----
- dynamic sampling used for this statement
For those who use where exists
:
Plan hash value: 2786958565
-------------------------------------------------- -------------------------------------------------- -
| Id | Operation | Name | Rows | Bytes | Cost (% CPU) | Time |
-------------------------------------------------- -------------------------------------------------- -
| 0 | SELECT STATEMENT | | 1 | 114 | 21M (1) | 70:12:13 |
| * 1 | FILTER | | | | | |
| 2 | TABLE ACCESS FULL | USERS | 23M | 2515M | 87681 (7) | 00:17:33 |
| 3 | BITMAP CONVERSION TO ROWIDS | | 7062 | 179K | 1 (0) | 00:00:01 |
| * 4 | BITMAP INDEX FAST FULL SCAN | I_M_LOG_MVIEW_4 | | | | |
-------------------------------------------------- -------------------------------------------------- -
Predicate Information (identified by operation id):
-------------------------------------------------- -
1 - filter (EXISTS (SELECT / * + * / 0 FROM "MYSCHEMA". "LOG_MVIEW"
"LOG_MVIEW" WHERE ("USER_NAME" =: B1 OR TO_NUMBER ("USER_ID") =: B2) AND
("USER_NAME" =: B3 OR TO_NUMBER ("USER_ID") =: B4) AND ("USER_NAME" =: B5 OR
TO_NUMBER ("USER_ID") =: B6)))
4 - filter ("USER_NAME" =: B1 OR TO_NUMBER ("USER_ID") =: B2)
Note
-----
- dynamic sampling used for this statement
DB object names have been changed to protect the innocent .: P
a source to share
The second query probably works more hard disk than the first query (join + distinc).
The first request will probably translate to something like:
for each row in the Log table, find the corresponding row in the User table (in memory). The database is probably smart enough to create User tables in memory structures, which are probably much smaller than the log table. I believe the query one (join + distinct) will only require one pass in the Log table. The differences are likely to be done in memory.The second query is probably causing the database to do multiple filled spaces in the Log table.
So in your second request, you probably get:
For each row in the table, the user reads all the rows in the Log table (from disk) to match the condition.
You should also take into account that some queries can have drastic differences in speed due to changes in memory availability, table loading and growing.
a source to share