Only join two columns when it is not null in one table oracle - oracle

I have a table a
ID | Name | City
1 |Jack | Null
2 |Tom | Null
And table b
ID | Name | City
1 |Jack | Dever
2 |Tom | Dallas
I need to write a query to join these two tables by id, name and city if they are not null in table a. But any of these three column could be null for each row.
I wrote one below but the performance is bad when data grows
Select * from a, b
Where (a.id is not null and a.id=b.id or a.id is null) and
(a.name is not null and a.name=b.name or a.name is null) and
(a.city is not null and a.city=b.city or a.city is null)
Basically, I need to join on the column when it is not null in table a.
Could you shed some light on this?
Thanks a lot!

ATTEMPT I:
Would this be what you need? It seems to do what I can read out from your question.
with a as (select 1 id, 'Jack' name, null city from dual
union all
select 2 id, 'Tom' name, null city from dual
union all
select 3 id, 'Mike' name, 'Miami' city from dual)
,b as (select 1 id, 'Jack' name, 'Dever' city from dual
union all
select 2 id, 'Tom' name, 'Dallas' city from dual
union all
select 3 id, 'Mike' name, 'Boise' city from dual)
select b.*
from b
left outer join a
on a.id = b.id
and a.name = b.name
where b.city = nvl(a.city, b.city);
If not, please advisa as to what would need to change in the result or possibly in the indata.
UPDATE I:
To allow all columns to have the possibility of being null, this could be one way of doing it. I've added testdata for the conditions I think your are describing. It gives the result I think you are looking for.
with a as (select 1 id, 'Jack' name, null city from dual
union all
select 2 id, 'Tom' name, null city from dual
union all
select 3 id, 'Mike' name, 'Miami' city from dual
union all
select 4 id, 'Don' name, null city from dual
union all
select 5 id, null name, 'London' city from dual
union all
select null id, 'Erin' name, 'Berlin' city from dual
)
,b as (select 1 id, 'Jack' name, 'Dever' city from dual
union all
select 2 id, 'Tom' name, 'Dallas' city from dual
union all
select 3 id, 'Mike' name, 'Boise' city from dual
union all
select 4 id, 'Don' name, 'Dover' city from dual
union all
select 5 id, 'Lis' name, 'London' city from dual
union all
select 6 id, 'Erin' name, 'Berlin' city from dual
)
select b.*, a.*
from b
inner join a
on b.id = nvl(a.id, b.id)
and b.name = nvl(a.name, b.name)
and b.city = nvl(a.city, b.city)
order by 1;

Related

split data in a single column into multiple columns in oracle

my_table :
Name
Value
item_1
AB
item_2
2
item_3
B1
item_1
CD
item_1
EF
item_2
3
item_3
B2
item_4
ZZ
required output:
item_1
item_2
item_3
item_4
AB
2
B1
ZZ
CD
3
B2
NULL
EF
NULL
NULL
NULL
SQL query :
with item_1 as (select value from my_table where name = 'item_1'),
item_2 as (select value from my_table where name = 'item_2'),
item_3 as (select value from my_table where name = 'item_3'),
item_4 as (select value from my_table where name = 'item_4')
select item_1.value, item_2.value,item_3.value, item_4.value from item_1 cross join item_2 cross join item_3 cross join item_4;
If I am using pivot along with MAX aggregate function, the query will display only max values of the corresponding items instead of displaying all the values.
Is there any way to split a single column into multiple columns(using where condition as mentioned in the above query) without cross join.
Use ROW_NUMBER and then PIVOT:
SELECT item_1,
item_2,
item_3,
item_4
FROM (
SELECT t.*,
ROW_NUMBER() OVER (PARTITION BY name ORDER BY ROWNUM) AS rn
FROM table_name t
)
PIVOT (
MAX(value) FOR name IN (
'item_1' AS item_1,
'item_2' AS item_2,
'item_3' AS item_3,
'item_4' AS item_4
)
)
Which, for the sample data:
CREATE TABLE table_name (Name, Value) AS
SELECT 'item_1', 'AB' FROM DUAL UNION ALL
SELECT 'item_2', '2' FROM DUAL UNION ALL
SELECT 'item_3', 'B1' FROM DUAL UNION ALL
SELECT 'item_1', 'CD' FROM DUAL UNION ALL
SELECT 'item_1', 'EF' FROM DUAL UNION ALL
SELECT 'item_2', '3' FROM DUAL UNION ALL
SELECT 'item_3', 'B2' FROM DUAL UNION ALL
SELECT 'item_4', 'ZZ' FROM DUAL;
Outputs:
ITEM_1
ITEM_2
ITEM_3
ITEM_4
AB
2
B1
ZZ
CD
3
B2
null
EF
null
null
null
db<>fiddle here
How about this?
DF column is calculated by row_number analytic function which partitions by each name (and sorts by value). It is ignored from the final column list, but its role is crucial in the GROUP BY clause.
SQL> with test (name, value) as
2 (select 'item_1', 'AB' from dual union all
3 select 'item_2', '2' from dual union all
4 select 'item_3', 'B1' from dual union all
5 select 'item_1', 'CD' from dual union all
6 select 'item_1', 'EF' from dual union all
7 select 'item_2', '3' from dual union all
8 select 'item_3', 'B2' from dual union all
9 select 'item_4', 'ZZ' from dual
10 ),
11 temp as
12 (select name, value,
13 row_number() over (partition by name order by value) df
14 from test
15 )
16 select
17 max(case when name = 'item_1' then value end) item_1,
18 max(case when name = 'item_2' then value end) item_2,
19 max(case when name = 'item_3' then value end) item_3,
20 max(case when name = 'item_4' then value end) item_4
21 from temp
22 group by df;
ITEM_1 ITEM_2 ITEM_3 ITEM_4
------ ------ ------ ------
AB 2 B1 ZZ
CD 3 B2
EF
SQL>

