Can I apply a WHERE clause if COUNT condition is met? - oracle

I'm having a hard time trying to add a WHERE clause that filters null values from a column ONLY when there are other rows that return data for that same column.
If all rows have null values for that column, keep them all.
If any row has data for that column, remove the rows with null values and just keep the rows with data.
I'm working on an Oracle database.
In my SELECT statement, I'm currently using a LEFT JOIN to pull data from table B even if the values for column B.info are null.
The actual query goes as follows:
SELECT A.id as A_ID, A.name as A_NAME,
B.id as B_ID, B.name as B_NAME, B.info as B_INFO
FROM A
LEFT JOIN B ON B.id = A.id_B
WHERE A.filename = 'file1.txt'
I have 2 possible scenarios in the bussiness I'm working on:
For a given "filename", the query returns some rows with the B.info column with null values and some others with the B.info column filled with data. I want the query to return only the rows with B.info != null.
Scenario 1 - Actual output:
+-------+--------+------+--------+-----------+
| A_ID | A_NAME | B_ID | B_NAME | B_INFO |
+-------+--------+------+--------+-----------+
| 1 | John | null | null | null |
+-------+--------+------+--------+-----------+
| 2 | John | 3 | Julia | Age is 35 |
+-------+--------+------+--------+-----------+
| 3 | John | null | null | null |
+-------+--------+------+--------+-----------+
Scenario 1 - Desired output:
+-------+--------+------+--------+-----------+
| A_ID | A_NAME | B_ID | B_NAME | B_INFO |
+-------+--------+------+--------+-----------+
| 2 | John | 3 | Julia | Age is 35 |
+-------+--------+------+--------+-----------+
For a given "filename", the query returns all of the rows with the B.info column with null values.
I want the query to keep returning those rows.
Scenario 2 - Actual output = desired output:
+-------+--------+------+--------+--------+
| A_ID | A_NAME | B_ID | B_NAME | B_INFO |
+-------+--------+------+--------+--------+
| 1 | Mark | null | null | null |
+-------+--------+------+--------+--------+
| 2 | Mark | null | null | null |
+-------+--------+------+--------+--------+
| 3 | Mark | null | null | null |
+-------+--------+------+--------+--------+
I tried adding the condition B.info is not null in the where clause but, although it returns the desired output for the scenario 1, the output for the scenario 2 returns no rows:
SELECT A.id as A_ID, A.name as A_NAME,
B.id as B_ID, B.name as B_NAME, B.info as B_INFO
FROM A
LEFT JOIN B ON B.id = A.id_B
WHERE A.filename = 'file1.txt'
AND B.info is not null
Scenario 1 - Output
+-------+--------+------+--------+-----------+
| A_ID | A_NAME | B_ID | B_NAME | B_INFO |
+-------+--------+------+--------+-----------+
| 2 | John | 3 | Julia | Age is 35 |
+-------+--------+------+--------+-----------+
Scenario 2 - Output
+-------+--------+------+--------+--------+
| A_ID | A_NAME | B_ID | B_NAME | B_INFO |
+-------+--------+------+--------+--------+
+-------+--------+------+--------+--------+
I also tried adding a CASE in the WHERE clause but it throws an error (ORA-00934: group function is not allowed here)
SELECT A.id as A_ID, A.name as A_NAME,
B.id as B_ID, B.name as B_NAME, B.info as B_INFO
FROM A
LEFT JOIN B ON B.id = A.id_B
WHERE A.filename = 'file1.txt'
AND B.info = CASE WHEN count(B.info) > 0 THEN null
ELSE B.info
END
I'm sorry I can't use the real example for confidentiality issues. I hope my example is clear enough. I would appreciate any help!

Count all rows and nullable rows. Use analytic count, because you need details. Then show only rows containing data or null rows if both counts are equal:
select id, a_name, b_name, info
from (
select a.id, b.id b_id, a.name a_name, b.name b_name, b.info,
count(case when b.id is null then 1 end) over (partition by a.filename) c1,
count(1) over (partition by a.filename) c2
from a left join b on a.id = b.id )
where b_id is not null or c1 = c2
demo

You can consider this problem a ranking problem: You want to show the best rows only, with non-null rows being considered "better" than null rows.
Ranking can be achieved with an appropriate ORDER BY clause. As of Oracle 12c:
select
a.id as a_id, a.name as a_name,
b.id as b_id, b.name as b_name, b.info as b_info
from a
left join b on b.id = a.id_b
where a.filename = 'file1.txt'
order by case when b.id is null then 2 else 1 end
fetch first rows with ties;
In older versions:
select a_id, a_name, b_id, b_name, b_info
from
(
select
a.id as a_id, a.name as a_name,
b.id as b_id, b.name as b_name, b.info as b_info,
rank() over (order by case when b.id is null then 2 else 1 end) as rnk
from a
left join b on b.id = a.id_b
where a.filename = 'file1.txt'
)
where rnk = 1;

