MySQL query to count occurrences from multiple tables - mysql

I have a problem when I have to select everything from one table (persons) then count how many objects they own by counting their occurrences on other tables (pens, chairs, books)
The current data is as followed:
select * from persons;
+----+-------+
| id | name |
+----+-------+
| 1 | Alex |
| 2 | Brad |
| 3 | Cathy |
+----+-------+
select * from pens;
+----+-----------+
| id | person_id |
+----+-----------+
| 1 | 2 |
| 2 | 2 |
| 3 | 2 |
| 4 | 3 |
+----+-----------+
select * from chairs;
+----+-----------+
| id | person_id |
+----+-----------+
| 1 | 1 |
+----+-----------+
select * from books;
+----+-----------+
| id | person_id |
+----+-----------+
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
+----+-----------+
I want the result to be something like this
+----+-------+-----------------------+-------------------------+------------------------+
| id | name | count(pens.person_id) | count(chairs.person_id) | count(books.person_id) |
+----+-------+-----------------------+-------------------------+------------------------+
| 1 | Alex | 0 | 1 | 1 |
| 2 | Brad | 3 | 0 | 1 |
| 3 | Cathy | 1 | 0 | 1 |
+----+-------+-----------------------+-------------------------+------------------------+
I have tried using inner join and left outer join, but join gave me an empty set (since no person matches all of the objects) and left outer join gave me incorrect results:
> select persons.*, count(pens.person_id),count(chairs.person_id),count(books.person_id) from persons join pens on pens.person_id=persons.id join books on books.person_id=persons.id join chairs on chairs.person_id=persons.id group by persons.id;
Empty set (0.002 sec)
> select persons.*, count(pens.person_id),count(chairs.person_id),count(books.person_id) from persons left outer join pens on pens.person_id=persons.id left outer join books on books.person_id=persons.id left outer join chairs on chairs.person_id=persons.id group by persons.id;
# +----+-------+-----------------------+-------------------------+------------------------+
id | name | count(pens.person_id) | count(chairs.person_id) | count(books.person_id) |
# +----+-------+-----------------------+-------------------------+------------------------+
1 | Alex | 0 | 1 | 1 |
2 | Brad | 3 | 0 | 3 |
3 | Cathy | 1 | 0 | 1 |
# +----+-------+-----------------------+-------------------------+------------------------+
Any suggestions will be greatly appreciated, sorry if it's obvious, I'm fairly new at this.

Using a left join approach to subqueries on each table we can try:
SELECT
p.id,
p.name,
COALESCE(ps.cnt, 0) AS cnt_pens,
COALESCE(c.cnt, 0) AS cnt_chairs,
COALESCE(b.cnt, 0) AS cnt_books
FROM persons p
LEFT JOIN
(
SELECT person_id, COUNT(*) AS cnt
FROM pens
GROUP BY person_id
) ps
ON ps.person_id = p.id
LEFT JOIN
(
SELECT person_id, COUNT(*) AS cnt
FROM chairs
GROUP BY person_id
) c
ON c.person_id = p.id
LEFT JOIN
(
SELECT person_id, COUNT(*) AS cnt
FROM books
GROUP BY person_id
) b
ON b.person_id = p.id
ORDER BY
p.id;

Related

GROUP by version and display by row

+----------+--------+
| name | version|
+----------+--------+
| book | 2 |
| book | 1 |
| book | 1 |
| pen | 1 |
| pen | 2 |
| pen | 2 |
| pen | 2 |
| paper | 1 |
+----------+--------+
I have the table above and i want to make a query to group by name and count by version(row)
Result:
+----------+--------+--------+
| name | version| count |
+----------+--------+--------+
| book | 1 | 2 |
| book | 2 | 1 |
| pen | 1 | 1 |
| pen | 2 | 3 |
| paper | 1 | 1 |
| paper | 2 | 0 |
+----------+--------+--------+
The query would be
SELECT name, version, count(*) as count
FROM your_table_name
GROUP BY name, version
If you want all name/version combinations, then use a cross join to generate all rows and then left join to bring in the existing data:
select n.name, v.version, count(t.name)
from (select distinct name from t) n cross join
(select distinct version from t) v left join
t
on t.name = n.name and t.version = v.version
group by n.name, v.version
order by n.name, v.version;

MySQL With Recursive

