oracle left outer joins not showing right null values - oracle

i'm having an issue with creating a query in oracle which doesnt seem to want to join on missing values
the table i have is this:
table myTable(refnum, contid, type)
values are:
1, 10, 90000
2, 20, 90000
3, 30, 90000
4, 20, 10000
5, 30, 10000
6, 10, 20000
7, 20, 20000
8, 30, 20000
a break down of the fields i'm after is this:
select a.refnum from myTable a where type = 90000
select b.refnum from myTable b where type = 10000 and contid in (select contid from myTable where type = 90000)
select c.refnum from myTable c where type = 20000 and contid in (select contid from myTable where type = 90000)
the outcome of the query i'm after is this:
a.refnum, b.refnum, c.refnum
i thought this would work:
select a.refnum, b.refnum, c.refnum
from myTable a
left outer join myTable b on (a.contid = b.contid)
left outer join myTable c on (a.contid = c.contid)
where a.id_tp_cd = 90000
and b.id_tp_cd = 10000
and c.id_tp_cd = 20000
so the values should be:
1, null, 6
2, 4, 7
3, 5, 8
but its only returning:
2, 4, 7
3, 5, 8
i thought left joins would show all values in the left and create a null for the right.
help :(

You are correct in saying that left joins will return nulls for the right where there is no match, but you are not allowing these nulls to be returned when you add this restriction to your where clause:
and b.id_tp_cd = 10000
and c.id_tp_cd = 20000
You should be able to put these in the 'on' clause of the join instead, so only relevant rows on the right are returned.
select a.refnum, b.refnum, c.refnum
from myTable a
left outer join myTable b on (a.contid = b.contid and b.id_tp_cd = 10000)
left outer join myTable c on (a.contid = c.contid and c.id_tp_cd = 20000)
where a.id_tp_cd = 90000

Or using the Oracle syntax instead of ansi
select a.refnum, b.refnum, c.refnum
from myTable a, mytable b, mytable c
where a.contid=b.contid(+)
and a.contid=c.contid(+)
and a.type = 90000
and b.type(+) = 10000
and c.type(+) = 20000;
REFNUM REFNUM REFNUM
---------- ---------- ----------
1 6
2 4 7
3 5 8

Related

Multiple joins display student course name

I have the following setup, which is working perfectly. I am difficulty figuring out the syntax how to display the course name in the output. In my test CASE all the rows should have the value Geometry.
In addition, how could I use rank or rank_dense to limit the output to display only 1 row with the highest average?
CREATE TABLE students(student_id, first_name, last_name) AS
SELECT 1, 'Faith', 'Aaron' FROM dual UNION ALL
SELECT 2, 'Lisa', 'Saladino' FROM dual UNION ALL
SELECT 3, 'Leslee', 'Altman' FROM dual UNION ALL
SELECT 4, 'Patty', 'Kern' FROM dual UNION ALL
SELECT 5, 'Betty', 'Bowers' FROM dual;
CREATE TABLE courses(course_id, course_name) AS
SELECT 1, 'Geometry' FROM dual UNION ALL
SELECT 2, 'Trigonometry' FROM dual UNION ALL
SELECT 3, 'Calculus' FROM DUAL;
CREATE TABLE grades(student_id,
course_id, grade) AS
SELECT 1, 1, 75 FROM dual UNION ALL
SELECT 1, 1, 81 FROM dual UNION ALL
SELECT 1, 1, 76 FROM dual UNION ALL
SELECT 2, 1, 100 FROM dual UNION ALL
SELECT 2, 1, 95 FROM dual UNION ALL
SELECT 2, 1, 96 FROM dual UNION ALL
SELECT 3, 1, 80 FROM dual UNION ALL
SELECT 3, 1, 85 FROM dual UNION ALL
SELECT 3, 1, 86 FROM dual UNION ALL
SELECT 4, 1, 88 FROM dual UNION ALL
SELECT 4, 1, 85 FROM dual UNION ALL
SELECT 4, 1, 91 FROM dual UNION ALL
SELECT 5, 1, 98 FROM dual UNION ALL
SELECT 5, 1, 74 FROM dual UNION ALL
SELECT 5, 1, 81 FROM dual;
/* average grade of each student */
select s.student_id
, s.first_name
, s.last_name
, round(avg(g.grade), 1) as student_avg
from students s
join grades g
on s.student_id = g.student_id
group by s.student_id, s.first_name, s.last_name
ORDER BY avg(g.grade) DESC;
Something like this?
SQL> with temp as
2 (select s.student_id
3 , s.first_name
4 , s.last_name
5 , c.course_name
6 , round(avg(g.grade), 1) as student_avg
7 , rank() over (order by avg(g.grade) desc) rnk
8 from students s join grades g on s.student_id = g.student_id
9 join courses c on c.course_id = g.course_id
10 group by s.student_id, s.first_name, s.last_name, c.course_name
11 )
12 select student_id, first_name, last_name, course_name, student_avg
13 from temp
14 where rnk <= 3
15 order by rnk;
STUDENT_ID FIRST_ LAST_NAM COURSE_NAME STUDENT_AVG
---------- ------ -------- ------------ -----------
2 Lisa Saladino Geometry 97
4 Patty Kern Geometry 88
5 Betty Bowers Geometry 84.3
SQL>

values from table that not exist in another (by seperate group column)

This qes is about oracle db.
I have a table (T1) with several columns:
group
id
price
A
1
50
A
5
40
B
4
54
C
1
33
C
6
33
D
5
13
D
3
4
And another table (T2) with 2 columns:
id
description
1
aaa
2
bbb
3
ccc
4
ddd
5
eee
6
fff
7
ggg
The Id in this table (t2) is unique.
the connection between the two tables is by the "id" column.
I need to check for each (!) Group in T1 (A, B, C, D),
which of the "id" from T2 - not found for this group in T1,
and the result needs to be: group + id (that does not exist in this group)
for the example on the above tables, I expected to get this result:
group
id
A
2
A
3
A
4
A
6
A
7
B
1
B
2
B
3
B
5
B
6
B
7
C
2
C
3
C
4
C
5
C
7
D
1
D
2
D
4
D
6
D
7
Thank You!
In Oracle you may use partitioned join
with a(grp, id, price) as (
select 'A', 1, 50 from dual union all
select 'A', 5, 40 from dual union all
select 'B', 4, 54 from dual union all
select 'C', 1, 33 from dual union all
select 'C', 6, 33 from dual union all
select 'D', 5, 13 from dual union all
select 'D', 3, 4 from dual
)
, b (id, descr) as (
select 1, 'aaa' from dual union all
select 2, 'bbb' from dual union all
select 3, 'ccc' from dual union all
select 4, 'ddd' from dual union all
select 5, 'eee' from dual union all
select 6, 'fff' from dual union all
select 7, 'ggg' from dual
)
select
grp
, id
from a
partition by (grp)
right join b
using (id)
where a.price is null
order by grp, id
GRP | ID
:-- | -:
A | 2
A | 3
A | 4
A | 6
A | 7
B | 1
B | 2
B | 3
B | 5
B | 6
B | 7
C | 2
C | 3
C | 4
C | 5
C | 7
D | 1
D | 2
D | 4
D | 6
D | 7
db<>fiddle here
The idea here is to create all possible record combinations by using a Cartesian join and then match all groups and select non existing via left join and not null
create table T1 (c1 varchar2(10), c2 number(10), c3 number(10))
insert into T1 values('A', 1, 50);
insert into T1 values('A', 5, 40);
insert into T1 values('B', 4 , 54);
insert into T1 values('C', 1, 33);
insert into T1 values('C', 6, 33);
insert into T1 values('D', 5, 13);
insert into T1 values('D', 3, 4);
create table T2 (c6 number(10), c7 varchar2(10))
insert into T2 values( 1, 'aaa');
insert into T2 values( 2, 'bbb');
insert into T2 values( 3, 'ccc');
insert into T2 values( 4, 'ddd');
insert into T2 values( 5, 'eee');
insert into T2 values( 6, 'fff');
insert into T2 values( 7, 'ggg');
SELECT tx.c1, tx.c6, ty.c1
FROM
(select c1, c6
from
(select distinct c1 from T1) ta,
(select distinct c6 from T2) tb) tx -- tx is the cartisian product
left join
(select c1, c2 from T1 group by c1, c2) ty
on tx.c1 = ty.c1 and tx.c6 = ty.c2
WHERE
ty.c1 is null
ORDER BY 1, 2
This gets you exactly what you're looking for.
Tested and verified
First, find all the possible combinations, then determine which of them don't exist:
WITH cteCombinations
AS (SELECT DISTINCT T1."group", T2.ID
FROM T1
CROSS JOIN T2)
SELECT c."group", c.ID
FROM cteCombinations c
LEFT OUTER JOIN T1
ON T1.ID = c.ID AND
T1."group" = c."group"
WHERE T1.ID IS NULL
ORDER BY c."group", c.ID
The CTE (Common Table Expression) uses a CROSS JOIN to find all of the unique combinations of group and ID; then the LEFT OUTER JOIN is used to determine which of the combinations don't exist in T1.
Another way to do it is:
WITH cteCombinations
AS (SELECT DISTINCT T1."group", T2.ID
FROM T1
CROSS JOIN T2)
SELECT c."group", c.ID
FROM cteCombinations c
WHERE (c."group", c.ID) NOT IN (SELECT "group", ID
FROM T1)
ORDER BY c."group", c.ID
Here we use the same CTE to generate the possible combinations, but instead of a LEFT OUTER JOIN we use a NOT IN comparison to determine which of the combinations are not present in table T1.
db<>fiddle here

Oracle, If-else condition in where clause [duplicate]

I have a table, A and B which are shown below,
Table
A:
id
idB
name
faculty
B:
id
name
Table B has 2 records as below.
SELECT *
FROM B;
1, 1, 'First'
2, 2, 'Second'
Table A has 8 records as below.
SELECT *
FROM A;
1, 1, A, IT
2, 1, B, IT
3, 1, C, IT
4, 1, D, Medicine
5, 1, E, Medicine
6, 1, F, Business
7, 1, G, Business
8, 1, H, IT
9, 2, A, Medicine
10, 2, B, Medicine
11, 2, C, Medicine
12, 2, D, Medicine
13, 2, E, Medicine
14, 2, F, Medicine
15, 2, G, Business
16, 2, H, Medicine
My question is:
How can I select data from table B where faculty should be IT and if there are multiple it should get with max ID. AND if there is no any IT, it should be get business?
My select view should be look like this below:
A and B records.
8, 1, H, IT, First
15, 2, G, Business, Second
Please could you advise and help in which way we can retrieve these data?
this query will help you to get your desired result
SELECT id
,name
,faculty
FROM A
WHERE faculty IN ('IT', 'Business')
How can I select data from table B where faculty should be IT and if there are multiple it should get with max ID. AND if there is no any IT, it should be get business?
This will get the row with the maximum ID that is in IT and if there are no IT rows then Business
SELECT *
FROM (
SELECT A.id,
A.idB,
A.name,
A.faculty,
B.name AS bname
FROM A
INNER JOIN B
ON ( A.idB = B.id )
WHERE A.faculty IN ( 'IT', 'Business' )
ORDER BY
DECODE( A.faculty, 'IT', 1, 'Business', 2 ),
A.id DESC
)
WHERE ROWNUM = 1;
If you want the top row from each group then:
SELECT id,
idB,
name,
faculty,
bname
FROM (
SELECT A.id,
A.idB,
A.name,
A.faculty,
B.name AS bname,
ROW_NUMBER() OVER (
PARTITION BY A.faculty
ORDER BY A.id DESC
) AS rn
FROM A
INNER JOIN B
ON ( A.idB = B.id )
WHERE A.faculty IN ( 'IT', 'Business' )
)
WHERE rn = 1;
SELECT A.*,T.Name
FROM
TableA A
INNER JOIN
(
SELECT MAX(A.id) AS id,A.FACULTY,B.NAME,
ROW_NUMBER() OVER (PARTITION BY B.NAME ORDER BY MAX(A.id) DESC) AS RN FROM TableA A
INNER JOIN TableB B
ON A.idB=B.id
WHERE faculty IN ('IT', 'Business')
GROUP BY A.FACULTY,B.NAME
) T
ON A.id = T.id
AND A.FACULTY = T.FACULTY
WHERE T.RN=1
Output
ID IDB NAME FACULTY NAME
8 1 H IT First
15 2 G Business Second
Demo
http://sqlfiddle.com/#!4/98b84/24

How to find duplicates on two columns with distinct values in 3rd column

I have 3 columns a,b,c in table.i need to find the duplicates for the columns a & b but with distinct value in c column.
Maybe you need something like this:
with test(a, b, c) as (
select 1, 2, 10 from dual union all
select 1, 2, 20 from dual union all
select 4, 5, 30 from dual union all
select 4, 5, 30 from dual union all
select 3, 2, 3 from dual union all
select 6, 2, 2 from dual
)
select a, b
from test
group by a,b
having count(distinct c) > 1
That is, you need to aggregate for A,B, but only keeping pairs for which there are more DISTINCT values for column C

Faster way to calculate percentage?

I currently use this method to come up with a percentage:
declare #height table
(
UserId int,
tall bit
)
insert into #height
select 1, 1 union all
select 2, 1 union all
select 6, 0 union all
select 3, 0 union all
select 7, 0 union all
select 4, 1 union all
select 8, 0 union all
select 5, 0
declare #all decimal(8,5)
select
#all = count(distinct UserId)
from #height
select
count(distinct UserId) / #all Pct
from #height
where tall = 1
Result: 0.375000000
Is there a better performing way to do this? As you can see the #height
table is hit twice.
Thanks!
This allows you to hit the table only once, and gives you the same result for your given dataset.
declare #height table
(
UserId int,
tall bit
)
insert into #height
select 1, 1 union all
select 2, 1 union all
select 6, 0 union all
select 3, 0 union all
select 7, 0 union all
select 4, 1 union all
select 8, 0 union all
select 5, 0
select SUM(convert(decimal(8,5), tall)) / convert(decimal(8,5), COUNT(*)) Pct
from #height
Depending on your requirements, this might work for duplicate userids. At least it gives the same result as yours does.
select SUM(convert(decimal(8,5), tall)) / convert(decimal(8,5), COUNT(distinct userid)) Pct
from
(select distinct UserId, tall
from #height) t
Here is an alternative query that produces your expected results. I don't know how the performance of this query compares to others, but I suspect it would be easy enough for you to test this.
declare #height table
(
UserId int,
tall bit
)
insert into #height
select 1, 1 union all
select 2, 1 union all
select 4, 1 union all
select 3, 0 union all
select 5, 0 union all
select 6, 0 union all
select 7, 0 union all
select 8, 0
Select 1.0 * Count(Distinct Case When Tall = 1 Then UserId End)
/ Count(Distinct UserId)
From #height

Resources