How to Choose a specific value from a table and to avoid duplicates?

I have two tables:
MainTable
id AccountNum status
1 11001 active
2 11002 active
3 11003 active
4 11004 active
AddTable
id date description
1 01.2020 ACCOUNT.SET
1 02.2020 ACCOUNT.CHANGE
1 03.2020 ACCOUNT.REMOVE
2 04.2020 ACCOUNT.SET
2 05.2020 ACCOUNT.CHANGE
3 08.2020 ACCOUNT.SET
4 05.2020 ACCOUNT.SET
4 09.2020 ACCOUNT.REMOVE
I need to get a such result:
EffectiveFrom is date when Account was set,
EffectiveTo is date when Account was removed
id AccountNum EffectiveFrom EffectiveTo
1 11001 01.2020 03.2020
2 11002 04.2020 null
3 11003 08.2020 null
4 11004 05.2020 09.2020
The problem is that after joining on AddTable I get the duplicates, but I need just one row on every Id and only dates where the description in ACCOUNT.SET,ACCOUNT.REMOVE.
Are you looking for left join?
select m.id as id,
m.AccountNum as AccountNum,
a.date as EffectiveFrom,
b.date as EffectiveTo
from MainTable m left join
AddTable a on (a.id = m.id and a.description = 'ACCOUNT.SET') left join
AddTable b on (b.id = m.id and b.description = 'ACCOUNT.REMOVE')
order by m.AccountNum
Use a PIVOT and a LEFT OUTER JOIN:
SELECT m.id,
a.EffectiveFrom,
a.EffectiveTo
FROM MainTable m
LEFT OUTER JOIN
(
SELECT *
FROM AddTable
PIVOT( MAX( dt ) FOR description IN (
'ACCOUNT.SET' AS EffectiveFrom,
'ACCOUNT.REMOVE' AS EffectiveTo
) )
) a
ON ( a.id = m.id )
ORDER BY m.id
So for your test data:
CREATE TABLE MainTable ( id, AccountNum, status ) AS
SELECT 1, 11001, 'active' FROM DUAL UNION ALL
SELECT 2, 11002, 'active' FROM DUAL UNION ALL
SELECT 3, 11003, 'active' FROM DUAL UNION ALL
SELECT 4, 11004, 'active' FROM DUAL;
CREATE TABLE AddTable ( id, dt, description ) AS
SELECT 1, DATE '2020-01-01', 'ACCOUNT.SET' FROM DUAL UNION ALL
SELECT 1, DATE '2020-01-02', 'ACCOUNT.CHANGE' FROM DUAL UNION ALL
SELECT 1, DATE '2020-01-03', 'ACCOUNT.REMOVE' FROM DUAL UNION ALL
SELECT 2, DATE '2020-01-04', 'ACCOUNT.SET' FROM DUAL UNION ALL
SELECT 2, DATE '2020-01-05', 'ACCOUNT.CHANGE' FROM DUAL UNION ALL
SELECT 3, DATE '2020-01-08', 'ACCOUNT.SET' FROM DUAL UNION ALL
SELECT 4, DATE '2020-01-05', 'ACCOUNT.SET' FROM DUAL UNION ALL
SELECT 4, DATE '2020-01-09', 'ACCOUNT.REMOVE' FROM DUAL;
This outputs:
ID | EFFECTIVEFROM | EFFECTIVETO
-: | :------------ | :----------
1 | 01-JAN-20 | 03-JAN-20
2 | 04-JAN-20 | null
3 | 08-JAN-20 | null
4 | 05-JAN-20 | 09-JAN-20
db<>fiddle here