Related

Pull conditional statement results based on multiple table joins

I have 3 tables to join to get the output in the below format.
My table 1 is like:
--------------------------------------------------------
T1_ID1 | T1_ID2 | NAME
--------------------------------------------------------
123 | T11231 | TestName11
123 | T11232 | TestName12
234 | T1234 | TestName13
345 | T1345 | TestName14
--------------------------------------------------------
My table 2 is like:
--------------------------------------------------------
T2_ID1 | T2_ID2 | NAME
--------------------------------------------------------
T11231 | T21231 | TestName21
T11232 | T21232 | TestName21
T1234 | T2234 | TestName22
--------------------------------------------------------
My table 3 is like:
----------------------------------------------------------
T3_ID1 | TYPE | REF
----------------------------------------------------------
T21231 | 1 | 123456
T21232 | 2 | 1234#test.com
T2234 | 2 | 123#test.com
----------------------------------------------------------
My desired output is:
------------------------------------------------------
T1_ID1 | PHONE | EMAIL
------------------------------------------------------
123 | 123456 | 1234#test.com
234 | | 123#test.com
345 | |
------------------------------------------------------
Requirements:
T1_ID2 of table 1 left joins with T2_ID1 of table 2.
T2_ID2 of table 2 left joins with T3_ID1 of table 3.
TYPE of table 3 specifies 1 if the value is phone and specified 2 if value is email.
My output should contain T1_ID1 of table 1 and its corresponding value of REF in table 3, with the REF in the same row.
That is, in this case, T1_ID1 with value 123 has both phone and email. So, it is displayed in the same row in output.
If phone alone is available for corresponding value of T1_ID1, then phone should be populated in the result with email as null and vice versa.
If neither phone nor email is available, nothing should be populated.
I had tried the below SQLs but in vain. Where am I missing? Please extend your help.
Option 1:
SELECT DISTINCT
t1.t1_id1,
t3.ref
|| (
CASE
WHEN t3.type = 1 THEN
1
ELSE
0
END
) phone,
t3.ref
|| (
CASE
WHEN t3.type = 2 THEN
1
ELSE
0
END
) email
FROM
table1 t1
LEFT JOIN table2 t2 ON t1.t1_id2 = t2.t2_id1
LEFT JOIN table3 t3 ON t2.t2_id2 = t3.t3_id1;
Option 2:
SELECT DISTINCT
t1.t1_id1,
t3.ref,
(
CASE
WHEN t3.type = 1 THEN
1
ELSE
0
END
) phone,
t3.ref,
(
CASE
WHEN t3.type = 2 THEN
1
ELSE
0
END
) email
FROM
table1 t1
LEFT JOIN table2 t2 ON t1.t1_id2 = t2.t2_id1
LEFT JOIN table3 t3 ON t2.t2_id2 = t3.t3_id1;
Option 3:
SELECT DISTINCT
t1.t1_id1,
(
CASE
WHEN t3.type = 1 THEN
1
ELSE
0
END
) phone,
(
CASE
WHEN t3.type = 2 THEN
1
ELSE
0
END
) email
FROM
table1 t1
LEFT JOIN table2 t2 ON t1.t1_id2 = t2.t2_id1
LEFT JOIN table3 t3 ON t2.t2_id2 = t3.t3_id1;
select t1_id1, max(t3.ref )phone, max(t33.ref) email
from table1
left outer join
table2 on t1_id2=t2_id1
left outer join table3 t3 on t3.t3_id1=t2_id2 and t3.type=1
left outer join table3 t33 on t33.t3_id1=t2_id2 and t33.type=2
group by t1_id1
if you have maximum one phone and one email in table3 for each t2_id2 entry in table2.

Outer join query FROM_TABLE Flag required

