SQL How to count items by association - mysql

Table users
ID name country
--- ----- -----
100 John 1
101 Mark 4
102 Peter 3
103 Lucy 3
Table followers
ID folowerId followedId
--- --------- ------
1 102 101
2 103 101
3 102 100
4 100 101
5 100 102
5 103 100
Table country
ID name
--- -----
1 China
2 Usa
3 Italia
4 India
How to get the number of followers for a specific user grouped by country ?
Example output for user 101:
country count
------- -----
China 1
Italia 2
I have tried this:
SELECT
User.name, COUNT(Follower.id) AS FollowerCount
FROM
followers AS Follower
JOIN users AS User ON Follower.UserId = User.id
GROUP BY
User.country;
but it's not working

You should use a count and group by
select c.name, count(*)
from followers b
left join users as a on b.folloswerId = a.id
left join country on a.country = c.id
where b.followedId = 101
group by c.name

Related

Count and Distinct SQL query

I have a table of Books, that contains the following columns:
Book_Id User_Id
001 1
002 2
001 1
004 2
005 3
006 3
007 2
008 2
009 1
Where :
Book_Id - identifier of a book that a user read; User_Id - identifier
of a reader/user.
Let's assume that User1 read books three times, but 2 of them were same, so the user 1 read 2 distinct books (001 and 009). User 2 read 4 distinct books, while user 3 read 2 distinct books.
In overall, there are 2 users that read 2 distinct books, and 1 user that read 4 distinct books.
The output expected is as below:
Distinct_Books_Count --- User_Count
2 2
4 1
I tried the following:
SELECT COUNT(DISTINCT Book_Id), COUNT(User_Id) FROM Books GROUP BY
User_Id
But I receive the following table:
Distinct_Books_Count User_Count
2 3
4 4
2 2
So any alternative solution or changes?
I call this a "histogram of histograms" query. You can do it using two group bys:
SELECT num_books, COUNT(*)
FROM (SELECT b.User_Id, COUNT(DISTINCT Book_Id) as num_books
FROM Books b
GROUP BY User_Id
) b
GROUP BY num_books
ORDER BY num_books;

MYSQL Stuck Generating temp table (massive query)

