SQL query to select columns from multiple tables with conditions on Group By - sql

I have 3 Tables with relationships:
TableA:
Party_Number Account_Number Email_Code Relation_Code
1111 A00071 null B
1111 A00071 null C
1111 A00071 null D
1111 A00072 140 D
1111 A00073 140 C
1111 A00074 140 C
1111 A00075 null B
TableB:
Account_Number Date
A00071 8/8/2020
A00072 null
A00073 null
A00074 null
A00075 null
TableC:
Party_Number Email
1111 abc#gmail.com
I need to join 3 tables to get the following result (only records where "Relation_Code" is 'C' or 'D'):
Party_Number Account_Number Email_Code Relation_Code Date Email
1111 A00071 null C 8/8/2020 abc#gmail.com
1111 A00071 null D 8/8/2020 abc#gmail.com
1111 A00072 140 D null abc#gmail.com
1111 A00073 140 C null abc#gmail.com
1111 A00074 140 C null abc#gmail.com
I wrote this query to get the result:
Select A.Party_Number, A.Account_Number, A.Relation_Code, A.Email_Code,
B.Date, C.Email
from TableA A, TableB B, TableC C
Where A.Account_Number= B.Account_Number
AND A.Party_Number = C.Party_Number
AND A.Relation_Code in ('C','D')
Order By A.Account_Number
But there can be rows with same Account_Number, but different Relation_Code ('C' and 'D'). For ex (A00071).
If there are duplicate Account_Number, I need to select only Account_Number where Relation_Code is 'D'.
How do I write a SQL query to join multiple tables and group by a condition?

Use standard joins! This helps formulating the logic and separating the joining conditions from other filtering predicates.
Here, it seems like, starting from a filtered on column relation_code, you want to allow "missing" relationships in b and c. We would phrase it with left joins:
select a.party_number, a.account_number, a.relation_code, a.email_code,
b.date, c.email
from tablea a
left join tableb b on b.account_number = a.account_number
left join tablec c on c.party_number = a.party_number
where a.relation_code in ('C','D')
order by a.account_number
If there are duplicate Account_Number, I need to select only Account_Number where Relation_Code is 'D'.
For this, we could use window functions to pre-filter a:
select a.party_number, a.account_number, a.relation_code, a.email_code,
b.date, c.email
from (
select a.*,
row_number() over(partition by account_number order by relation_code desc) rn
from tablea a
where relation_code in ('C','D')
) a
left join tableb b on b.account_number = a.account_number
left join tablec c on c.party_number = a.party_number
where a.rn = 1
order by a.account_number

Related

Select multiple count(*) in multiple tables with single query

I have 3 tables:
Basic
id
name
description
2
Name1
description2
3
Name2
description3
LinkA
id
linkA_ID
2
344
3
3221
2
6642
3
2312
2
323
LinkB
id
linkB_ID
2
8287
3
42466
2
616422
3
531
2
2555
2
8592
3
1122
2
33345
I want to get results as the table below:
id
name
description
linkA_count
linkB_count
2
Name1
description2
3
2
3
Name2
description3
5
3
my query:
SELECT
a.id
,a.name
,a.description
,COUNT(b.linkA_ID) AS linkA_count
,COUNT(c.linkB_ID) AS linkb_count
FROM
basic a
JOIN linkA b on (a.id = b.id)
JOIN linkb c on (a.id = c.id)
GROUP BY
a.id
,a.name
,a.description
Result from the query is count of linkA always same as linkB
A more traditional approach is to use "derived tables" (subqueries) so that the counts are performed before joins multiply the rows. Using left joins allows for all id's in basic to be returned by the query even if there are no related rows in either joined tables.
select
basic.id
, coalesce(a.LinkACount,0) LinkACount
, coalesce(b.linkBCount,0) linkBCount
from basic
left join (
select id, Count(linkA_ID) LinkACount from LinkA group by id
) as a on a.id=basic.id
left join (
select id, Count(linkB_ID) LinkBCount from LinkB group by id
) as b on b.id=basic.id
Try This (using SubQuery)
SELECT
basic.id
,basic.name
,basic.description
,(select Count(linkA_ID) from LinkA where LinkA.id=basic.id) as LinkACount
,(select Count(linkB_ID) from LinkB where LinkB.id=basic.id) as LinkBCount FROM basic
Method 2 (Try CTE)
with a as(select id,Count(linkA_ID)LinkACount from LinkA group by id)
, b as (select id,Count(linkB_ID)LinkBCount from LinkB group by id)
select basic.id,a.LinkACount,b.linkBCount
from basic
join a on (a.id=basic.id)
join b on (b.id=basic.id)
If you only select from your table you see why your query cannot work.
SELECT
*
FROM
basic a
JOIN linkA b on (a.id = b.id)
JOIN linkb c on (a.id = c.id)
WHERE a.ID = 3
=> just use distinct in your count
SELECT
a.id
,a.name
,a.description
,COUNT(DISTINCT(b.linkA_ID)) AS linkA_count
,COUNT(DISTINCT(c.linkB_ID)) AS linkb_count
FROM
basic a
JOIN linkA b on (a.id = b.id)
JOIN linkb c on (a.id = c.id)
GROUP BY
a.id
,a.name
,a.description