I want one flag in output of full outer join in oracle saying from_table which show this tows is fron which table in outer join.
For ex
A full outer join will give you the union of A and B, i.e. all the rows in A and all the rows in B. If something in A doesn't have a corresponding datum in B, then the B portion is null, and vice versa.
select * from a FULL OUTER JOIN b on a.a = b.b;
a | b
-----+-----
1 | null
2 | null
3 | 3
4 | 4
null | 6
null | 5
I need below output:
a | b | from_table
-----+-----
1 | null | A
2 | null | A
3 | 3 | both
4 | 4 | both
null | 6 | B
null | 5 | B
Kindly suggest the query
select a.*, b.*,
case when a.a is not null and b.b is not null then 'both'
when a.a is not null then 'a'
else 'b'
end as from_table
from a
FULL OUTER JOIN b on a.a = b.b;
SQLFiddle Demo
It's eligible to use nvl and nvl2 functions consecutively :
select a, b, decode(sign(nvl2(x,1,0)*nvl2(y,1,0)),1,'both',nvl(x,y)) "from_table"
from
(
select a.*, b.*, nvl2(a,'A',null) x, nvl2(b,'B',null) y
from a FULL OUTER JOIN b on a.a = b.b order by a,b
);
demo

Delete Oracle with join tables

I have a table that has three columns ( primary key) and I need a delete sentece that allows me to remove the elements I don't need , using a join with the this table and other table, I've tried two delete sentences but they are not working as expected:
First One: This one gets the values I dont need and they are removed from table A, but the issue here is it deletes the values from Table B and C too and those rows can't be removed
DELETE
FROM
(SELECT A.*
FROM TABLE_A A
JOIN TABLE_B B
ON A.CODE =B.CODE
JOIN TABLE_C C
ON B.PRODUCT =C.PRODUCT
WHERE B.VALUE >10000
AND C.RANGE NOT IN (4006, 4005, 4004, 4003, 4002, 4001)
);
**Second One:**The problem with this one is that it removes all the rows from table A, but if I test the query ( select) it returns 5 rows, the ones that should be removed.
DELETE
FROM A WHERE EXIST
(SELECT A.*
FROM TABLE_A A
JOIN TABLE_B B
ON A.CODE =B.CODE
JOIN TABLE_C C
ON B.PRODUCT =C.PRODUCT
WHERE B.VALUE >10000
AND C.RANGE NOT IN (4006, 4005, 4004, 4003, 4002, 4001)
);
So has anyone any idea of what I could be doing wrong?
The first one will delete matched rows across the joins, the second one will delete all rows when there EXISTS any one matched row as you are not correlating the deleted rows to the sub-query.
You can perform this correlation using the ROWID pseudo-column:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE table_a ( id, code ) AS
SELECT 1, 1 FROM DUAL UNION ALL
SELECT 2, 2 FROM DUAL UNION ALL
SELECT 3, 3 FROM DUAL;
CREATE TABLE table_b ( id, code, product, value ) AS
SELECT 1, 1, 1, 10001 FROM DUAL UNION ALL
SELECT 2, 2, 2, 10001 FROM DUAL UNION ALL
SELECT 3, 3, 1, 9999 FROM DUAL;
CREATE TABLE table_c ( id, product, range ) AS
SELECT 1, 1, 1001 FROM DUAL UNION ALL
SELECT 2, 2, 4001 FROM DUAL;
DELETE
FROM table_A
WHERE ROWID IN (
SELECT A.ROWID
FROM TABLE_A A
JOIN TABLE_B B
ON A.CODE = B.CODE
JOIN TABLE_C C
ON B.PRODUCT = C.PRODUCT
WHERE B.VALUE >10000
AND C.RANGE NOT IN (4006, 4005, 4004, 4003, 4002, 4001)
);
Query 1:
SELECT * FROM table_a
Results:
| ID | CODE |
|----|------|
| 2 | 2 |
| 3 | 3 |
Query 2:
SELECT * FROM table_b
Results:
| ID | CODE | PRODUCT | VALUE |
|----|------|---------|-------|
| 1 | 1 | 1 | 10001 |
| 2 | 2 | 2 | 10001 |
| 3 | 3 | 1 | 9999 |
Query 3:
SELECT * FROM table_c
Results:
| ID | PRODUCT | RANGE |
|----|---------|-------|
| 1 | 1 | 1001 |
| 2 | 2 | 4001 |

Retrieve from Oracle db key value pair

