Joining two tables with aggregrate functions - mysql

I have three tables I need to combine. My first table(Foreign table) has the id's for literally all objects. Table A has people upvote id's from Foreign Table and Table B has people downvote id's from Foreign Table.
I want to combine these 3 tables into 1 big table with counts in them. See the result table. Count table A refers to the amount of times the specific id is present in table A. How do I combine these to the final table?
Foreign Table
id
---
1
2
3
...
Table A
id | foreign_id
-----------------------
1 3
2 3
3 1
Table B
id | foreign_id
-----------------------
1 3
2 1
3 2
Result Table
id | count table A | count table B
------------------------------------
1 1 1
2 0 1
3 2 1

Probably the fastest method is a a correlated subquery:
select f.*,
(select count(*) from a where a.foreign_id = f.id) as a_cnt,
(select count(*) from b where b.foreign_id = f.id) as b_cnt
from foreign f;
This avoids the GROUP BY on the outer query. As a bonus it can use an index on a(foreign_id) and b(foreign_id) which also benefit performance.

for manage properly the 0 result you could use sum and check for null
select f.id
, sum( when a.foreign_id is null then 0 else 1 end ) count_a
, sum( when b.foreign_id is null then 0 else 1 end ) count_b
from Foreign f
LEFT JOIN TableA a f.id = a.foreign_id
LEFT JOIN TableA a f.id = b.foreign_id
GROUP BY f.id

You can join, aggregate by the primary key of the foreign table and count distinct values of the primary keys of table_a and table_b:
select
f.id,
count(distinct a.id) count_table_a,
count(distinct b.id) count_table_b
from foreign_table f
left join table_a a on a.foreign_id = f.id
left join table_b b on b.foreign_id = f.id
group by f.id
count() returns 0 where there is no match, so there is no need for special processing unlike when suming.
Demo on DB Fiddle:
id | count_table_a | count_table_b
-: | ------------: | ------------:
1 | 1 | 1
2 | 0 | 1
3 | 2 | 1

I guess below would work
Select f.id, count(a.*),count(b.*) from
foreign f
Left Join
tableA a on f.id=a.foreignid
Join
TableB b on f.id=b.foreignid
Group by f.id
Or
Select id, * from foreign f left join
(
Select foreignid, count(*) from tableA a
group
by foreignid join
Select foreignid, count(*) from tableB b
group
by foreignid on
a.foreignid<b.foreignid) t on
f.id=t.foreignid;

Related

How to left join when only matched one record from right table

I want to know How to left join when only matched one record from right table.
For example,
tableA
id
value
1
34
2
42
3
60
tableB
id
value
tableA_id
1
20
1
2
31
1
3
50
2
I want to get result like below using left outer join.
tableA_id
tableA_value
tableB_value
1
34
null
2
42
50
3
60
null
tableB_value of first row is null, because tableA.id = tableB.tableA_id matches multiple records.
how to solve it ?
Thanks.
You can make use of COUNT() as an analytic function to keep track of how many times a tableA_id occurs in the A table:
SELECT a.id AS tableA_id, a.value AS tableA_value, b.value AS tableB_value
FROM tableA a
LEFT JOIN
(
SELECT *, COUNT(*) OVER (PARTITION BY tableA_id) cnt
FROM tableB
) b
ON a.id = b.tableA_id AND b.cnt = 1
ORDER BY a.id;
Demo
You can do this using aggregation on the b table as well:
SELECT a.id AS tableA_id, a.value AS tableA_value, b.value AS tableB_value
FROM tableA a LEFT JOIN
(SELECT tableA_id, MAX(value) as value
FROM tableB
GROUP BY tableA_id
HAVING COUNT(*) = 1
) b
ON a.id = b.tableA_id
ORDER BY a.id;
This works because if there is only one row in B for a given id, then MAX() returns the value on that row.

SQL count rows in third table

