Oracle SQL - join two tables + show unmatched results - sql

I have two tables:
POSITION_TABLE
Account
Security
Pos_Quantity
1
A
100
2
B
200
TRADE_TABLE
Account
Security
Trade_Quantity
1
A
50
2
C
10
I want to join them in a way that matching rows are displayed as one row, but unmatching rows are also displayed, so standard LEFT JOIN wouldnt work.
Expected output:
Account
Security
Pos_Quantity
Trade_Quantity
1
A
100
50
2
B
200
0
2
C
0
10
How do I do that?

A full outer join would work nicely here:
with position_table as (select 1 account, 'A' security, 100 pos_quantity from dual union all
select 2 account, 'B' security, 200 pos_quantity from dual),
trade_table as (select 1 account, 'A' security, 50 trade_quantity from dual union all
select 2 account, 'C' security, 10 trade_quantity from dual)
select coalesce(pt.account, tt.account) account,
coalesce(pt.security, tt.security) security,
coalesce(pt.pos_quantity, 0) pos_quantity,
coalesce(tt.trade_quantity, 0) trade_quantity
from position_table pt
full outer join trade_table tt on pt.account = tt.account
and pt.security = tt.security
order by account,
security;
db<>fiddle - note how you can see that the full outer join works just fine with subqueries defined in a where clause!

Related

How to get frequency of each value in two columns in a mapping table

We have a mapping table with two columns, SITE_FROM & SITE_TO, the data is like:
We want to run a query to summarize for each site, get count in the SITE_FROM column and count in the SITE_TO column, such as:
<- desired
The explanation is, S1 appeared 2 times in SITE_FROM, 2 times in SITE_TO. S2 appeared 3 times in SITE_FROM, 4 times in SITE_TO. S3 appears 0 times in SITE_FROM, 3 times in SITE_TO...
I came up with some query:
SELECT SITE_FROM AS SITE,
count(*) CNT,
count(decode(SITE_TO,'S1',1)) S1,
count(decode(SITE_TO,'S2',1)) S2,
count(decode(SITE_TO,'S3',1)) S3,
count(decode(SITE_TO,'S4',1)) S4,
count(decode(SITE_TO,'S5',1)) S5
FROM site_changes
GROUP BY SITE_FROM
ORDER BY 1
;
But it returns detailed site to site mapping, also it has to have hard coded values in it, if later more sites are added, we need to remember to update the query as well:
<- undesired
Thank you for your time.
First get a defined list of all sites by unironing the data from site for each column. (CTE BELOW) this allows for any site to be added (no hard coding)
then join the CTE to CITE for from and to and count with group by.
since the left join will result in NULLS when no matches are found, and nulls do not get counted we should get the desired counts.
DEMO: https://dbfiddle.uk/k3ehGbIt
CTE AS (SELECT SITE_FROM as SITE FROM SITE_CHANGES
UNION
SELECT SITE_TO FROM SITE_CHANGES)
SELECT A.SITE,
coalesce(SUM(B.SITE_FROM_CNT),0) as SITE_FROM,
coalesce(SUM(C.SITE_TO_CNT) ,0) as SITE_TO
FROM CTE A
LEFT JOIN (SELECT SITE_FROM, count(SITE_FROM) SITE_FROM_CNT
FROM SITE_CHANGES
GROUP BY SITE_FROM) B
on A.SITE= B.SITE_FROM
LEFT JOIN (SELECT SITE_TO, count(SITE_TO) SITE_TO_CNT
FROM SITE_CHANGES
GROUP BY SITE_TO) C
on A.SITE = C.SITE_TO
GROUP BY A.SITE
ORDER BY A.SITE
Giving us:
+------+-----------+---------+
| SITE | SITE_FROM | SITE_TO |
+------+-----------+---------+
| S1 | 2 | 2 |
| S2 | 3 | 4 |
| S3 | 0 | 3 |
| S4 | 4 | 1 |
| S5 | 1 | 0 |
+------+-----------+---------+
I union'd both sets to get the full permutation of to and from site id's. Then joined into that set twice with left outer joins. To avoid nulls I used NVL replacing the null with 0.
SELECT
SITE
, NVL(Z.SITE_FROM,0) SITE_FROM
, NVL(A.SITE_TO,0) SITE_TO
FROM
(
SELECT sitefrom SITE
FROM
site_changes
UNION
SELECT siteto SITE
from site_changes
) X
LEFT OUTER JOIN
(
SELECT COUNT(1) SITE_FROM, SITEFROM FROM site_changes GROUP BY SITEFROM
) Z ON X.SITE = Z.SITEFROM
LEFT OUTER JOIN
(
SELECT COUNT(1) SITE_TO, SITETO FROM site_changes GROUP BY SITETO
) A ON X.SITE = A.SITETO
The challenge is not having the same sites on both columns so we unpivot them to get a list of all the info and then pivot them back with count to get the info we need.
select *
from t
unpivot (site for to_from in ("SITE FROM","SITE TO"))
pivot (count(*) for to_from in('SITE FROM' as "SITE FROM", 'SITE TO' as "SITE TO")) p
order by site
SITE
SITE FROM
SITE TO
S1
2
2
S2
3
4
S3
0
3
S4
4
1
S5
1
0
Fiddle
Here's the same concept, but using a full outer join instead of selecting out the distinct elements.
with sf as (
select
site_from as site,
count(*) as site_from
from site_changes
group by
site_from
),
st as (
select
site_to as site,
count(*) as site_to
from site_changes
group by
site_to
)
select
site,
nvl(site_from, 0) as site_from,
nvl(site_to, 0) as site_to
from sf
full outer join st
using (site)
#vlookup indicated that using FULL OUTER JOIN might be less performant. If you have a chance to compare the queries, please share the results.