I need to return my categories with the item quantity in each department, the parent categories must inherit from the child categories the quantity of products in each category.
Categories
+-------------+----------------------+--------+
| id | name | parent_id |
+-------------+----------------------+--------+
| 1 | ELECTRONICS | NULL |
| 2 | TELEVISIONS | 1 |
| 3 | TUBE | 2 |
| 4 | LCD | 2 |
| 5 | PLASMA | 2 |
| 6 | PORTABLE ELECTRONICS | 1 |
| 7 | MP3 PLAYERS | 6 |
| 8 | FLASH | 7 |
| 9 | CD PLAYERS | 6 |
| 10 | 2 WAY RADIOS | 6 |
+-------------+----------------------+--------+
Product
+-------------+----------------------+--------+
| id | product | category_id |
+-------------+----------------------+--------+
| 1 | TV LCD 32" | 4 |
| 2 | TV LCD 45" | 4 |
| 3 | TV TUBE 29" | 3 |
| 3 | IPOD | 7 |
+-------------+----------------------+--------+
Expected result
+-------------+----------------------+------------+
| id | name | level| quantity |
+-------------+----------------------+------------+
| 1 | ELECTRONICS | 1 | 4 |
| 2 | TELEVISIONS | 2 | 3 |
| 3 | TUBE | 3 | 1 |
| 4 | LCD | 3 | 2 |
| 5 | PORTABLE ELECTRONICS | 2 | 1 |
| 6 | MP3 PLAYERS | 3 | 1 |
+-------------+----------------------+------------+
I need to do using with recursive, because the speed is much higher than using nested
WITH RECURSIVE category_path (id, name, level, parent_id) AS
(
SELECT id, name, 1 level, parent_id
FROM categories
WHERE parent_id IS NULL
UNION ALL
SELECT c.id, c.name, level + 1, c.parent_id
FROM category_path AS cp
JOIN categories AS c
ON cp.id = c.parent_id
)
SELECT * FROM category_path
Time: 0.020s
using nested
SELECT
parent.id,
parent.name,
parent.parent_id,
COUNT(departaments.product_id)
FROM
categories AS node
INNER JOIN
categories AS parent
ON node.lft BETWEEN parent.lft AND parent.rgt
INNER JOIN
departaments
ON node.id = departaments.categorie_id
GROUP BY
parent.id
ORDER BY
node.lft;
Time: 1.510s
First write a query to get product count per category. This is quite simple:
with products_per_category as (
select c.id, count(p.id) as pcount
from categories c
left join products p on p.category_id = c.id
group by c.id
)
select *
from products_per_category
order by id
db-fiddle
Then write a recursive CTE to generate a transitive closure:
with recursive rcte as (
select c.id, c.id as ancestor_id
from categories c
union all
select r.id, c.parent_id
from rcte r
join categories c on c.id = r.ancestor_id
)
select *
from rcte
order by id, ancestor_id
The result will be like:
| id | ancestor_id |
| --- | ----------- |
| 1 | 1 |
| 2 | 1 |
| 2 | 2 |
...
| 9 | 1 |
| 9 | 6 |
| 9 | 9 |
| 10 | 1 |
| 10 | 6 |
| 10 | 10 |
db-fiddle
It's like you get paths from root node to each category. Eg. for 9 the path is 1->6->9
If you order it by ancestor_id first, you will get:
| id | ancestor_id |
| --- | ----------- |
| 1 | 1 |
...
| 10 | 1 |
| 2 | 2 |
| 3 | 2 |
| 4 | 2 |
| 5 | 2 |
| 3 | 3 |
...
db-fiddle
Here you can see, that category 2 (ancestor_id=2) has subcategories (id) 2,3,4,5. Note that every category has itself as subcategory. This will make the next step simpler.
Now all we need is to join the two CTEs and sum up the product counts:
with recursive products_per_category as (
select c.id, count(p.id) as pcount
from categories c
left join products p on p.category_id = c.id
group by c.id
), rcte as (
select c.id, c.id as ancestor_id
from categories c
union all
select r.id, c.parent_id
from rcte r
join categories c on c.id = r.ancestor_id
where c.parent_id is not null
)
select
c.id,
c.name,
sum(p.pcount) as quantity
from rcte r
join categories c on c.id = r.ancestor_id
left join products_per_category p on p.id = r.id
group by c.id
Result:
| id | name | quantity |
| --- | -------------------- | -------- |
| 1 | ELECTRONICS | 4 |
| 2 | TELEVISIONS | 3 |
| 3 | TUBE | 1 |
| 4 | LCD | 2 |
| 5 | PLASMA | 0 |
| 6 | PORTABLE ELECTRONICS | 1 |
| 7 | MP3 PLAYERS | 1 |
| 8 | FLASH | 0 |
| 9 | CD PLAYERS | 0 |
| 10 | 2 WAY RADIOS | 0 |
db-fiddle
If you want to remove empty categories (quantity = 0), then just replace all LEFT JOINs with INNER JOINs.
Update
To get the level, you can use a subquery in the outer SELECT:
(select count(*) from rcte r2 where r2.id = c.id) as level
db-fiddle

How to join from 3 table with condition