A row in Table A can be linked to many rows in Table B. A row in Table B will be linked to either one or zero rows in Table C.
For a given row in Table A, I would like to count the (indirectly) linked rows in Table C.
I would like to return this for each row in Table A. The below doesn't give any errors, but doesn't give the correct value.
SELECT
*,
(
SELECT count(*)
FROM TABLE_A
INNER JOIN TABLE_B ON TABLE_A.id = TABLE_B.foreignKeyA
INNER JOIN TABLE_C ON TABLE_B.id = TABLE_C.foreignKeyB
) as cCount
FROM TABLE_A
Sample data:
TABLE_A
id
1
2
TABLE_B
id foreignKeyA
1 1
2 1
3 2
4 2
TABLE_C
id foreignKeyB
1 3
2 4
Should return (for the rows of Table A):
id cCount
1 0
2 2
I would suggest a correlated subquery:
SELECT a.*,
(SELECT count(*)
FROM TABLE_B b JOIN
TABLE_C c
ON c.foreignKeyB = b.id
WHERE b.foreignKeyA = a.id
) as aCount
FROM TABLE_A a;
Obviously, if you want the count for each row in TABLE_C, then you would adjust the table names and conditions.
You can do this with JOINs, but you need outer joins and an overall aggregation.
> with table_count as (
Select count(*) as cnt, TableB_FK
From TableB B
JOIN TableA A on A.FK = B.FK
Group By B.FK
)
select C.*, nvl(t.cnt,0)
from TableC C
left join table_count T on T.tableB_fk = C.FK
Table_count has aggregate count for foreign Keys.
Left Join table C with table count and replace null with 0
Change the INNER joins to LEFT joins for the case there are not any linked rows to TABLE_C, in which case you want 0 as result:
SELECT TABLE_A.id, count(TABLE_C.id) cCount
FROM TABLE_A
LEFT JOIN TABLE_B ON TABLE_B.foreignKeyA = TABLE_A.id
LEFT JOIN TABLE_C ON TABLE_C.foreignKeyB = TABLE_B.id
GROUP BY TABLE_A.id
See the demo.
Results:
| id | cCount |
| --- | ------ |
| 1 | 0 |
| 2 | 2 |

Two right joins

