MySQL How to GROUP BY counted column? - mysql

I have two tables users, orders
each table has below column
users(table)
id
orders(table)
user_id
How can i get the number of users whose order count is 1,2,3,4 ....n?
Like this?
users count | order count
999 | 1
100 | 2
80 | 3
70 | 4
60 | 5
50 | 6
What I have been trying so far is
SELECT cnt.uid as u_cnt, cnt.ocnt as or_cnt
FROM (
SELECT u.id as uid, COUNT(o.id) as o_cnt
FROM users as u
INNER JOIN orders o on u.id = o.user_id
) as cnt;
GROUP BY or_cnt
BUT I get only 1 u_cnt and summed or_cnt

You need two levels of group by clauses here: First, you need to group by user and count the number of orders each user has. Then, you need to take that result, group by the number of orders and count how many users have such an order count.
The easiest way to achieve this is probably with a subquery, where both the inner and outer query have a group by clause:
SELECT cnt.ocnt as or_cnt, COUNT(*) as user_count
FROM (
SELECT u.id as uid, COUNT(o.id) as o_cnt
FROM users as u
INNER JOIN orders o on u.id = o.user_id
GROUP BY u.id -- This was missing in your original query
) as cnt
GROUP BY or_cnt

You can use two levels of aggregation. More importantly, you do not need a JOIN. All the information you need is in orders:
SELECT o_cnt, COUNT(*) as user_count
FROM (SELECT o.user_id, COUNT(*) as o_cnt
FROM orders o
GROUP BY o.user_id
) u
GROUP BY o_cnt
ORDER BY o_cnt;

Related

Get the number of users that belong to a group and the number of orders that belong to the group through its users

I have a user_groups table, a users table and an orders table.
The relationship between them is this: users.group_id, orders.user_id.
I'm trying to get the amount of users that belong to each group, and the amount of orders that belong to each group through its users.
My code:
select user_groups.*, count(users.id) as user_count, count(orders.id) as order_count
from user_groups
left join users on user_groups.id=users.group_id
left join orders on users.id=orders.user_id
group by user_groups.id
Expected output:
id | user_count | order_count
1 | 5 | 67
2 | 1 | 1
Actual output:
The amount of orders should be 5, not 71:
Use count(distinct):
select ug.*, count(distinct u.id) as user_count, count(distinct o.id) as order_count
from user_groups ug left join
users u
on ug.id = u.group_id left join
orders o
on u.id = o.user_id
group by ug.id;
count(id) counts the number of non-NULL values. You apparently want to count the number of different values, which is what distinct does.

SQL join and sum of items

There are two tables
users
+--+----+
|id|name|
+--+----+
1 A
2 B
orders
+--+--------+-------+-------+
|id|subtotal|created|user_id|
+--+--------+-------+-------+
1 10 1000001 1
2 20 1000002 1
3 10 1000003 2
4 10 1000005 1
The idea is to get AVG, SUM and the last created order from the users.
SELECT
users.name,
users.phone,
SUM(a.subtotal),
COALESCE(a.created, NULL)
FROM users
LEFT JOIN
(
SELECT
orders.id,
orders.subtotal,
orders.user_id,
orders.created
FROM
orders
JOIN(
SELECT MAX(i.created) created, i.user_id
FROM orders i
GROUP BY i.user_id
)AS j ON(j.user_id = orders.user_id AND orders.created = j.created) GROUP BY orders.user_id
) AS a ON users.id = a.user_id
GROUP BY users.id
For example the SQL request should return this:
+--+----+---+--------+
|id|name|sum|date |
+--+----+---+--------+
1 A 40 1000005
2 B 10 1000003
But the SQL above failed to calculate sum. What did i miss?
Your query seems way too complicated. How about this?
SELECT u.id, u.name, SUM(o.subtotal), MAX(o.created)
FROM users u LEFT JOIN
orders o
ON u.id = o.user_id
GROUP BY u.id, u.name;
In MySQL it is particularly important to avoid unnecessary subqueries in the FROM clause. These are actually materialized and that can impede the use of indexes for performance.

Order data by data from another table