I have 3 table inside my database, my first table is person table its store all person name, my second table is hobby table its store all hobby of all person,the third table is referensi table store all references person and hobby.
Tabel person : Tabel hobby : Tabel referensi :
----------------- ------------------ -------------------------------------
| id | name | | id | hobby | | id | ref_person | ref_hobby |
----------------- ------------------ -------------------------------------
| 1 | Rose | | 1 | Makan | | 1 | 1 | 1 |
| 2 | Lisa | | 2 | Renang | | 2 | 1 | 3 |
| 3 | Jisoo | | 3 | Nyanyi | | 3 | 1 | 4 |
| 4 | Jennie| | 4 | Youtube| | 4 | 3 | 5 |
----------------- | 5 | Masak | -------------------------------------
------------------
I want to count all hobby by that person
Example I want select Rose : Or I want select Jisoo :
--------------------------- ---------------------------
| id | hobby | count | | id | hobby | count |
--------------------------- ---------------------------
| 1 | Makan | 1 | | 1 | Makan | 0 |
| 2 | Renang | 0 | | 2 | Renang | 0 |
| 3 | Nyanyi | 1 | | 3 | Nyanyi | 0 |
| 4 | Youtube| 1 | | 4 | Youtube| 0 |
| 5 | Masak | 0 | | 5 | Masak | 1 |
--------------------------- ---------------------------
And so forth, how do I solve this problem?
This is my query that I write but doesn't seem to work, because only data with count greater than 0 is shown.
SELECT
hobby.id,
hobby.name,
count( referensi.id ) AS count
FROM
referensi
LEFT OUTER JOIN hobby ON hobby.id = referensi.ref_hobby
JOIN person ON referensi.ref_person = person.id
WHERE person.id = 1
GROUP BY
hobby.id
Thanks in advance.
To solve this you need to JOIN referensi to person, selecting only entries in referensi corresponding to the person of interest, and then RIGHT JOIN to hobby. If there is no matching entry, the output is 0, otherwise 1. For example, for person 1:
SELECT h.id,
h.hobby,
CASE WHEN r.id IS NULL THEN 0 ELSE 1 END AS count
FROM referensi r
JOIN person p ON p.id = r.ref_person AND p.id = 1
RIGHT JOIN hobby h ON h.id = r.ref_hobby
ORDER BY h.id
This can also be implemented with a correlated subquery:
SELECT h.id,
h.hobby,
EXISTS (SELECT * FROM referensi r WHERE r.ref_hobby = h.id AND r.ref_person = 1) AS count
FROM hobby h
If a person/hobby tuple can appear in the referensi table more than once, you do need to do a COUNT:
SELECT h.id,
h.hobby,
COUNT(r.id) AS count
FROM referensi r
JOIN person p ON p.id = r.ref_person AND p.id = 1
RIGHT JOIN hobby h ON h.id = r.ref_hobby
GROUP BY h.id
Output (for all three queries on your sample data):
id hobby count
1 Makan 1
2 Renang 0
3 Nyanyi 1
4 Youtube 1
5 Masak 0
Demo on SQLFiddle
You can try to use condition aggravate function with OUTER JOIN
setting condition in CASE WHEN
Query 1:
SELECT
hobby.id,
hobby.name,
COUNT(CASE WHEN person.id = 3 THEN 1 END) AS count
FROM
hobby
LEFT JOIN referensi ON hobby.id = referensi.ref_hobby
LEFT JOIN person ON referensi.ref_person = person.id
GROUP BY
hobby.id,
hobby.name
Results:
| id | name | count |
|----|---------|-------|
| 1 | Makan | 0 |
| 2 | Renang | 0 |
| 3 | Nyanyi | 0 |
| 4 | Youtube | 0 |
| 5 | Masak | 1 |
You want to start your joining from hobby table, and use LEFT JOINs to optionnaly bring up the matching records in other tables.
SELECT
h.id,
h.hobby,
count( p.id ) AS count
FROM
hobby h
LEFT JOIN referensi r ON h.id = r.ref_hobby
LEFT JOIN person p ON r.ref_person = p.id AND p.id = 1
WHERE p.name is NULL OR p.name = 'Rose'
GROUP BY h.id, h.hobby
It is also a good practice to use table aliases, I added them to your query.
Demo on DB Fiddle for user Rose :
| id | hobby | count |
| --- | ------- | ----- |
| 1 | Makan | 1 |
| 2 | Renang | 0 |
| 3 | Nyanyi | 1 |
| 4 | Youtube | 1 |
| 5 | Masak | 0 |

replace IDs from GROUP_CONCAT column with names