I am trying to select all records in TABLEC and its equivalent value in TABLEA or TABLEB using right join. I am using MYSQL 5.5.47.
--Table data as follows
TABLEA TABLEB TABLEC
ID FNAME ID MNAME ID LNAME
0 ANOOP 0 N 0 SINGH
1 BIMA 2 SITA 3 RAJ
4 CIMI 4 B 5 KUMAR
6 RAVI 5 A 6 D
--Using below query and trying to select all records in TABLEC and its equivalent value in TABLEA or TABLEB
SELECT A.FNAME, B.MNAME, C.LNAME
FROM TABLEA AS A
RIGHT JOIN TABLEB AS B ON A.ID = B.ID
RIGHT JOIN TABLEC AS C ON C.ID = B.ID
--I am getting the following result
ANOOP N SINGH
NULL NULL RAJ
NULL A KUMAR
***NULL*** NULL D
The highlighted value doesn’t show the value as 'RAVI' instead it shows NULL in MYSQL 5.5.47. I tried to modify the '=' condition in second join related to C & A but still no luck. What am I doing wrong here? How do I get the value 'RAVI' in place of NULL? Any suggestion would be highly helpful.
DROP TABLE IF EXISTS table_a;
DROP TABLE IF EXISTS table_b;
DROP TABLE IF EXISTS table_c;
CREATE TABLE table_a
(id INT NOT NULL PRIMARY KEY
,fname VARCHAR(12) NULL
);
INSERT INTO table_a VALUES
(0,'ANOOP'),
(1,'BIMA'),
(4,'CIMI'),
(6,'RAVI');
CREATE TABLE table_b
(id INT NOT NULL PRIMARY KEY
,mname VARCHAR(12) NULL
);
INSERT INTO table_b VALUES
(0,'N'),
(2,'SITA'),
(4,'B'),
(5,'A');
CREATE TABLE table_c
(id INT NOT NULL PRIMARY KEY
,lname VARCHAR(12) NULL
);
INSERT INTO table_c VALUES
(0,'SINGH'),
(3,'RAJ'),
(5,'KUMAR'),
(6,'D');
SELECT a.fname
, b.mname
, c.lname
FROM table_c c
LEFT
JOIN table_a a
ON a.id = c.id
LEFT
JOIN table_b b
ON b.id = c.id;
+-------+-------+-------+
| fname | mname | lname |
+-------+-------+-------+
| ANOOP | N | SINGH |
| NULL | NULL | RAJ |
| NULL | A | KUMAR |
| RAVI | NULL | D |
+-------+-------+-------+
4 rows in set (0.02 sec)
You are trying to select all records in TABLEC and its equivalent value in TABLEA or TABLEB using right join. So Table A and B is joined to Table c records. So we need to use Left join(you will get all records of Table C and common records of Table A and B). More info please ref this link
SELECT
ifnull(A.FNAME,""),
ifnull(B.MNAME,""),
ifnull(C.LNAME,"")
FROM
TABLEA AS A
LEFT JOIN
TABLEB AS B
ON
A.ID = B.ID
LEFT JOIN
TABLEC AS C
ON
C.ID = B.ID
The problem is you don't have a table that contains all the ids. So you have to make one. Then you can join from that.
Get all the ids with this query
SELECT ID FROM TABLEA
UNION
SELECT ID FROM TABLEB
UNION
SELECT ID FROM TABLEC
Now we can use this query/table of ids to join the others
SELECT A.FNAME, B.MNAME, C.LNAME
FROM (
SELECT ID FROM TABLEA
UNION
SELECT ID FROM TABLEB
UNION
SELECT ID FROM TABLEC
) I
LEFT JOIN TABLEA A ON I.ID = A.ID
LEFT JOIN TABLEB B ON I.ID = B.ID
LEFT JOIN TABLEC C ON I.ID = C.ID
Of course if you had another table (TABLEID) that had a list of all IDs you could use that instead of the sub-query above. It might be your model has such a table, but we won't know unless you tell us.
As you said "select all records in TABLEC and its equivalent value in TABLEA or TABLEB", so you need to join the C with A and C with B. So your need to update your query as :
SELECT A.FNAME, B.MNAME, C.LNAME
FROM TABLEC AS C RIGHT JOIN TABLEB AS B
ON B.ID = C.ID
RIGHT JOIN TABLEA AS A
ON C.ID = A.ID
If you want all the record that exist in A, B and C. The NULL value will shown for the record which doesn't have the value,
SELECT A.FNAME, B.MNAME, C.LNAME
FROM (
TABLEA AS A
LEFT JOIN TABLEB AS B ON B.ID = A.ID
)
RIGHT JOIN TABLEC AS C ON ( C.ID = B.ID
OR B.ID = NULL
OR A.ID = C.ID )
WHERE 1
Use this works perfectly
SELECT A.FNAME, B.MNAME, C.LNAME
FROM TABLEC AS C
LEFT JOIN TABLEA AS A ON (A.ID = C.ID)
LEFT JOIN TABLEB AS B ON (B.ID = C.ID)

Counting all childs and sub-childs of each node in a hierarchy