I have 4 tables (1 to many):
Dont say anything about that "email" relation. It is how my developer boss built it years ago.
EMPLOYEES (+-50 results)
------------------------------------------------
id name
1 EmpName 1
2 EmpName 2
CUSTOMERS (+50k results)
------------------------------------------------
id name email employee_assigned
1 John john#doe.com 12
2 Donald donald#duck.com 6
INTERESTS_CATEGORIES (+650k results)
------------------------------------------------
id customer_email category_id
1 john#doe.com 97
2 john#doe.com 13
3 donald#duck.com 56
4 donald#duck.com 126
5 donald#duck.com 45
INTERESTS_PRODUCTS (+650k results)
------------------------------------------------
id customer_email product_id
1 john#doe.com 78
2 john#doe.com 23
3 donald#duck.com 19
4 donald#duck.com 56
5 donald#duck.com 45
So I need to filter the customers by their assigned employee and their interests.
And here is the query:
SELECT
*
FROM
(
SELECT
customers.id AS 'id',
customers.name AS 'first_name',
customers.email,
employees.id AS 'employee_id'
FROM
customers,
employees
WHERE
employees.id = 2
AND
customers.employee_assigned = employees.id
) AS myCustomers
LEFT JOIN interests_categories
ON interests_categories.customer_email = myCustomers.email
LEFT JOIN interests_products
ON interests_categories.customer_email = myCustomers.email
WHERE
(
interests_categories.category_id = 20
OR
interests_categories.category_id = 21
)
GROUP BY myCustomers.email
So, the problem:
If the employee has a low number of assigned customers (like 3) query
is successfull.
If the employee has a medium-high number of assigned customers (over 100) query stucks.
I execute SHOW PROCESSLIST and it is stucked "Generating temp table".
Anyone has idea? :(
Thank you.
Check the indexes on your tables and try this:
SELECT
c.id AS 'id',
c.name AS 'first_name',
c.email,
e.id AS 'employee_id'
ic.*,
ip.*
FROM customers c
JOIN employees e
ON c.employee_assigned = e.id
LEFT JOIN interests_categories ic
ON ic.customer_email = c.email
LEFT JOIN interests_products ip
ON ic.customer_email = c.email
WHERE
(
ic.category_id IN (20,21)
AND e.id = 2
)
GROUP BY myCustomers.email
Incidentally, a less dumb design might look like as follows. If it was me, I'd start with this, and provide properly representative CREATE and INSERT statements accordingly. Also, I'm curious about where category_id comes from - because that's potentially an area for further optimization.
EMPLOYEES
------------------------------------------------
employee_id name
6 EmpName 1
12 EmpName 2
CUSTOMERS
------------------------------------------------
customer_id name email employee_assigned
1 John john#doe.com 12
2 Donald donald#duck.com 6
INTERESTS_CATEGORIES
------------------------------------------------
customer_id category_id
1 97
1 13
2 56
2 126
2 45
INTERESTS_PRODUCTS
------------------------------------------------
customer_id product_id
1 78
1 23
2 19
2 56
2 45

MySQL percentage of values in a GROUP BY query

Let's say, I have 3 tables: appointments, doctors and departments.
appointments:
id status doctor_id
---- --------- ---------
1 approved 1
2 cancelled 4
3 approved 4
4 approved 1
5 approved 4
6 NULL 5
7 approved 2
8 NULL 5
9 approved 4
10 approved 3
11 cancelled 1
12 NULL 4
13 approved 3
14 cancelled 1
15 approved 4
16 cancelled 4
17 cancelled 2
18 NULL 4
19 cancelled 1
20 cancelled 4
doctors:
id name department_id
---- --------- -------------
1 John 1
2 Robert 2
3 Patricia 3
4 Mary 1
5 Susan 3
departments:
id name
---- ---------
1 Dermatology
2 Neurology
3 Radiology
What I need is percentage of approved appointments to total of approved and cancelled (approved / (approved + cancelled) * 100) in Dermatology department, grouped by doctors.
I used the following query, which got me closer to solution.
SELECT COUNT(*) AS appointment_count,
doctors.name AS doctor_name,
appointments.status AS appointment_status
FROM appointments
LEFT JOIN doctors ON appointments.doctor_id = doctors.id
LEFT JOIN departments ON doctors.department_id = departments_id
WHERE departments.id = 1
GROUP BY doctors.id,
appointments.status
result:
count doctor_name appointment_status
----- ----------- ---------
2 John approved
3 John cancelled
0 John NULL
4 Mary approved
3 Mary cancelled
2 Mary NULL
But I need the percentage of approved / (approved + cancelled) for each doctors. So the result should be:
approved_percentage doctor_name
------------------- -----------
%40 John
%57 Mary
How can I achieve such a result?
In MySQL, the simplest way to do conditional counts and averages is simply to use the boolean expression. This suggests:
SELECT d.name, count(*),
SUM(a.status = 'approved') as approved_count,
AVG(a.status = 'approved') * 100 as approved_percentage
FROM doctors d INNER JOIN
appointments a
ON a.doctor_id = d.id
GROUP BY d.name;
You can group only by doctors and then use count(if()) functionality like this:
SELECT d.name, count(*), count(if(a.status = 'approved', 1, null)) approved_count,
count(if(a.status = 'approved', 1, null))/count(*) * 100 approved_percentage
FROM doctors d
INNER JOIN appointments a ON a.doctor_id = d.id
GROUP BY d.name

Mysql left join: show rows based on another attribute from the joined table

I have following tables:
Pools Members User
----------------------------------------------------------------------------------
id name id pool_id user_id id name
----------------------------------------------------------------------------------
1 abc 1 1 101 101 test1
2 xyz 2 1 102 102 test2
3 2 105 105 test5
4 2 106 106 test6
When a user is logged in, he should view the list of those pools for which he is not a member e.g If user with id 101 is logged in, following result should be shown.
Pool Members
xyz 2 members
Might be you want to this
SELECT name AS Pool,
count(pool_id) AS Members
FROM Pools
JOIN Members ON Pools.id=Members.pool_id
WHERE Pool.id NOT IN
(SELECT pool_id
FROM Members
WHERE user_id=101)
GROUP BY Member.pool_id

Add total of 3 rows for specific id

I have three tables:
Students
-------------------------------------------------------------
studentId first last gender weight
-------------------------------------------------------------
1 John Doe m 185
2 John Doe2 m 130
3 John Doe3 m 250
Lifts
-------------------
liftId name
-------------------
1 Bench Press
2 Power Clean
3 Parallel Squat
4 Deadlift
5 Shoulder Press
StudentLifts
------------------------------------------------
studentLiftId studentId liftId weight
------------------------------------------------
1 1 1 185
2 2 3 130
3 3 1 190
4 1 2 120
5 2 1 155
6 3 2 145
7 1 1 135
8 1 1 205
9 2 3 200
10 1 3 150
11 2 2 110
12 3 3 250
I would like to have four top lists:
Bench Press
Parallel Squat
Power Clean
Total of the above 3
I can successfully grab a top list for each specific lift using the following query:
SELECT s.studentId, s.first, s.last, s.gender, s.weight, l.name, sl.weight
FROM Students s
LEFT JOIN (
SELECT *
FROM StudentLifts
ORDER BY weight DESC
) sl ON sl.studentId = s.studentId
LEFT JOIN Lifts l ON l.liftId = sl.liftId
WHERE l.name = 'Bench Press'
AND s.gender = 'm'
AND s.weight > 170
GROUP BY s.studentId
ORDER BY sl.weight DESC
However, I am stuck on how to add the highest total of each lift for each student. How can I first find the highest total for each student in each lift, and then add them up to get a total of all three lifts?
Edit
The result set that I am looking for would be something like:
-------------------------------------------------
studentId first last weight
-------------------------------------------------
3 John Doe3 585
1 John Doe 475
2 John Doe2 465
I also forgot to mention that I would actually like two lists, one for students above 170 and one for students below 170.
SELECT -- join student a total weight to the student table
A.studentId,
A.first,
A.last,
C.totalWeight
FROM
Student A,
(
SELECT -- for each studet add the max weights
sum(B.maxWeight) as totalWeight,
B.studentID
FROM (
SELECT -- for each (student,lift) select the max weight
max(weight) as maxWeight,
studentId,
liftID
FROM
StudentLifts
GROUP BY
studentId,
liftID
) B
GROUP BY
studentId
) C
WHERE
A.studentID = C.studentId
-- AND A.weight >= 170
-- AND A.weight < 170
-- pick one here to generate on of the two lists.