SQL help i need to find the inventory remaining in my office

In sql help i have 3 tables, table one is asset table which is as follow
id
asset_code
asset_name
asset_group
asset_quantity
1
A001
demo asset
4
5
2
A002
demo asset 2
6
3
and another table is asset_allocation
id
asset_id
allocated_quantity
allocated_location
1
1
2
IT office
2
1
1
main hall
the last table is asset_liquidated which will present assets that are no longer going to be used
id
asset_id
liquidated_quantity
1
1
2
2
1
1
lets say i have 5 computers and i have allocated 3 computers and 1 is no longer going to be used so i should be remaining with 1 computer so now how do i make sql auto generate this math for me
You need to use aggregation and the join your tables -
SELECT id, asset_code, asset_name, asset_group, asset_quantity,
asset_quantity - COALESCE(AA.allocated_quantity, 0) - COALESCE(AL.liquidated_quantity, 0) available_quantity
FROM asset A
LEFT JOIN (SELECT asset_id, SUM(allocated_quantity) allocated_quantity
FROM asset_allocation
GROUP BY asset_id) AA ON A.id = AA.asset_id
LEFT JOIN (SELECT asset_id, SUM(liquidated_quantity) liquidated_quantity
FROM asset_liquidated
GROUP BY asset_id) AL ON A.id = AL.asset_id
This query will give you -1 as available_quantity for asset_id 1 as you have only 5 available, 3 of them are allotted and 3 are liquidated as per your sample data.
Please see if this helps
SELECT
asset_quantity AS Total_Assets
,ISNULL(allocated_quantity, 0) allocated_quantity
,ISNULL(liquidated_quantity, 0) liquidated_quantity
FROM asset
LEFT OUTER JOIN (
SELECT
asset_id, SUM(allocated_quantity) AS allocated_quantity
FROM asset_allocation
GROUP BY asset_id
) asset_allocation2
ON asset_allocation2.asset_id = asset.id
LEFT OUTER JOIN (
SELECT
asset_id, SUM(liquidated_quantity) AS liquidated_quantity
FROM asset_liquidated
GROUP BY asset_id
) asset_liquidated 2
ON asset_liquidated 2.asset_id = asset.id