I have this hierarchy of tables :
Table A
|
| Table B
|
| Table C
|
| Table D
For a given row in Table A, let's say the row with ID=1, I want to get this output :
ID | childB | ChildC | childD
-------------------------------
1 | x | x | x
Where childB is the number of children in Table B, ChildC are the children in Table C of the children found in Table B..., etc.
I want to get this output by one sql query. Now I can get only the counting of the children in Table B using this query :
SELECT a.ID, (SELECT
COUNT(b.parentID)
FROM TableB AS b
WHERE b.parentID= a.ID)
AS childB
FROM TableA a
WHERE a.ID =1
if you want it for a specific ID (as you mentioned for example ID=1) you can left join them all on idparent and id and use count(distinct):
select a.ID,
count(distinct b.id) childB,
count(distinct c.id) childC,
count(distinct d.id) childD
from tableA a
left join tableB b on b.parentID = a.ID
left join tableC c on c.parentID = b.ID
left join tableD d on d.parentID = c.ID
where a.ID=1
group by a.ID;
here is a fiddle DEMO.

MYSQL - Select total amount of 3 joined table

I have 3 tables:
table a => id, name
table b => id, id_a, amount
table c => id, id_a, amount
How can I select table a and the total amount from table b, and table c?
I already have tried:
SELECT a.`name`, SUM(b.`amount`) AS Total
FROM a
LEFT JOIN b ON a.`id` = b.`id_a`
UNION
SELECT a.`name`, SUM(c.`amount`) AS Total
FROM a
RIGHT JOIN c ON a.`id` = c.`id_a`
GROUP BY a.`id`
But it shows multiple records from table a where there is id_a value in both table b and table c.
FYI: if table a is inserted, so table b or table c or both table b and table c will be inserted the detail. So there is no possible table a has no detail in table a or table b
here is the example data
table a:
id | name
1 | name 1
2 | name 2
table b:
id | id_a | amount
1 | 1 | 300
2 | 1 | 200
table c:
id | id_a | amount
1 | 1 | 200
2 | 1 | 100
3 | 2 | 300
4 | 2 | 100
Preferred Result:
name | amount
name 1 | 800
name 2 | 400
UPDATE THE ANSWER => Thanks to #Jens
SELECT d.NAME, SUM(d.Total) FROM (
SELECT a.id AS id , a.`name` AS NAME, SUM(b.`amount`) AS Total
FROM a
LEFT JOIN b ON a.`id` = b.`id_a`
GROUP BY a.`id`
UNION
SELECT a.`id` AS id, a.`name`, SUM(c.`amount`) AS Total
FROM a
RIGHT JOIN c ON a.`id` = c.`id_a`
GROUP BY a.`id`
) AS d
GROUP BY d.`id`
ANOTHER ANSWER FROM #Alex IS WORKING TOO AND SIMPLER
Thanks to both #Jens and #Alex. It's increase my knowledge
Try
select d.name, sum(d.total) from (
SELECT a.id as id , a.`name` as name, b.`amount` AS Total
FROM a
LEFT JOIN b ON a.`id` = b.`id_a`
UNION
SELECT a.id as id a.`name`, c.`amount` AS Total
FROM a
RIGHT JOIN c ON a.`id` = c.`id_a`
) as d
GROUP BY d.`id`
UPDATED:
SELECT d.NAME, SUM(d.Total) FROM (
SELECT a.id AS id , a.`name` AS NAME, SUM(b.`amount`) AS Total
FROM a
LEFT JOIN b ON a.`id` = b.`id_a`
GROUP BY a.`id`
UNION
SELECT a.`id` AS id, a.`name`, SUM(c.`amount`) AS Total
FROM a
RIGHT JOIN c ON a.`id` = c.`id_a`
GROUP BY a.`id`
) AS d
GROUP BY d.`id`
Have you tried this?
SELECT a.name, sum(b.amount), sum(c.amount)
FROM a
JOIN b on a.id = b.id_a
JOIN c on a.id = c.id_a
GROUP BY a.id
not sure about the relations, but you can replace JOIN with LEFT JOIN if that's your case.
UPDATE
This should be working:
SELECT a.name as "name", sum(d.amount) as "amount"
FROM a
JOIN (SELECT id_a, amount FROM b UNION ALL SELECT id_a, amount FROM c) as d
on d.id_a = a.id
group by a.name
This yields the following results:
name1 | 800
name2 | 400