Oracle 9.2 pivot distinct value

The pivot function is available from Oracle 11 and i will need similar result using Oracle 9.2.
The main argument is that i need to pivot some values with a distinct result in a table like this:
id col3
1 a
1 b
--
2 a
2 a
2 b
--
3 a
3 b
3 c
My result sould be something like this
id a b c
1 1 1 0
2 1 1 0
3 1 1 1
To create a "manual" pivot i'm using a case/when but I am not able to understand how to get distinct value.
Right now the query is this:
with t as
( select 1 as id, 'a' as col1 from dual union all
select 1 as id, 'b' from dual union all
select 2 as id, 'a' from dual union all
select 2 as id, 'a' from dual union all
select 2 as id, 'b' from dual union all
select 3 as id, 'a' from dual union all
select 3 as id, 'b' from dual union all
select 3 as id, 'c' from dual
)
select t.id,
count(case when t.col1 = 'a' then 1 end) a,
count(case when t.col1 = 'b' then 1 end) b,
count(case when t.col1 = 'c' then 1 end) c
This produce correct value but obviously it just "count" the total a/b/c value and not the distinct.
thanks for the support
If I correctly understand your need, you can try something like the following; it aggregates by id and counts the distinct values of col3:
with t as
( select 1 as id, 'a' as col1 from dual union all
select 1 as id, 'b' from dual union all
select 2 as id, 'a' from dual union all
select 2 as id, 'a' from dual union all
select 2 as id, 'b' from dual union all
select 3 as id, 'a' from dual union all
select 3 as id, 'b' from dual union all
select 3 as id, 'c' from dual
)
select id,
count(distinct decode (col1, 'a', id, null)) a,
count(distinct decode (col1, 'b', id, null)) b,
count(distinct decode (col1, 'c', id, null)) c
from t
group by id
Of course the query depends on the number of different values of col3, but this is the same problem than pivot.

Select tree by leaf in Oracle Database

There is hierarchical queries in Oracle, using CONNECT BY PRIOR. Everybody knows, how to select children by parent, but I need to select parent by child.
Here is my table:
ID PID NAME TYPE
1 null EARTH PLANET
2 1 USA COUNTRY
3 2 CALIFORNIA STATE
4 3 Los_Angeles CITY
5 3 San_Francisco CITY
6 3 San_Diego CITY
In my app I have ID of San_Diego, and I need to know, in what country San_Diego is? I need to get USA (TYPE=COUNTRY) with my query? How to select it with oracle hierarchical structures?
Hierarchy means, that one row of a table is a "parent", and another one - a "child". PRIOR is used to show who is who. Clause CONNECT BY PRIOR EMPNO = MGR means, that if two rows have the same value, but one row in a column EMPNO, and the second - in a column MGR, then second is a "parent" and first is a "child". So, query
SELECT EMPNO,ENAME,MGR,LEVEL
FROM TMP_PCH
CONNECT BY PRIOR EMPNO = MGR
START WITH MGR = 'John'
returns all subordinates of John (and John itself), and query
SELECT EMPNO,ENAME,MGR,LEVEL
FROM TMP_PCH
CONNECT BY PRIOR MGR = EMPNO
START WITH MGR = 'John'
returns all bosses of John (and John itself).
You need to create correct condition for connect by and start with leaf 'San Diego':
select name
from (
select * from test
connect by id = prior pid
start with name='San_Diego')
where type='COUNTRY'
SQL Fiddle demo
SELECT * FROM places_table
WHERE type = 'COUNTRY'
START WITH id = 6 -- San Diego
CONNECT BY PRIOR pid = id;
There's no difference, you just have to use PRIOR in the parent side. For example to get the tree for the city of San Diego:
WITH your_table AS (
SELECT 1 id, NULL pid, 'EARTH' name, 'PLANET' type FROM dual
UNION
SELECT 2 id, 1 pid, 'USA' name, 'COUNTRY' type FROM dual
UNION
SELECT 3 id, 2 pid, 'CALIFORNIA' name, 'STATE' type FROM dual
UNION
SELECT 4 id, 3 pid, 'Los_Angeles' name, 'CITY' type FROM dual
UNION
SELECT 5 id, 3 pid, 'San_Francisco' name, 'CITY' type FROM dual
UNION
SELECT 6 id, 3 pid, 'San_Diego' name, 'CITY' type FROM dual
)
SELECT id, name, level
FROM your_table
CONNECT BY id = PRIOR pid
START WITH id = 6
If you just wanted to get the country, you could filter by level:
WITH your_table AS (
SELECT 1 id, NULL pid, 'EARTH' name, 'PLANET' type FROM dual
UNION
SELECT 2 id, 1 pid, 'USA' name, 'COUNTRY' type FROM dual
UNION
SELECT 3 id, 2 pid, 'CALIFORNIA' name, 'STATE' type FROM dual
UNION
SELECT 4 id, 3 pid, 'Los_Angeles' name, 'CITY' type FROM dual
UNION
SELECT 5 id, 3 pid, 'San_Francisco' name, 'CITY' type FROM dual
UNION
SELECT 6 id, 3 pid, 'San_Diego' name, 'CITY' type FROM dual
)
SELECT id, name, level
FROM your_table
WHERE LEVEL = 3
CONNECT BY id = PRIOR pid
START WITH id = 6