Ranking of a tuple in another table

So I have 2 tables, team A and team B, with their score. I want the rank of the score of every member of team A within team B using SQL or vertica, as shown below
Team A Table
user score
-------------
asa 100
bre 200
cqw 50
duy 50
Team B Table
user score
------------
gfh 20
ewr 80
kil 70
cvb 90
Output:
Team A Table
user score rank in team B
------------------------------
asa 100 1
bre 200 1
cqw 50 4
duy 50 4
Try this - and this only works in Vertica.
INTERPOLATE PREVIOUS VALUE is an outer-join predicate specific to Vertica that joins two tables on non-equal columns, using the 'last known' value in the outer-joined table to make a match succeed.
WITH
-- input, don't use in query itself
table_a (the_user,score) AS (
SELECT 'asa',100
UNION ALL SELECT 'bre',200
UNION ALL SELECT 'cqw',50
UNION ALL SELECT 'duy',50
)
,
table_b(the_user,score) AS (
SELECT 'gfh',20
UNION ALL SELECT 'ewr',80
UNION ALL SELECT 'kil',70
UNION ALL SELECT 'cvb',90
)
-- end of input - start WITH clause here
,
ranked_b AS (
SELECT
RANK() OVER(ORDER BY score DESC) AS the_rank
, *
FROM table_b
)
SELECT
a.the_user AS a_user
, a.score AS a_score
, b.the_rank AS rank_in_team_b
FROM table_a a
LEFT JOIN ranked_b b
ON a.score INTERPOLATE PREVIOUS VALUE b.score
ORDER BY 1
;
a_user|a_score|rank_in_team_b
asa | 100| 1
bre | 200| 1
cqw | 50| 4
duy | 50| 4
Simple correlated query should do:
select
a.*,
(select count(*) + 1 from table_b b where b.score > a.score) rank_in_b
from table_a a;
All you need to do is count the number of people with more score than current user in the table b and add 1 to it to get the rank.

Group by with two columns

I am trying to write a query using group by in sub query ,I referred lot of blogs but could not get all the values.
I have three tables and below is the structure of those tables.
Pet_Seller_Master
ps_id ps_name city_id
2 abc 1
3 xyz 2
4 fer 4
5 bbb 1
City_Master
city_id city_name
1 Bangalore
2 COIMBATORE
4 MYSORE
Api_Entry
api_id ps_id otp
1 2 yes
2 3
3 2 yes
4 3 yes
5 4
6 5 yes
7 5 yes
8 5 yes
Query is to get number of sellers, no of pet sellers with zero otp, no of pet sellers with 1 otp, no of pet sellers with 2 otp,no of pet sellers with otp>2 for the particular city and within date range.
Through Below query I am able to get city , psp , and zero otp
select cm.city_name,
count(ps.ps_id) as PSP,
((select count(ps1.ps_id)
FROM ps_master ps1
WHERE ps1.city = cm.city_id)-
(SELECT count(distinct ps1.ps_id)
from ps_master ps1
INNER JOIN api_entry ae ON ps1.ps_id = ae.ps_id and otp!=''
WHERE ps1.city = cm.city_id and date(timestamp) >= curdate() - INTERVAL DAYOFWEEK(curdate())+6 DAY AND date(timestamp) < curdate())) as zero_psp
from ps_master ps INNER JOIN city_master cm ON ps.city = cm.city_id and cm.city_type = 'IN HOUSE PNS'
group by city_id
Please tell me the solution to solve this query.
Thanks in advance
It's not hard to do and you were on a right track. Here is what I would use:
select c.city_name, a.otp, p.ps_name, COUNT(*) nbr
from Api_Entry a
inner join Pet_Seller_Master p on p.ps_id=a.ps_id
inner join City_Master c on p.city_id=c.city_id
group by c.city_name, a.otp, p.ps_name
Now, if you want to get the number of sellers with zero otp, you just apply where clause:
where otp <> 'yes'
If you want to get the number of pet sellers with otp>2, then you just use subquery:
select *
from (
select c.city_name, a.otp, p.ps_name, COUNT(*) nbr
from #tempA a
inner join #tempP p on p.ps_id=a.ps_id
inner join #tempC c on p.city_id=c.city_id
group by c.city_name, a.otp, p.ps_name
) g
where nbr > 2