Select count of rows in two other tables

I have 3 tables. The main one in which I want to retrieve some information and two others for row count only.
I used a request like this :
SELECT A.*,
COUNT(B.id) AS b_count
FROM A
LEFT JOIN B on B.a_id = A.id
WHERE A.id > 50 AND B.ID < 100
GROUP BY A.id
from Gerry Shaw's comment here. It works perfectly but only for one table.
Now I need to add the row count for the third (C) table. I tried
SELECT A.*,
COUNT(B.id) AS b_count
COUNT(C.id) AS c_count
FROM A
LEFT JOIN B on B.a_id = A.id
LEFT JOIN C on C.a_id = A.id
GROUP BY A.id
but, because of the two left joins, my b_count and my c_count are false and equal to each other. In fact my actual b_count and c_count are equal to real_b_count*real_c_count. Any idea of how I could fix this without adding a lot of complexity/subqueries ?
Data sample as requested:
Table A (primary key : id)
id | data1 | data2
------+-------+-------
1 | 0,45 | 0,79
----------------------
2 | -2,24 | -0,25
----------------------
3 | 1,69 | 1,23
Table B (primary key : (a_id,fruit))
a_id | fruit
------+-------
1 | apple
------+-------
1 | banana
--------------
2 | apple
Table C (primary key : (a_id,color))
a_id | color
------+-------
2 | blue
------+-------
2 | purple
--------------
3 | blue
expected result:
id | data1 | data2 | b_count | c_count
------+-------+-------+---------+--------
1 | 0,45 | 0,79 | 2 | 0
----------------------+---------+--------
2 | -2,24 | -0,25 | 1 | 2
----------------------+---------+--------
3 | 1,69 | 1,23 | 0 | 1
There are two possible solutions. One is using subqueries behind SELECT
SELECT A.*,
(
SELECT COUNT(B.id) FROM B WHERE B.a_id = A.id AND B.ID < 100
) AS b_count,
(
SELECT COUNT(C.id) FROM C WHERE C.a_id = A.id
) AS c_count
FROM A
WHERE A.id > 50
the second are two SQL queries joined together
SELECT t1.*, t2.c_count
FROM
(
SELECT A.*,
COUNT(B.id) AS b_count
FROM A
LEFT JOIN B on B.a_id = A.id
WHERE A.id > 50 AND B.ID < 100
GROUP BY A.id
) t1
JOIN
(
SELECT A.*,
COUNT(C.id) AS c_count
FROM A
LEFT JOIN C on C.a_id = A.id
WHERE A.id > 50
GROUP BY A.id
) t2 ON t1.id = t2.id
I prefer the second syntax since it clearly shows the optimizer that you are interested in GROUP BY, however, the query plans are usually the same.
If tables B & C also have their own key fields, then you can use COUNT DISTINCT on the primary key rather than foreign key. That gets around the multi-line problem you see on link to several tables. If you can post the table structures then we can advise further.
Try something like this
SELECT A.*,
(SELECT COUNT(B.id) FROM B WHERE B.a_id = A.id) AS b_count,
(SELECT COUNT(C.id) FROM C WHERE C.a_id = A.id) AS c_count
FROM A
That is the easier way I can think:
Create table #a (id int, data1 float, data2 float)
Create table #b (id int, fruit varchar(50))
Create table #c (id int, color varchar(50))
Insert into #a
SELECT 1, 0.45, 0.79
UNION ALL SELECT 2, -2.24, -0.25
UNION ALL SELECT 3, 1.69, 1.23
Insert into #b
SELECT 1, 'apple'
UNION ALL SELECT 1, 'banana'
UNION ALL SELECT 2, 'orange'
Insert into #c
SELECT 2, 'blue'
UNION ALL SELECT 2, 'purple'
UNION ALL SELECT 3, 'orange'
SELECT #a.*,
(SELECT COUNT(#b.id) FROM #b where #b.id = #a.id) AS b_count,
(SELECT COUNT(#c.id) FROM #c where #c.id = #a.id) AS b_count
FROM #a
ORDER BY #a.id
Result:
id data1 data2 b_count b_count
1 0,45 0,79 2 0
2 -2,24 -0,25 1 2
3 1,69 1,23 0 1
If table b and c have unique id, you can try this:
SELECT A.*,
COUNT(distinct B.fruit) AS b_count,
COUNT(distinct C.color) AS c_count
FROM A
LEFT JOIN B on B.a_id = A.id
LEFT JOIN C on C.a_id = A.id
GROUP BY A.id
See SQLFiddle MySQL demo.

Group by the union of two columns

How can GROUP BY based on the union of two columns be achieved performantly? There may be NULL values in either column. Something like (obviously this doesn't work):
SELECT a.val, b.val
FROM a
LEFT JOIN b on a.id = b.id
GROUP BY UNION(a.val, b.val)
With results like:
a.val | b.val
-----------
1 1
2 2
NULL 3
4 NULL
5 5
Thanks!
Why can't you use NVL
SELECT NVL(a.val, b.val) FROM a LEFT JOIN b on a.id = b.id
GROUP BY NVL(a.val, b.val)

Left Join Preserving NULL Records

Say I have these tables:
TableA TableB:
id name id name
-- ---- -- ----
1 Pirate 1 Rutabaga
2 Monkey 2 Pirate
3 Ninja 3 Darth Vader
4 Spaghetti 4 Ninja
When I Left Join I get the following:
SELECT * FROM TableA
LEFT OUTER JOIN TableB
ON TableA.name = TableB.name
id name id name
-- ---- -- ----
1 Pirate 2 Pirate
2 Monkey null null
3 Ninja 4 Ninja
4 Spaghetti null null
But what I want is a different "Left Join" where I get the following:
id name id name
-- ---- -- ----
1 Pirate 2 Pirate
1 Pirate null null
2 Monkey null null
3 Ninja 4 Ninja
3 Ninja null null
4 Spaghetti null null
How would I get this augmented "Left Join" that returns null records for all left table entries along with the joined values?
It sounds to me like this is what you're looking for:
SELECT TableA.id, TableA.name, TableB.id, TableB.name
FROM TableA
INNER JOIN TableB
ON TableA.name = TableB.name
UNION
SELECT id, name, null, null
FROM TableA
Note that you don't need a LEFT JOIN in there because the second query obviates the need for doing that. You are asking for the INNER JOIN results plus records with TableA's data with just nulls for TableB's.
You would use union all. Of course, there is an issue with the ids. Something like this might work:
select row_number() over (order by (select NULL)) as id, name, bid, bname
from ((select a.id, a.name, b.id as bid, b.name as bname
from tableA a inner join
tableB b
on a.name = b.name
) union all
(select a.id, a.name, NULL, NULL
from tableA a
)
) ab;
This doesn't preserve the first id column. But then again, it is hard to say what that is for.
EDIT:
Actually, this might preserve it the way you want it:
select id, name, bid, bname
from ((select a.id, a.name, b.id as bid, b.name as bname
from tableA a inner join
tableB b
on a.name = b.name
) union all
(select a.id + (max(id) over ()), a.name, NULL, NULL
from tableA a
)
) ab;

Sql Case Column 1 and Column same value then apply calculation

I would like apply calculation in CASE Statement, but the result is not I'm expected and I'm not sure how to apply the query to filter date(depend on month). What is the problem with my query?? Please help... Thank you
this is my query
SELECT DISTINCT a.SERVICE_CODE,
CASE WHEN
b.SERVICE_CODE IS NULL THEN a.AMT ELSE SUM (a.AMT) - SUM(b.AMT) END AS TOTAL_AMT
FROM Table a
LEFT OUTER JOIN Table b ON (a.SERVICE_CODE = b.SERVICE_CODE)
WHERE b.SERVICE_CODE >='3000' AND c.SERVICE_CODE >='3000'
Table a
Invoice_No date Service_code Amt
001 1/7/2014 6000 300
002 1/8/2014 6003 700
003 5/8/2014 6003 100
004 10/8/2014 6005 1000
Table b
Credit_No date Service_code Amt
c100 1/7/2014 6000 300
c200 13/8/2014 6003 700
Desired output
Service_code Total_Amt
6003 100
6005 1000
Thank you
try like this
In MYSQL
select t1.SERVICE_CODE,(t1.amt- case when c.amt is null then 0 else c.amt end)
as AMT from t1 c right outer join (select t.Invoice_No,SERVICE_CODE,
sum(amt) as amt from t group by SERVICE_CODE) as t1
on c.SERVICE_CODE=t1.SERVICE_CODE
inner join tablea a on a.invoice_no=t1.Invoice_No
Fiddle
Try this:
select
bcode,
case
when ccode is null then bamt
else bamt - camt
end as amt
from
(select service_code bcode, sum(amt) bamt
from b
where service_code >= 3000
group by service_code) b
left join
(select service_code ccode, sum(amt) camt
from c
where service_code >= 3000
group by service_code) c on b.bcode = c.ccode
order by bcode
Going by sample data, table b may have values for service_code that table c may not. Therefore, use a left outer join to link b and c. Also, since invoice_no does not need to appear in the output, you do not need to join to a at all.
Demo