I need to retrieve 3 values with different key from a key value pair table.
My database schema as follows. I need to reach to table3 from table1 by taking the E_SUBID and then joining the table2 with E_SUBID. Once table1 and table2 are joined I need take to take E_CID from table2 to join it with table2 E_CID to get the "Attr_Value" keeping E_CID as a criteria.
Table1
------------------------
|E_SUBID| B_LocationID |
|1 100 |
|2 101 |
|3 102 |
Table2
-----------------
|E_CID | E_SUBID|
|10 1 |
|11 2 |
|12 3 |
Table3
---------------------------------
|E_CID | Attr_name | Attr_Value |
|10 Product Samsung |
|10 Model Smartphone |
|10 usage daily |
|11 Product Apple |
|11 Model Ipad |
|11 usage everyday |
|12 Model smartwatch |
I have been successful to join table1,table2 and table3 but I cannot get the required output which as follows
OUTPUT
|Product | Model | Usage |
Samsung Smartphone daily
Apple Ipad everyday
null smartwatch null
The query which joins table1, table2 and table3 as follows
select distinct t3.Attr_value as Product
from table1 t1, table2 t2, table3 t3
where t1.E_SUBID = t2.E_SUBID and
t2.E_CID = t3.E_CID and
t3.Attr_name=?????
order by Product;
Thank you for your time.
In a case like this, you can join to table3 as often as you need to for each attribute name you wish to display:
select
p.attr_value product,
m.attr_value "model", -- Quotes to escape reserved word
u.attr_value usage
from table1 t1
join table2 t2 on t1.e_subid = t2.e_subid
left outer join table3 p on t2.e_cid = p.e_cid and p.attr_name = 'Product'
left outer join table3 m on t2.e_cid = m.e_cid and m.attr_name = 'Model'
left outer join table3 u on t2.e_cid = u.e_cid and u.attr_name = 'Usage'
order by 1;
Edit
Based on the comment, by making table3 optional (outer join) the query should return all rows and whether or not a Model or Usage or Product has been defined.
Try as below ... Basically you are trying to transpose the rows to column in table3.
Select Product, "Model", Usage
From
(
Select
t1.E_SUBID,
t2.E_CID,
Max(Case when T3.Attr_name = 'Product' Then T3.Attr_Value else null end) Product,
max(Case when T3.Attr_name = 'Model' Then T3.Attr_Value else null end) Model,
max(Case when T3.Attr_name = 'Usage' Then T3.Attr_Value else null end) Usage
From Table1 t1,
Table2 t2,
Table3 t3
Where
t1.E_SUBID = t2.E_SUBID
and t2.E_CID = t3.E_CID
group by t1.t1.E_SUBID,t2.E_CID
);

Oracle:Cursor Procedure: sum 2 tables with join

I have 3 tables.
Table1
|Obj_id|Obj_Name|
----------------
|A | AAA|
|B | BBB|
|C | CCC|
Table2
|Obj_id|Amount1|
----------------
|A | 1000|
|C | 20|
|A | 100|
|B | 50|
|C | 10|
Table3
|Obj_id|Amount2|
----------------
|B | 500|
|C | 10|
|C | 40|
Now I need to create a procedure "report" that returns a cursor like below
Report
|Obj_Name|Amount1|Amount2|Obj_id|
---------------------------------
|AAA | 1100| 0|A |
|BBB | 50| 500|B |
|CCC | 30| 50|C |
I googled everywhere but there is no matching answer.
Try this
SELECT
T1.Obj_Name
,NVL(T2.Amount1,0) AS Amount1
,NVL(T3.Amount2,0) AS Amount2
,NVL(T1.Obj_id ,0) AS Obj_id
FROM Table1 T1
LEFT JOIN Table2 T2 ON T1.Obj_id = T2.Obj_id
LEFT JOIN Table3 T3 ON T1.Obj_id = T3.Obj_id
ORDER BY T1.Obj_Name
SQL FIDDLE DEMO
OUTPUT
Obj_Name Amount1 Amount2 Obj_id
AAA 1000 0 A
BBB 0 500 B
CCC 20 10 C
EDIT
SELECT
T1.Obj_Name AS Obj_Name
,NVL(T2.Amount1,0) AS Amount1
,NVL(T3.Amount2,0) AS Amount2
,T1.Obj_id AS Obj_id
FROM Table1 T1
LEFT JOIN (SELECT Obj_id,SUM(Amount1) AS Amount1 FROM Table2 GROUP BY Obj_id) T2 ON T1.Obj_id = T2.Obj_id
LEFT JOIN (SELECT Obj_id,SUM(Amount2) AS Amount2 FROM Table3 GROUP BY Obj_id) T3 ON T1.Obj_id = T3.Obj_id
ORDER BY T1.Obj_Name;
SQL FIDDLE DEMO UPDATED
OUTPUT
OBJ_NAME AMOUNT1 AMOUNT2 OBJ_ID
AAA 1100 0 A
BBB 50 500 B
CCC 30 50 C

Resources