I have two tables:
rooms (all the rooms)
id | title | ...
-----------------
1 |Room 1 |
2 |Room 2 |
3 |Room 3 |
user_rooms (in which room is every user, column user is user's id and it's primary column)
user | room | ...
------------------
20 | 3 |
14 | 1 |
35 | 3 |
So I want to select all the rooms from the 'rooms' table but to order them in that way to show the rooms with the most users in them and after that the rooms with less and less users. For example, I want to show room 3 first (because 2 users are in it), then room 1 (one user in it), and finally room 2 (since no users are in it). How to achieve that?
SELECT aa.id, aa.title
FROM rooms AS aa
LEFT JOIN (
SELECT room, COUNT(*) AS total_count
FROM user_rooms
GROUP BY room
) AS _aa
ON aa.id = _aa.room
ORDER BY _aa.total_count;
This would often be done without a subquery:
select r.id, r.title, count(ur.room) as numusers
from rooms r left join
user_rooms ur
on r.id = ur.room
group by r.id, r.title
order by numusers desc;
This would often be more efficient than a version using a subquery in the from clause because it can take advantage of an index on the join key.
Interestingly, the same index would be used for a correlated subquery in the select, which is an alternative approach:
select r.id, r.title,
(select count(*)
from user_rooms ur
where r.id = ur.room
) as numusers
from rooms r
order by numusers desc;
This might be the most efficient approach, because it removes the aggregation in the outer query.
select r.id, r.title, coalesce(t.cnt,0)
from rooms r left join
(select room, count(*) as cnt
from user_rooms
group by room) t on t.room = r.id
order by t.cnt desc
This will give you only rooms with users
SELECT title, count(user) As MostUsers
FROM Rooms R
INNER JOIN user_rooms U
ON R.?field? = U.?field?
GROUP BY title
ORDER BY MostUsers
You need to complete the query inserting the names of the fields that you can use to Join the tables
If you want all rooms you can use a Left Join:
SELECT title, count(user) As MostUsers
FROM Rooms R
LEFT JOIN user_rooms U
ON R.?field? = U.?field?
GROUP BY title
ORDER BY MostUsers
Please try the following query:
select * from rooms r
order by (select count(1)
from userroom ur
where ur.roomid = r.roomid ) desc

MySQL subquery limit 1

I have an sql query that returns a list of residential units, and a subquery that is supposed to get the last entered bill for that unit.
However when I add LIMIT 1 to the subquery, no bill entries are returned? If I leave it out, I get duplicate unit rows depending on the number of bill for the unit.
select * from unit u
left join (select id as billId, unit_id, added_on, end_reading, bill_type from bills
order by id desc) b ON unit_id = u.id
where community_Id = 1
and unit_section = 7
and unit_floor in (1,2,3,4,5)
order by unit_floor, display_order asc;
Anyone know how I can the subquery result limited to 1 bill?
When using joins that duplicate your results, add a group by statement. It's an alternative of the distinct from a simple select
select * from unit u
left join (select id as billId, unit_id, added_on, end_reading, bill_type from bills
order by id desc) b ON unit_id = u.id
where community_Id = 1
and unit_section = 7
and unit_floor in (1,2,3,4,5)
group by u.id
order by unit_floor, display_order asc;
Think you will need a sub query to get the first (lowest) id for each unit_id from the bills table. Then use that to join between the unit and bills table, getting the other matching columns from bills for that lowest id
SELECT u.*, bills.*
FROM unit u
LEFT OUTER JOIN
(
SELECT unit_id, MIN(id) AS min_id
FROM bills
GROUP BY unit_id
) b ON b.unit_id = u.id
LEFT OUTER JOIN bills
ON b.unit_id = bills.unit_id
AND b.min_id = bills.id
WHERE u.community_Id = 1
AND u.unit_section = 7
AND u.unit_floor in (1,2,3,4,5)
ORDER BY u.unit_floor, u.display_order asc;

Select rows in order of total count

I have a table of users which hold a a users id that they voted for like this:
uid | voted_for
1 | 3
2 | 3
3 | 1
What i'm aiming to do is order uid based on how many people have voted for that uid. But I have no idea how to do it.
So the end result would be:
uid | Total_Votes
3 | 2
1 | 1
2 | 0
Hope you can help explain the best way to structure the SQL for this.
Perhaps something like this will help joining the table on itself:
SELECT u.*, voted_for_cnt
FROM users u
LEFT JOIN (
SELECT voted_for, count(1) voted_for_cnt
FROM users
GROUP BY voted_for
) t ON u.uid = t.voted_for
ORDER BY t.voted_for_cnt DESC
SQL Fiddle Demo
This simple query will produce the output you requested:
select voted_for as uid, count(*) as total_votes
from users
group by 1
order by 2 desc
If you want all data about each user in the output, join users to itself:
select u.*, count(v.uid) as total_votes
from users u
left join users v on v.voted_for = u.uid
group by 1,2,3,4,5 -- put as many numbers here as there are columns in the users table
order by total_votes desc
This second query will give a total_votes score of zero if no one voted for the user.
Alternatively, you can select only those columns you want:
select u.uid, u.name, count(v.uid) as total_votes
from users u
left join users v on v.voted_for = u.uid
group by 1,2
order by 3 desc
```
To return only the winners, do this:
select u.uid, u.name, count(*) as total_votes
from users u
left join users v on v.voted_for = u.uid
group by 1,2
having count(*) = (
select max(c) from (
select count(*) as c from users group by voted_for))
order by 3 desc