Reputation: 1
Want to join table A to table B for specific types of rows in table B. If none of those types are in table B, then return the row in table A with null for columns in table B. Do not want null row, if a row with a type also returned.
My attempt to extrapolate the approach using RANK:
SELECT DISTINCT
A.a_id, A.emp, B.b_id, B.id_for_c, C.type, C.rnk
FROM A
JOIN B on B.b_id = A.a_id
Left JOIN ( SELECT b_id, C.type, id_for_c, C.c_id, rnk
FROM ( SELECT B.b_id, B.id_for_c, C.type, C.c_id,
RANK() OVER (PARTITION BY B.b_id
ORDER BY CASE WHEN C.type IS NOT NULL THEN 1 ELSE 2 END) AS rnk
FROM B
LEFT OUTER JOIN C
ON C.c_id = B.id_for_c
AND (C.type=1 or C.type=2)
) C
ORDER BY b_id, C.type, rnk) C
on C.c_id = B.id_for_c;
Data:
create table a (a_id number, emp number);
create table b (b_id number, id_for_c number);
create table c (c_id number, type number);
insert into a values (101, 1000);
insert into a values (102, 2000);
insert into a values (103, 3000);
insert into a values (104, 4000);
insert into b values (101, 25);
insert into b values (101, 75);
insert into b values (102, 50);
insert into b values (103, 75);
insert into b values (104, 25);
insert into b values (104, 75);
insert into c values (25, 1);
insert into c values (50, 2);
insert into c values (75, 3);
Upvotes: 0
Views: 131
Reputation: 191285
You can use an analytic rank()
function to favour non-null B values of null ones:
SELECT id, type
FROM (
SELECT A.id, B.type,
RANK() OVER (PARTITION BY A.id
ORDER BY CASE WHEN B.type IS NOT NULL THEN 1 ELSE 2 END) AS rnk
FROM A
LEFT OUTER JOIN B
ON B.id = A.id_for_b
AND (B.type=1 or b.type=2)
)
WHERE rnk = 1
ORDER BY id, type;
The inner query adds a ranking column which gives each matching result a ranking of either 1 or 2 (which you can see here. The outer query then eliminates those ranked 2; which will only be null values where a non-null value also exists.
As with Brian's answer, this only hits your real tables once.
If any A has multiple non-null values, i.e. with both types 1 and 2 (or no non-nulls, but multiple nulls) then they would all be shown; I'm not sure if either scenario is possible for you.
For your three-table variation I don't think you want to be joining to a subquery; certainly not back to the tables you've already worked from. Hopefully this gets the result you want:
SELECT a_id, b_id, id_for_c, type, c_id
FROM (
SELECT A.a_id, A.emp, B.b_id, B.id_for_c, C.type, C.c_id,
RANK() OVER (PARTITION BY B.b_id
ORDER BY CASE WHEN C.type IS NOT NULL THEN 1 ELSE 2 END) AS rnk
FROM A
LEFT OUTER JOIN B
ON B.b_id = A.a_id
LEFT OUTER JOIN C
ON C.c_id = B.id_for_c
AND (C.type=1 or C.type=2)
)
WHERE rnk = 1
ORDER BY a_id, b_id;
| A_ID | B_ID | ID_FOR_C | TYPE | C_ID |
|------|------|----------|--------|--------|
| 101 | 101 | 25 | 1 | 25 |
| 102 | 102 | 50 | 2 | 50 |
| 103 | 103 | 75 | (null) | (null) |
| 104 | 104 | 25 | 1 | 25 |
Upvotes: 0
Reputation: 13248
Try:
with sub as
(SELECT A.id, B.type
FROM A
LEFT OUTER JOIN B
ON B.id = A.id_for_b
and (B.type = 1 or b.type = 2))
select *
from sub
where not exists (select 1
from sub x
where x.id = sub.id
and x.type in (1, 2)
and sub.type is null)
Illustration:
http://sqlfiddle.com/#!4/174bd/1/0
(your existing query is the block in the with clause). I use an exists subquery to eliminate what you don't want.
Upvotes: 1