Why outer join query not working?

Hallo,
my objective is to generate a table that shows the total of each CODE that belong to the owner, take note that each owner must have a CODE tied to it no matter the TOTAL value is zero. So there will be APP, REJ, CAN tied to each of the APPROVAL_ID.
APPROVAL_ID CODE TOTAL
----------- ---- -----
101 APP 2
101 REJ 1
101 CAN 3
102 APP 2
102 REJ 4
102 CAN 0
103 APP 0
103 REJ 0
103 CAN 4
Thus, here is the source code:
select approval_id, code, total
from (
select 'APP' code, '1' seq from dual
union all
select 'REJ' code, '2' seq from dual
union all
select 'CAN' code, '3' seq from dual
)
left outer join (
select m.approval_id, own.name, m.decision, count(*) total,
case own.channel
when 'CH1' then 'CH1'
when 'CH2' then 'CH2'
else 'Others Channel'
end the_channel
from tableM m, owner own
where m.decision in ('REJ', 'APP', 'CAN')
and own.id=m.approval_id
group by m.approval_id, own.name, m.decision, own.channel
order by m.approval_id
)
on code=decision
group by approval_id, code, total
order by approval_id;
The output from the above query is like below:
APPROVAL_ID CODE TOTAL
----------- ---- -----
101 APP 2
101 REJ 1
101 CAN 3
102 APP 2
102 REJ 4
103 CAN 4
The output of the inner query is like below:
APPROVAL_ID CODE TOTAL
----------- ---- -----
101 APP 2
101 REJ 1
101 CAN 3
102 APP 2
102 REJ 4
103 CAN 4
Something was not right to the query because I know that some of the row is having total value of zero, it should print something like (null) value in it. But why does it hidden from the view? Is there anything wrong to my query?
THanks #!
First, you need to do a cross join between your owner and your code table.
Then you do the left join.
I have modelised 3 table : Type for your 3 lines APP, REJ and CAN, then a user table, equivalent to your owner table, and a third table decision, equivalent to your tableM.
The query looks like this :
SELECT c.user_id, c.type_code, COUNT(d.id)
FROM
(
SELECT t.ID as type_id, u.id as user_id, t.CODE as type_code
FROM Type t, Userr u
) c
LEFT OUTER JOIN Decision d
ON d.user_id = c.user_id
AND d.type_id = c.type_id
GROUP BY c.user_id, c.type_code
Not tested but for yours set of table :
select a.id_own, a.code, count(m.approval_id)
from
(
select code, own.id as id_own
from (
select 'APP' code, '1' seq from dual
union all
select 'REJ' code, '2' seq from dual
union all
select 'CAN' code, '3' seq from dual
) , owner own
) a
left outer join tableM m
on a.code = m.decision
and a.id_own = m.approval_id
group by a.id_own, a.code
order by a.id_own
Note that the count(m.approval_id) will give you the number of approval_id that appear not null in the left join.
Your outer join is on code=decision. That means you get one row for each codes which don't occur as decision on the right side. Obviously you want to do your left join with a cross join of 3 codes and all distinct APPROVAL_IDs giving all the combinations of code and APPROVAL_ID on the left side.
My first guess is that
group by approval_id, code, total
should be
group by approval_id, code