I would like to compare two SQL statements which produce the same result. For example
SELECT
id, name,
(SELECT street || ' ' || town from addresses WHERE addresses.id=customers.addressid) AS address
FROM customers;
-- vs
SELECT
id, name,
street || ' ' || town AS address
FROM customers c LEFT JOIN addresses a ON c.id=a.customerid;
Here I want to compare the performance of the subquery compared to using a join. I know about the other advantages and disadvantages of subqueries and joins, but I’m more interested in comparing their performance.
How can I do a performance comparison? Is there some sort of timing test?
Is there some sort of timing test?
Yes, in SQL*Plus, do it as
SQL> set timing on
and then run queries, each of them several times (to avoid caching issues) and compare time needed for each query to complete. Note that on small data sets you won't notice any difference, so - whichever you use, it'll be OK.
SQL> select e.deptno, (select d.dname from dept d where d.deptno = e.deptno) dname, e.ename
2 from emp e
3 where e.deptno = 20;
DEPTNO DNAME ENAME
---------- -------------- ----------
20 RESEARCH SMITH
20 RESEARCH JONES
20 RESEARCH SCOTT
20 RESEARCH ADAMS
20 RESEARCH FORD
Elapsed: 00:00:00.04
SQL> select e.deptno, d.dname, e.ename
2 from emp e join dept d on e.deptno = d.deptno
3 where e.deptno = 20;
DEPTNO DNAME ENAME
---------- -------------- ----------
20 RESEARCH SMITH
20 RESEARCH JONES
20 RESEARCH SCOTT
20 RESEARCH ADAMS
20 RESEARCH FORD
Elapsed: 00:00:00.03
SQL>
Other than that, did you compare explain plans? Did you collect statistics on tables and indexes (and do it regularly)? Are there any indexes (should be on columns used in joins, most probably)?
Related
I have tried the following query but I still get this error and I am not able to find a solution to resolve it. I want to be able to get one of the three dates (LOAD,LAST_MODIFIED,ORDER_ITEM_LAST_UPDATE) according to the situation. However, I need also to use the actual calendar date to use a specific column inside. It is why I do the join (+) with the LOAD_DATE. Now, I try to find a way not to get the error ORA-01719, Someone can help me?
ORA-01719: outer join operator (+) not allowed in operand of OR or IN
01719. 00000 - "outer join operator (+) not allowed in operand of OR or IN"
*Cause: An outer join appears in an or clause.
*Action: If A and B are predicates, to get the effect of (A(+) or B),
try (select where (A(+) and not B)) union all (select where (B)).
Error at Line: 17 Column: 35
CODE:
SELECT A.PRODUCT_ID AS KEY,
TO_CHAR(A.ORDER_DATE, 'MM/DD/YYYY') AS ORDER_CREATION_DATE,
TO_CHAR(B.LOAD_DATE, 'MM/DD/YYYY') AS LOAD_DATE,
TO_CHAR(B.LAST_MODIFIED_DATE, 'MM/DD/YYYY') AS LAST_MODIFIED_DATE,
TO_CHAR(B.ORDER_ITEM_LAST_UPDATED, 'MM/DD/YYYY') AS ORDER_ITEM_LAST_UPDATED,
A.ORDER_STATUS AS ORDER_STATUS,
A.ORDER_ITEM_STATUS AS ORDER_ITEM_STATUS,
A.ORDER_ACTION AS ORDER_ACTION,
NVL(B.ORDER_ITEM_ACTION, 'NOT APPLICABLE') AS ORDER_ITEM_ACTION,
NVL(B.CEASE_REASON, 'NOT APPLICABLE') AS ORDER_CEASE_REASON,
A.PRO_SYSTEM AS PRO_SYSTEM,
CASE WHEN (A.PRO_SYSTEM = 'X') THEN 'Y' ELSE 'Z' END AS FLOW
FROM PART1 A, PART2 B,DATA_REP_CALENDAR CAL
WHERE A.ORDER_SEQ = B.ORDER_SEQ
AND TRUNC(B.LOAD_DATE) = CAL.ACTUAL_CALENDAR_DATE (+)
AND TRUNC(B.LOAD_DATE) BETWEEN ADD_MONTHS( TO_DATE('07/01/2019', 'MM/DD/YYYY'),- 6) and LAST_DAY( TO_DATE('07/01/2019', 'MM/DD/YYYY'))
OR TRUNC(B.LAST_MODIFIED_DATE) BETWEEN ADD_MONTHS( TO_DATE('07/01/2019', 'MM/DD/YYYY'),- 6) and LAST_DAY( TO_DATE('07/01/2019', 'MM/DD/YYYY'))
OR TRUNC(B.ORDER_ITEM_LAST_UPDATED) BETWEEN ADD_MONTHS( TO_DATE('07/01/2019', 'MM/DD/YYYY'),- 6) and LAST_DAY( TO_DATE('07/01/2019', 'MM/DD/YYYY'));
A simple example based on Scott's EMP and DEPT tables; there's department 40, and nobody works in it. If you want to display it (while joining those tables), use outer join. The "old" Oracle outer join operator is (+) which has its ... drawbacks. If you use outer join, many things (that won't work with (+)) will now work.
This is what you have now:
SQL> select d.deptno, d.dname, e.ename
2 from dept d,
3 emp e
4 where d.deptno = e.deptno (+)
5 order by d.deptno, e.ename;
DEPTNO DNAME ENAME
---------- -------------- ----------
10 ACCOUNTING CLARK
10 ACCOUNTING KING
10 ACCOUNTING MILLER
20 RESEARCH ADAMS
20 RESEARCH FORD
20 RESEARCH JONES
20 RESEARCH SCOTT
20 RESEARCH SMITH
30 SALES ALLEN
30 SALES BLAKE
30 SALES JAMES
30 SALES MARTIN
30 SALES TURNER
30 SALES WARD
40 OPERATIONS
15 rows selected.
SQL>
This is how you should rewrite it:
SQL> select d.deptno, d.dname, e.ename
2 from dept d left join emp e on e.deptno = d.deptno
3 order by d.deptno, e.ename;
DEPTNO DNAME ENAME
---------- -------------- ----------
10 ACCOUNTING CLARK
10 ACCOUNTING KING
10 ACCOUNTING MILLER
20 RESEARCH ADAMS
20 RESEARCH FORD
20 RESEARCH JONES
20 RESEARCH SCOTT
20 RESEARCH SMITH
30 SALES ALLEN
30 SALES BLAKE
30 SALES JAMES
30 SALES MARTIN
30 SALES TURNER
30 SALES WARD
40 OPERATIONS
15 rows selected.
SQL>
I need to modify my script using rowcount to check if the data in table or not?. Here, i write the query to select a data for last 5 days from current system date. But sometimes there is no data in table for 5 days. So i need to fetch for 10 day or more.
Query:
Select ep.ENTERPRISE_NAME||'|'||s.id||'|'||s.SUBMISSION_DATE||'|'||E.VALUE
from JOB_SUMMARY_EXT e, ob_summary s, enterprise ep
where e.id = s.id and e.name_res_key = 'Model'
and s.job_id in (select id from job_summary where
trunc(start_date) > trunc(sysdate) -10 and service_name ='Model2' )
I don't know how to modify my Query using rowcount. If rowcount is 0 then i want select data for 10 days.Otherwise it should to fetch for 5 days automatically. I want this to be done as single query.
It looks that you want to select the last 5 "days" from that table. So, why would you anchor to SYSDATE if there aren't rows for each of those days? I'd suggest another approach: literally, select last 5 days. Here's how.
As I don't have your tables, I'm using Scott's EMP table which contains information about employees. It is an ancient one so HIREDATE column is set to 1980s, but never mind that. Sorting employees by HIREDATE in descending order shows:
SQL> alter session set nls_date_format = 'dd.mm.yyyy';
Session altered.
SQL> select ename, hiredate from emp order by hiredate desc;
ENAME HIREDATE
---------- ----------
ADAMS 12.01.1983 1.
SCOTT 09.12.1982 2.
MILLER 23.01.1982 3.
FORD 03.12.1981 4.
JAMES 03.12.1981 4.
KING 17.11.1981 5. --> I want to fetch rows up to KING
MARTIN 28.09.1981
TURNER 08.09.1981
CLARK 09.06.1981
BLAKE 01.05.1981
JONES 02.04.1981
WARD 22.02.1981
ALLEN 20.02.1981
SMITH 17.12.1980
14 rows selected.
SQL>
As you can see, the 4th date is shared by two employees so I want to include them both. DENSE_RANK analytic function helps:
SQL> with last5 as
2 (select ename,
3 job,
4 sal,
5 hiredate,
6 dense_rank() over (order by hiredate desc) rnk
7 from emp
8 )
9 select ename, job, sal, hiredate
10 from last5
11 where rnk <= 5;
ENAME JOB SAL HIREDATE
---------- --------- ---------- ----------
ADAMS CLERK 1100 12.01.1983
SCOTT ANALYST 3000 09.12.1982
MILLER CLERK 1300 23.01.1982
JAMES CLERK 950 03.12.1981
FORD ANALYST 3000 03.12.1981
KING PRESIDENT 5000 17.11.1981
6 rows selected.
SQL>
What does it do? The LAST5 CTE sorts employees (as above), DENSE_RANK ranks them; finally, the last SELECT (which begins at line #9) fetches desired rows.
In your case, that might look like this:
with last5 as
(select id,
dense_rank() over (order by start_date desc) rnk
from job_summary
where service_name = 'Model2'
)
select ep.enterprise_name,
s.id,
s.submission_date,
e.value
from job_summary_ext e
join ob_summary s on e.id = s.id
join last5 t on t.id = s.id
join enterprise ep on <you're missing join condition for this table>
where e.name_res_key = 'Model';
Note that you're missing join condition for the ENTERPRISE table; if that's really so, no problem - you'd use cross join for that table, but I somehow doubt that you want that.
Finally, as you use SQL*Plus, perhaps you don't need to concatenate all columns and separate them by the pipe | sign - set it as a column separator, e.g.
SQL> set colsep '|'
SQL>
SQL> select deptno, dname, loc from dept;
DEPTNO|DNAME |LOC
----------|--------------|-------------
10|ACCOUNTING |NEW YORK
20|RESEARCH |DALLAS
30|SALES |CHICAGO
40|OPERATIONS |BOSTON
SQL>
If you want to
return 10 last days if select count(*) returns 0, or
return 5 last days if select count(*) returns a positive number
then something like this might help (again based on Scott's EMP table):
with
tcnt as
-- count number of rows; use your own requirement, I'm checking
-- whether someone got hired today. In Scott's EMP table, nobody was
-- so CNT = 0
(select count(*) cnt
from emp
where hiredate >= trunc(sysdate)
)
select e.ename, e.job, e.sal, e.hiredate
from emp e cross join tcnt c
where e.hiredate >= case when c.cnt = 0 then trunc(sysdate) - 10
else trunc(sysdate) - 5
end;
Apply it to your tables; I don't know which of those 3 tables' count you want to check.
Tried to add in comments but it was too long for comments and Not clear on count based on but here is case in where clause substitute your count statement with nvl function
SELECT ep.ENTERPRISE_NAME||'|'||s.id||'|'||s.SUBMISSION_DATE||'|'||E.VALUE
FROM JOB_SUMMARY_EXT e,
ob_summary s,
enterprise ep
WHERE e.id = s.id
AND e.name_res_key = 'Model'
AND s.job_id IN
(SELECT id
FROM job_summary
WHERE service='Model'
AND trunc(start_date) >
CASE WHEN
(WRITE your SELECT COUNT criteria WITH NVL FUNCTION)<=0 THEN
trunc(sysdate) -10
ELSE trunc(sysdate)-5
END )
I tried to concat two columns and using group by expression, but it is not work. how can I grouped multiple columns in oracle. first_name and last_name are reduction data.
SELECT employee_id,
employee_name,
employee_unique
FROM
(SELECT a.id AS employee_id,
(a.first_name
|| a.last_name) AS employee_name,
b.employee_unique
FROM A a
INNER JOIN b
ON a.id=b.employee_id
GROUP BY a.id,
b.employee_unique,
(a.first_name
|| a.last_name)
);
What does "not work" mean? It works for me (though, as I don't have your tables, I used Scott's EMP and DEPT, but everything else is more or less the same). If it isn't correct, you should explain what is wrong with it. If you want us to work with your data, please, provide CREATE TABLE and INSERT INTO sample data.
SQL> select employee_id,
2 employee_name,
3 employee_unique
4 from
5 (select a.empno as employee_id,
6 a.ename || a.job as employee_name,
7 b.dname as employee_unique
8 from emp a
9 inner join dept b
10 on a.deptno=b.deptno
11 group by a.empno,
12 b.dname,
13 a.ename || a.job
14 );
EMPLOYEE_ID EMPLOYEE_NAME EMPLOYEE_UNIQU
----------- ------------------- --------------
7654 MARTINSALESMAN SALES
7876 ADAMSCLERK RESEARCH
7566 JONESMANAGER RESEARCH
7698 BLAKEMANAGER SALES
7844 TURNERSALESMAN SALES
7369 SMITHCLERK RESEARCH
7788 SCOTTANALYST RESEARCH
7900 JAMESCLERK SALES
7902 FORDANALYST RESEARCH
7782 CLARKMANAGER ACCOUNTING
7934 MILLERCLERK ACCOUNTING
7499 ALLENSALESMAN SALES
7521 WARDSALESMAN SALES
7839 KINGPRESIDENT ACCOUNTING
14 rows selected.
SQL>
Though, as there's nothing really to be grouped (no aggregation here), you could have used distinct (without group by clause) and get the same result:
select employee_id,
employee_name,
employee_unique
from
(select distinct
a.empno as employee_id,
a.ename || a.job as employee_name,
b.dname as employee_unique
from emp a
inner join dept b
on a.deptno=b.deptno
);
I have two tables in my database they are EXP1 and EXP2. I tried with the below query, this query is working when both the tables have same number of columns but my table EXP1 has 1000 columns n EXP2 has 1000+4.
select *
from
(
(select * from exp1
minus
select * from exp2)
union all
(select * from exp2
minus
select * from exp1)
);
INTRO: Below I show how one can do "by hand" what the tools (SQL Developer for example) can do much faster and much better. My interest in this (and yours!) is two-fold: learn and use some ideas that can help in many other problems; and understand what those tools do under the hood in the first place.
OK. Suppose you have two tables, and they have many columns in common (possibly not in the same order) and a few columns may be different - there may be a handful of columns in one table but not in the other. First you want to be able to look just at the common columns.
Then, suppose that's done. Now what's left of the two tables has many rows in common, but there are a few that are different. A row may exist in one table but not in the other, or two rows, one from each table, may be very similar but they may differ in just one or a small number of column values. Logically these are still one row in the first table but not the second, and the other row only in the second table but not in the first. However, let's say both tables have the same PK column - then you may have the same PK value in both tables, but at least one of the OTHER columns has different values for that PK value in the two tables. And, you want to find these differences between the two tables.
In what follows I will assume that if two columns, in the two tables, have the same name, they will also have the same data type. If that is not guaranteed in your case, it can be fixed with a little more work in the part where I identify the "common columns" - instead of matching them just by name, from the catalog views, they would have to be matched also by data type.
When you get to comparing rows in the two tables in the final step, (A minus B) union all (B minus A) works, but is not very efficient. Each table is read twice, and minus is an expensive operator. The more efficient solution, which I illustrate below, was discussed in a long thread on AskTom several years ago. Namely: collect all the rows from both tables (with union all), group by all the columns, and disregard the groups that have a count of 2. This means rows that were found in both tables, so they are duplicates in the union all! Actually, you will see a small additional trick to identify from which table the "non-duplicated" rows come. Add a column for "table_name" and in the final select, after grouping and keeping the groups with count(*) = 1, select max(table_name). You need an aggregate function (like max()) because you are grouping, but for these rows each group only has one row, so the max() is really just the table name.
The beauty of this approach is that it can be used to identify the common columns, too! In that case, we will compare rows from the USER_TAB_COLS view - we select column names that appear in either of the tables, and keep only the column names that are duplicates (so the column names appear in both tables). In that part of the solution, I also retrieve column_id, which is used to order the columns. Don't worry if you are not familiar with keep (dense_rank first...) - it's not really that complicated, but it's not that important either.
First let's set up a test case. I copy the EMP table from the SCOTT schema to my own schema, I replicate it (so now I have two copies, named EMP1 and EMP2), and I modify them slightly. I delete a different column from each, I delete a few (different) rows from each, and I modify one salary in one table. I will not show the resulting (slightly different) tables, but if you are following along, just select * from both and compare them before you continue reading.
Create the tables:
create table EMP1 as select * from scott.emp;
Table EMP1 created.
select * from EMP1;
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
----- ---------- --------- ---- ------------------- ----- ------ -------
7369 SMITH CLERK 7902 1980-12-17 00:00:00 800 20
7499 ALLEN SALESMAN 7698 1981-02-20 00:00:00 1600 300 30
7521 WARD SALESMAN 7698 1981-02-22 00:00:00 1250 500 30
7566 JONES MANAGER 7839 1981-04-02 00:00:00 2975 20
7654 MARTIN SALESMAN 7698 1981-09-28 00:00:00 1250 1400 30
7698 BLAKE MANAGER 7839 1981-05-01 00:00:00 2850 30
7782 CLARK MANAGER 7839 1981-06-09 00:00:00 2450 10
7788 SCOTT ANALYST 7566 1987-04-19 00:00:00 3000 20
7839 KING PRESIDENT 1981-11-17 00:00:00 5000 10
7844 TURNER SALESMAN 7698 1981-09-08 00:00:00 1500 0 30
7876 ADAMS CLERK 7788 1987-05-23 00:00:00 1100 20
7900 JAMES CLERK 7698 1981-12-03 00:00:00 950 30
7902 FORD ANALYST 7566 1981-12-03 00:00:00 3000 20
7934 MILLER CLERK 7782 1982-01-23 00:00:00 1300 10
Modify them slightly:
create table EMP2 as select * from EMP1;
Table EMP2 created.
alter table emp1 drop column hiredate;
Table EMP1 altered.
alter table emp2 drop column comm;
Table EMP2 altered.
delete from EMP1 where ename like 'A%';
2 rows deleted;
delete from EMP2 where sal >= 3000;
3 rows deleted
update EMP2 set sal = 2950 where empno = 7698;
1 row updated
commit;
At this point you would do well to select * from EMP1; and select * from EMP2; and compare.
Now let's find out what columns the two tables have left in common.
select column_name,
min(column_id) keep(dense_rank first order by table_name) as col_id
from user_tab_cols
where table_name in ('EMP1', 'EMP2')
group by column_name
having count(*) = 2
order by col_id;
COLUMN_NAME COL_ID
----------- ------
EMPNO 1
ENAME 2
JOB 3
MGR 4
SAL 5
DEPTNO 7
6 rows selected
Perfect, so now we can compare the two tables, but only after we "project" them along the common columns only.
select max(table_name) as table_name, EMPNO, ENAME, JOB, MGR, SAL, DEPTNO
from (
select 'EMP1' as table_name, EMPNO, ENAME, JOB, MGR, SAL, DEPTNO from EMP1
union all
select 'EMP2' as table_name, EMPNO, ENAME, JOB, MGR, SAL, DEPTNO from EMP2
)
group by EMPNO, ENAME, JOB, MGR, SAL, DEPTNO
having count(*) = 1
order by EMPNO, ENAME, JOB, MGR, SAL, DEPTNO, table_name;
TABLE_NAME EMPNO ENAME JOB MGR SAL DEPTNO
---------- ----- ---------- --------- ------ ------ --------
EMP2 7499 ALLEN SALESMAN 7698 1600 30
EMP1 7698 BLAKE MANAGER 7839 2850 30
EMP2 7698 BLAKE MANAGER 7839 2950 30
EMP1 7788 SCOTT ANALYST 7566 3000 20
EMP1 7839 KING PRESIDENT 5000 10
EMP2 7876 ADAMS CLERK 7788 1100 20
EMP1 7902 FORD ANALYST 7566 3000 20
7 rows selected
The output is pretty much what we needed. Notice the first column, which tells us where the "unpaired" row comes from; and note BLAKE, who has different salary in the two tables (and the first column helps us to see what salary he has in which table).
This looks perfect so far, but what to do when you have 1000 columns? You could put it together in C or Java etc., using the result from the "common columns" query above - or you could do it all in Oracle, with dynamic SQL.
As far as I know, there is no set limit on the length of the text of an SQL statement in Oracle; the documentation says "The limit on how long a SQL statement can be depends on many factors, including database configuration, disk space, and memory" (and probably on your Oracle version, which they didn't mention). In any case, it will be more than 4000 characters, so we need to work with CLOB. In particular, we can't use listagg() - we need a workaround. I use xmlagg() below. Then, the documentation says if you concatenate text and at least one operand is CLOB the result will be CLOB; if that doesn't work for you, you may have to wrap the smaller text fragments within to_clob(). The "dynamic SQL" query below will produce the full text of the query I used above; you will simply copy it and paste it back into your front-end and execute it. You may have to delete wrapping double-quotes or such, depending on your front-end and settings.
First here is how we can create a (potentially very long) string, the list of common column names, which is repeated five times in the final query - just look again at the "final query" we used to compare the two tables above.
with
common_cols ( column_name, col_id ) as (
select column_name,
min(column_id) keep(dense_rank first order by table_name) as col_id
from user_tab_cols
where table_name in ('EMP1', 'EMP2')
group by column_name
having count(*) = 2
),
col_string ( str ) as (
select rtrim(xmlcast(xmlagg(xmlelement(e, column_name, ', ') order by col_id)
as clob), ', ') from common_cols
)
select * from col_string;
STR
-----------------------------------
EMPNO, ENAME, JOB, MGR, SAL, DEPTNO
And finally the full dynamic SQL query (the result is exactly the query I used to compare EMP1 and EMP2 on their common columns earlier):
with
common_cols ( column_name, col_id ) as (
select column_name,
min(column_id) keep(dense_rank first order by table_name) as col_id
from user_tab_cols
where table_name in ('EMP1', 'EMP2')
group by column_name
having count(*) = 2
),
col_string ( str ) as (
select rtrim(xmlcast(xmlagg(xmlelement(e, column_name, ', ') order by col_id)
as clob), ', ') from common_cols
)
select 'select max(table_name) as table_name, ' || str || chr(10) ||
'from (' || chr(10) ||
' select ''EMP1'' as table_name, ' || str || ' from EMP1' || chr(10) ||
' union all' || chr(10) ||
' select ''EMP2'' as table_name, ' || str || ' from EMP2' || chr(10) ||
' )' || chr(10) ||
'group by ' || str || chr(10) ||
'having count(*) = 1' || chr(10) ||
'order by ' || str || ', table_name;' as comp_sql_str
from col_string;
I was given a query to explain. Could someone please explain it to me:
select j.ip_num from
jobs j, address a
where j.jobtype='C' and
a.sel_code(+)='H' and
j.ip_num=a.ip_num and
a.ip_num is null order by a.ip_num
That query selects every JOB.IP_NUM which doesn't have a matching ADDRESS record or where the matching ADDRESS record has a SEL_CODE not equal to 'H'.
The (+) is Oracle's old outer join syntax. It is the only OUTER JOIN syntax supported in versions of Oracle before 9i.
In this query we get one row for every row in EMP which matches a department, plus a row for the DEPTNO=40, which has no employees:
SQL> select d.dname
2 , e.ename
3 from dept d
4 , emp e
5 where d.deptno = e.deptno(+)
6 /
DNAME ENAME
-------------- ----------
ACCOUNTING SCHNEIDER
ACCOUNTING BOEHMER
ACCOUNTING KISHORE
RESEARCH ROBERTSON
RESEARCH KULASH
RESEARCH GASPAROTTO
RESEARCH RIGBY
RESEARCH CLARKE
SALES HALL
SALES CAVE
SALES SPENCER
SALES BILLINGTON
SALES PADFIELD
SALES VAN WIJK
SALES KESTELYN
SALES LIRA
OPERATIONS PSMITH
HOUSEKEEPING VERREYNNE
HOUSEKEEPING FEUERSTEIN
HOUSEKEEPING PODER
HOUSEKEEPING TRICHLER
COMMUNICATIONS
22 rows selected.
SQL>
Now, if we put an additional filter on the EMP table like this, we simply get one record for each Department, because only one record in EMP now matches:
SQL> select d.dname
2 , e.ename
3 from dept d
4 , emp e
5 where d.deptno = e.deptno(+)
6 and e.ename(+) = 'CAVE'
7 /
DNAME ENAME
-------------- ----------
ACCOUNTING
RESEARCH
SALES CAVE
OPERATIONS
HOUSEKEEPING
COMMUNICATIONS
6 rows selected.
SQL>
/
To convert this query into the ANSI SQL syntax we have to do this:
SQL> select d.dname
2 , e.ename
3 from dept d
4 left outer join emp e
5 on ( d.deptno = e.deptno
6 and e.ename = 'CAVE' )
7 /
DNAME ENAME
-------------- ----------
ACCOUNTING
RESEARCH
SALES CAVE
OPERATIONS
HOUSEKEEPING
COMMUNICATIONS
6 rows selected.
SQL>
Note that if we don't include the additonal clause in the JOIN but leave it in the WHERE clause we get a different result:
SQL> select d.dname
2 , e.ename
3 from dept d
4 left outer join emp e
5 on ( d.deptno = e.deptno )
6 where e.ename = 'CAVE'
7 /
DNAME ENAME
-------------- ----------
SALES CAVE
SQL>
This is the equivalent of omitting the (+) in the second old skool query.
The query is joining the 2 tables jobs, and address. These tables are joining on the field ip_num but you are looking for the records that exist in the jobs table but do not exist in the address table.
This is a LEFT OUTER JOIN. This query could also be written
SELECT j.ip_num
FROM jobs j
LEFT OUTER JOIN address a
ON j.ip_num=a.ip_num
WHERE j.jobtype='C' AND
a.sel_code(+)='H' AND
a.ip_num is null
ORDER BY a.ip_num
It might be useful to see a visual picture joins http://www.codinghorror.com/blog/2007/10/a-visual-explanation-of-sql-joins.html