PL/SQL cursor select unique record and print in flat file

I have set of values in a cursor. For example:
CURSOR c_stock_option IS
SELECT empid, '1' AS ISenrolled
FROM employee emp
UNION ALL
SELECT empid, '2' AS ISenrolled
FROM employee emp;
Now I want to check if the empid appears both in first select (where ISenrolled
=1) and second select (where ISenrolled
=2). I want to only grab the value from first select where enroll=1 and reject the one where enroll=2. I want to only print records that qualifies this criteria.
FOR v_stock_option_record IN c_stock_option LOOP
IF v_esppstock_recs IN (v_stock_option_record.empid) THEN
END IF;
-- Participant file.
v_member_string_1 := v_stock_option_record.empid || G_DELIMITER || --1. participant id
v_stock_option_record.last_name || G_DELIMITER || --4. Last Name
v_stock_option_record.first_name || G_DELIMITER || --5. First Name
END loop;
In the first part of query it is selecting all the employees that have purchased the stocks (that will give only the set of employees who have purchased the stocks, other part of the query gives all the active employees in the company, so the employee who is in the first part of the select will always be in second part of the select, but the employee who is in second part of select is not necessarily in the 1st part. In the scenario when employee appears in both parts what I need to do is just select the employee who has isenrolled=1).
Below is the SQL to differentiate
SELECT
empid,
'1' AS ISenrolled
FROM employee emp,
hrempusf usf
where emp.employee = usf.employee
AND usf.field_key = 76 ---- 76 determines that employee has purchased stocks
UNION ALL
SELECT
empid,
'2' AS ISenrolled
FROM employee emp;
You don't need complicated PL/SQL for this, you just need a LEFT OUTER JOIN. This will return all the EMPLOYEE records, regardless of whether it matches an HREMPUSF record.
SELECT
empid
, nvl2(usf.field_key ,'1', '2') AS ISenrolled
FROM employee emp
left outer join hrempusf usf
on ( usf.employee = emp.employee
and usf.field_key = 76 )
The NVL2() returns the second value if the first argument is not null and the third argument if it is null.
Here is one way that should work, it will only return the 1=>IsEnrolled if it exists, otherwise it'll return the 2 IsEnrolled.
The "data" is to mimic your two select statements.
with data as(
select 1 empId, 1 ISEnrolled from dual
union
select 2 empId, 1 ISEnrolled from dual
union
select 3 empId, 1 ISEnrolled from dual
union
select 4 empId, 1 ISEnrolled from dual
union
select 5 empId, 1 ISEnrolled from dual /** these 5 are mimicing your first select */
union
select 1 empId, 2 ISEnrolled from dual /** the next are the 'all' */
union
select 2 empId, 2 ISEnrolled from dual
union
select 3 empId, 2 ISEnrolled from dual
union
select 4 empId, 2 ISEnrolled from dual
union
select 5 empId, 2 ISEnrolled from dual
union
select 6 empId, 2 ISEnrolled from dual
union
select 7 empId, 2 ISEnrolled from dual
union
select 8 empId, 2 ISEnrolled from dual
union
select 9 empId, 2 ISEnrolled from dual
union
select 10 empId, 2 ISEnrolled from dual)
,
onlyOneIsEnrolled as (
select empId, isEnrolled
from data
where isEnrolled = 1
) ,
notInOneIsEnrolled as(
select empId, isEnrolled
from data d
where not exists(select null
from onlyOneIsEnrolled ooie
where ooie.empid = d.empId
)
)
select EmpId, isEnrolled
from onlyOneIsEnrolled
union
select EmpId, isEnrolled
from notInOneIsEnrolled
order by isEnrolled, EmpId
This is just one way to accomplish the task, the onlyOneIsEnrolled gathers all the 1's, then notInOneIsEnrolled gets all the emps not in the above, the final part of the query puts them together.
There are many ways to accomplish this task, this utilizes CTE's but depending on your need you may be able to do this without utilizing the with clause.

Resources