I have three tables which I successfully joined with the following sql query
SELECT `bonuses`.`id`, `bonuses`.`bonus_name`, `bonuses`.`size`, creatorName.`name`, GROUP_CONCAT(DISTINCT bonus_user.user_id ORDER BY bonus_user.user_id SEPARATOR ', ') as bonusUsers from `bonuses`
inner join `users` as creatorName on `bonuses`.`created_from` = creatorName.`id`
inner join `bonus_user` on `bonuses`.`id` = `bonus_user`.`bonus_id`
group by `bonuses`.`id`
The result I get is as following. As a further step I want to replace the ids in the column "bonusUsers" by the names from the users table. How do I manage this?
+----+--------------+------+--------------+--------------+
| id | bonus_name | size | name | bonusUsers |
+----+--------------+------+--------------+--------------+
| 3 | Bonus Test 3 | 5 | Test1 | 1, 2, 3 |
| 4 | Bonus Test 4 | 3 | Test1 | 1, 2, 3 |
+----+--------------+------+--------------+--------------+
users
+----+-------+
| id | name |
+----+-------+
| 1 | Test1 |
| 2 | Test2 |
| 3 | Test3 |
+----+-------+
bonuses
+----+--------------+------+--------------+
| id | bonus_name | size | created_from |
+----+--------------+------+--------------+
| 1 | Bonus Test 1 | 1 | 1 |
| 2 | Bonus Test 2 | 1 | 1 |
| 3 | Bonus Test 3 | 5 | 1 |
| 4 | Bonus Test 4 | 3 | 1 |
+----+--------------+------+--------------+
bonus_user
+----+----------+------------+
| id | bonus_id | bonus_user |
+----+----------+------------+
| 1 | 3 | 1 |
| 2 | 3 | 2 |
| 3 | 3 | 3 |
| 4 | 4 | 1 |
| 5 | 4 | 2 |
| 6 | 4 | 3 |
+----+----------+------------+
Do another join with users table
SELECT `b`.`id`, `b`.`bonus_name`, `b`.`size`, u.`name`,
GROUP_CONCAT(DISTINCT bu1.`name` ORDER BY bu.user_id SEPARATOR ', ') as bonusUsers
from `bonuses` b
inner join `users` as u on `b`.`created_from` = u.`id`
inner join `bonus_user` bu on `b`.`id` = `bu`.`bonus_id`
inner join `users` as bu1 on `bu`.`user_id` = bu1.`id`
group by `b`.`id`, `b`.`bonus_name`, `b`.`size`, u.`name`
Demo
You need to aliase the users table and join it to the bonus_user tables to get the name of the user from the aliased table
SELECT `bonuses`.`id`, `bonuses`.`bonus_name`, `bonuses`.`size`, creatorName.`name`,
GROUP_CONCAT(DISTINCT user_names.name ORDER BY bonus_user.user_id SEPARATOR ', ') as bonusUsers from `bonuses`
inner join `users` as creatorName on `bonuses`.`created_from` = creatorName.`id`
inner join `bonus_user` on `bonuses`.`id` = `bonus_user`.`bonus_id`
inner join `users AS user_names` on `bonus_user`.`bonus_user` = `user_names`.`id`
group by `bonuses`.`id`
This may help as well https://www.w3schools.com/sql/sql_alias.asp

Mysql count column values and merge columns

I was having problems in creating counting rows by grouping based on a given field value.
For example: I have a Table A structure like this:
+------+------------+
| id | Person |
+------+------------+
| 1 | "Sandy" |
| 2 | "Piper" |
| 3 | "Candy" |
| 4 | "Pendy" |
+------------+------+
Also I have a Table B structure like this:
+------+------------+---------+
| id | Person | Point |
+------+------------+---------+
| 1 | "Sandy" | 10 |
| 2 | "Piper" | 20 |
| 3 | "Candy" | 30 |
| 4 | "Sandy" | 10 |
| 5 | "Piper" | 20 |
| 6 | "Zafar" | 30 |
+------------+------+---------+
And needed a result like:
+------+------------+---------+
| id | Person | Point |
+------+------------+---------+
| 1 | "Piper" | 40 |
| 2 | "Candy" | 30 |
| 3 | "Zafar" | 30 |
| 4 | "Sandy" | 20 |
| 5 | "Pendy" | 0 |
+------------+------+---------+
I hope the table examples are itself self-explanatory.
SELECT person
, SUM(point) total
FROM
( SELECT person,point FROM table_b
UNION
ALL
SELECT person,0 FROM table_a
) x
GROUP
BY person
ORDER
BY total DESC;
It is a simple left join with a group by
select tableA.person, sum(tableB.points) from tableA left join tableB on tableA.person = tableB.person group by tableA.person
union
select tableB.person, sum(tableB.points) from tableB left join tableA on tableA.person = tableB.person where tableA.id is null group by tableA.person
I think below sql useful to you.
select a.id, a.Person,b.total_point from (
select id, Person from tablea) as a join
(select Person, sum(Point) as total_point from tableb group by person) as b on a.person =b.person
Thank you