LEFT OUTER JOIN with XMLTABLE doesn't work? - oracle

Here is the sample query-
WITH empdata AS (SELECT xmltype ('<office>
<emp>
<empno>1</empno>
<ename>Abraham</ename>
<deptno>10</deptno>
</emp>
<emp>
<empno>2</empno>
<ename>Alexander</ename>
<deptno>10</deptno>
</emp>
<emp>
<empno>3</empno>
<ename>Benjamin</ename>
<deptno>20</deptno>
</emp>
<emp>
<empno>4</empno>
<ename>Bradley</ename>
<deptno>20</deptno>
</emp>
</office>') AS xcol FROM dual),
dept AS
(SELECT 10 deptno, 'Accounting' dname FROM dual
UNION ALL
SELECT 20, 'Broking' FROM dual
UNION ALL
SELECT 30, 'HR' FROM dual)
SELECT d.dname, e.ename, e.empno
FROM dept d
CROSS JOIN empdata e_data
LEFT OUTER JOIN
xmltable (
'office/emp'
PASSING e_data.xcol
COLUMNS deptno NUMBER (28, 0) PATH 'deptno',
ename VARCHAR2 (10) PATH 'ename',
empno NUMBER (28, 0) PATH 'empno') e
ON d.deptno = e.deptno;
Result I'm getting-
DNAME ENAME EMPNO
---------- ---------- ----------
Accounting Abraham 1
Accounting Alexander 2
Broking Benjamin 3
Broking Bradley 4
Why isn't the third row from dept, i.e. that of HR isn't showing in the result set? Ideally according to the rules of a LEFT JOIN all the records from the table in the left should show. Why is that one being filtered out?

You have the outer join and cross join the wrong way round. You need to cross-join empdata to xmltable as a subquery, and use that subquery (inline view) as the target for the outer join:
...
SELECT d.dname, e.ename, e.empno
FROM dept d
LEFT OUTER JOIN
(
SELECT x.*
FROM empdata e_data
CROSS JOIN
xmltable (
'office/emp'
PASSING e_data.xcol
COLUMNS deptno NUMBER (28, 0) PATH 'deptno',
ename VARCHAR2 (10) PATH 'ename',
empno NUMBER (28, 0) PATH 'empno') x
) e
ON d.deptno = e.deptno;
DNAME ENAME EMPNO
---------- ---------- ----------
Accounting Abraham 1
Accounting Alexander 2
Broking Benjamin 3
Broking Bradley 4
HR

Related

Oracle rewrite query using rank or dense_rank

I have the following query and sample data, which is working fine. It finds the max salary for employee(s) for each department.
Although it works, I like to change it to use rank or dense_rank (unsure how to do this) to achieve the output I GENERATED below but
Note department_id=1 has 2 different employees making the exact salary and I need to keep the result like that. In addition, is there a way I can only compare e.department_id = d.department_id once?
Any help would be greatly appreciated. Below is my test CASE and sample data.
CREATE table dept (department_id, department_name) AS
SELECT 1, 'IT' FROM DUAL UNION ALL
SELECT 2, 'SALES' FROM DUAL;
CREATE TABLE employees (employee_id, manager_id, first_name, last_name, department_id, sal,
serial_number) AS
SELECT 1, NULL, 'Alice', 'Abbot', 1, 100000, 'D123' FROM DUAL UNION ALL
SELECT 2, 1, 'Beryl', 'Baron',1, 50000,'D124' FROM DUAL UNION ALL
SELECT 3, 1, 'Carol', 'Chang',1, 100000, 'A1424' FROM DUAL UNION ALL
SELECT 4, 2, 'Debra', 'Dunbar',1, 75000, 'A1425' FROM DUAL UNION ALL
SELECT 5, NULL, 'Emily', 'Eden',2, 90000, 'C1725' FROM DUAL UNION ALL
SELECT 6, 3, 'Fiona', 'Finn',1, 88500,'C1726' FROM DUAL UNION ALL
SELECT 7,5, 'Grace', 'Gelfenbein',2, 55000, 'C1727' FROM DUAL;
select
e.employee_id,
e.first_name,
e.last_name,
e.department_id,
d.department_name,
e.sal
from employees e join dept d on e.department_id = d.department_id
where not exists
( select null
from employees
where department_id = e.department_id
and sal > e.sal );
EMPLOYEE_ID FIRST_NAME LAST_NAME DEPARTMENT_ID DEPARTMENT_NAME SAL
1 Alice Abbot 1 IT 100000
3 Carol Chang 1 IT 100000
5 Emily Eden 2 SALES 90000
You may use RANK as follows:
WITH cte AS (
SELECT e.employee_id, e.first_name, e.last_name, e.department_id,
d.department_name, e.sal,
RANK() OVER (PARTITION BY e.department_id ORDER BY e.sal DESC) rnk
FROM employees e
INNER JOIN dept d ON e.department_id = d.department_id
)
SELECT employee_id, first_name, last_name, department_id, department_name, sal
FROM cte
WHERE rnk = 1;

How to use a rowcount in select statement to modify the query to fetch data for 10 days , if rowcount is 0 for 5 days?

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 )

not a single-group group function when using max

I am trying to get out the max avrg using query below but I am getting wrror saying
ORA-00937: not a single-group group function
00937. 00000 - "not a single-group group function"
*Cause:
*Action: Error at Line: 1 Column:
SELECT B.STUDENT_ID,
A.FRIST_NAME,
A.FATHER_NAME,
A.LAST_NAME,
SUM (B.GRADE) AS SUM_GRADE,
COUNT(B.COURSE_ID) AS COURSE_COUNT,
max(SUM(B.GRADE) / COUNT(B.COURSE_ID)) AS AVRG
FROM STUDENT A,
STUDENT_COURSE B
WHERE A.STUDENT_ID = B.STUDENT_ID
GROUP BY A.FRIST_NAME, A.FATHER_NAME, A.LAST_NAME, B.STUDENT_ID;
this error gone when I remove the max function any one can help me why ?
I tried to use having maxbut I am getting error that says invalid renational
any way to use having with this query ?
One option is to use your current query (without MAX) as an inline view, and apply the MAX function to "sum/count":
SELECT student_id,
first_name,
father_name,
last_name,
sum_grade,
course_count,
-- this:
MAX(avrg) max_avrt
FROM (-- your current query
SELECT b.student_id,
a.frist_name,
a.father_name,
a.last_name,
SUM(b.grade) AS sum_grade,
COUNT(b.course_id) AS course_count,
SUM(b.grade) / COUNT(b.course_id) AS avrg
FROM student a,
student_course b
WHERE a.student_id = b.student_id
GROUP BY a.frist_name,
a.father_name,
a.last_name,
b.student_id
)
GROUP BY student_id, first_name, father_name, last_name, sum_grade, course_count;
However, you won't achieve anything good, as you'd still get the same record set due to outer GROUP BY clause. Consider using SUM in its analytic form.
Here's a simple example which shows what I mean, based on Scott's schema.
This is what you have now:
SQL> select deptno, sum(sal) / count(*) ssc
2 from emp
3 group by deptno
4 order by deptno;
DEPTNO SSC
---------- ----------
10 2916,66667
20 2258,33333
30 1566,66667
Apparently, you'd like to select the first SSC value (2916). If you apply what I wrote earlier (i.e. use that query as an inline view), you'd get this:
SQL> select deptno, max(ssc) max_ssc
2 from (select deptno, sum(sal) / count(*) ssc
3 from emp
4 group by deptno
5 )
6 group by deptno
7 order by deptno;
DEPTNO MAX_SSC
---------- ----------
10 2916,66667
20 2258,33333
30 1566,66667
SQL>
No improvement, eh? So, analytical function might be what you need:
SQL> select deptno,
2 max(sum(sal) / count(*)) over (order by deptno) max_ssc
3 from emp
4 group by deptno
5 order by deptno;
DEPTNO MAX_SSC
---------- ----------
10 2916,66667
20 2916,66667
30 2916,66667
This does return desired MAX value (if that's what you're looking for. If not, explain what you'd want to get as a result).

Display Data in own order by using union

I want to display
all emps who are seniors to king and who are juniors to smith as in following order.
who are seniors to king are under king header and juniors to smith are under smith header
I tried this one,
select ename from emp where hiredate<(select hiredate from emp where ename='king')
union
select ename from emp where hiredate>(select hiredate from emp where ename='smith');
OUTPUT is only one header(ENAME)
How can i get my desired output(Two Headers KING SMITH)
Can any one help me
Addding another artificial column is a little trick:
select 1 status, ename from emp
where hiredate<(select hiredate from emp where ename='king')
union all
select 2 status, ename from emp
where hiredate>(select hiredate from emp where ename='smith')
order by 1;
You can even use a union all instead of union since all lines will be different. Or, if you really need headers/separators:
select 0 status, 'KING''S SENIORS' from dual
union all
select 1 status, ename from emp where hiredate<(select hiredate from emp where ename='king')
union all
select 2 status, 'SMITH''S JUNIORS' from dual
union all
select 3 status, ename from emp where hiredate>(select hiredate from emp where ename='smith')
order by 1;
may be this one will be useful
select e.ename,
case when e.hiredate < khd.hiredate then 1 else 0 end king_header,
case when e.hiredate > shd.hiredate then 1 else 0 end smith_juniour
from emp e,
(select hiredate from emp where ename='king') khd,
(select hiredate from emp where ename='smith') shd
where e.hiredate < khd.hiredate or e.hiredate > shd.hiredate

Is this a right query? If it is what does it mean

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

Resources