sql Group by and max() doesn't get the right results - mysql

I have the following table
mes_id | user_a | user_b | message | room | time
------------------------------------------------------
1 | 23 | 24 | hello | 1 | 5676766767
user_a sent a message for user_b in room 1
I want to get the all the last messages received for a specific user (for example user_b) per room, I have tried few queries but I didn't get the right one.
This solution didn't work for me: sql_group_by_max
An update
I am using 5.5.18 MySQL Server
ok
this gave me the result thanks
SELECT *
FROM messeges C
JOIN (
SELECT room, user_a, MAX( messeges.date ) AS max_time
FROM messeges
GROUP BY room, user_a
)a
ON a.room = c.room
AND a.user_a = c.user_a
AND a.max_time = c.date
WHERE c.user_b = '396'

try this:
select * from chat C join
(select room,user_b,MAX([time]) as max_time
from chat
group by room,user_b)a
on a.room=c.room
and a.user_b=c.user_b
and a.max_time=c.[time]
If you want multiple rows, (In sql server)
with cte as (select *,ROW_NUMBER ()
over (partition by room,user_b order by [time] desc) as rownum from chat)
select * from cte order by rownum

try this:
Select *
FROM chat a
INNER JOIN ( Select user_a, user_b , room ,MAX(time) as max_time
FROM chat
group by user_a, user_b , room) b on a.user_a=b.user_a and a.user_b=b.user_b and a.room=b.room

Related

How to "Group by" with multiple tables in MySQL

I've two tables.
1. Users (id,name,createdAt)
2. Images (id,createdAt)
I want to generate a report something like this
date | newUsers | newImages
2019-09-12 | 12 | 3
2019-09-13 | 15 | 5
2019-09-14 | 16 | 8
What I've done upto now is
SELECT
u.newUsers,
i.newImages
FROM
(SELECT
COUNT(*) AS newUsers,createdAt
FROM
users group by date(createdAt)) u
left join
(select count(*) as newImages,createdAt from images group by date(createdAt)) i
This has a syntax error unfortunately.
How do I achieve this using mysql?
Also I am using Server version: 5.7.27-0ubuntu0.18.04.1 (Ubuntu)
you have missing on clause
select
u.newUsers,
i.newImages,
u.createdAt
from
(select
count(*) AS newUsers
, date(createdAt) as createdAt
from
users
group by date(createdAt)) u
left join
(select count(*) as newImages
, date(createdAt) as createdAt
from images
group by date(createdAt)) i
on i.createdAt = u.createdAt
You can try below way -
SELECT u.createdAt,COUNT(*) AS newUsers,count(*) as newImages
FROM users u left join images i on u.createdAt=i.createdAt
left join
group by u.createdAt
If you want to be sure that you don't miss any dates, then left join is not sufficient. One method is union all/group by:
select date, sum(is_user) as num_users, sum(is_image) as num_images
from ((select date(createdat) as date, 1 as is_user, 0 as is_image
from users
) union all
(select date(createdat) as date, 0 as is_user, 1 as is_image
from images
)
) ui
group by date;

sum of count(*) for all rows in MySQL

I'm stuck with sum() query where I want the sum of count(*) values in all rows with group by.
Here is the query:
select
u.user_type as user,
u.count,
sum(u.count)
FROM
(
select
DISTINCT
user_type,
count(*) as count
FROM
users
where
(user_type = "driver" OR user_type = "passenger")
GROUP BY
user_type
) u;
Current Output:
----------------------------------
| user | count | sum |
----------------------------------
| driver | 58 | 90 |
----------------------------------
Expected Output:
----------------------------------
| user | count | sum |
----------------------------------
| driver | 58 | 90 |
| passenger | 32 | 90 |
----------------------------------
If I remove sum(u.count) from query then output is looks like:
--------------------------
| user | count |
--------------------------
| driver | 58 |
| passenger | 32 |
--------------------------
You need a subquery:
SELECT user_type,
Count(*) AS count,
(SELECT COUNT(*)
FROM users
WHERE user_type IN ("driver","passenger" )) as sum
FROM users
WHERE user_type IN ("driver","passenger" )
GROUP BY user_type ;
Note you dont need distinct here.
OR
SELECT user_type,
Count(*) AS count,
c.sum
FROM users
CROSS JOIN (
SELECT COUNT(*) as sum
FROM users
WHERE user_type IN ("driver","passenger" )
) as c
WHERE user_type IN ("driver","passenger" )
GROUP BY user_type ;
You can use WITH ROLLUP modifier:
select coalesce(user_type, 'total') as user, count(*) as count
from users
where user_type in ('driver', 'passenger')
group by user_type with rollup
This will return the same information but in a different format:
user | count
----------|------
driver | 32
passenger | 58
total | 90
db-fiddle
In MySQL 8 you can use COUNT() as window function:
select distinct
user_type,
count(*) over (partition by user_type) as count,
count(*) over () as sum
from users
where user_type in ('driver', 'passenger');
Result:
user_type | count | sum
----------|-------|----
driver | 32 | 90
passenger | 58 | 90
db-fiddle
or use CTE (Common Table Expressions):
with cte as (
select user_type, count(*) as count
from users
where user_type in ('driver', 'passenger')
group by user_type
)
select user_type, count, (select sum(count) from cte) as sum
from cte
db-fiddle
I would be tempted to ask; Are you sure you need this at the DB level?
Unless you are working purely in the database layer, any processing of these results will be built into an application layer and will presumably require some form of looping through the results
It could be easier, simpler, and more readable to run
SELECT user_type,
COUNT(*) AS count
FROM users
WHERE user_type IN ("driver", "passenger")
GROUP BY user_type
.. and simply add up the total count in the application layer
As pointed out by Juan in another answer, the DISTINCT is redundant as the GROUP BY ensures that each resultant row is different
Like Juan, I also prefer an IN here, rather than OR condition, for the user_type as I find it more readable. It also reduces the likelihood of confusion if combining further AND conditions in the future
As an aside, I would consider moving the names of the user types, "driver" and "passenger" into a separate user_types table and referencing them by an ID column from your users table
N.B. If you absolutely do need this at the DB level, I would advocate using one of Paul's excellent options, or the CROSS JOIN approach proffered by Tom Mac, and by Juan as his second suggested solution
Try this. Inline view gets the overall total :
SELECT a.user_type,
count(*) AS count,
b.sum
FROM users a
JOIN (SELECT COUNT(*) as sum
FROM users
WHERE user_type IN ("driver","passenger" )
) b ON TRUE
WHERE a.user_type IN ("driver","passenger" )
GROUP BY a.user_type;
You could simply combine SUM() OVER() with COUNT(*):
SELECT user_type, COUNT(*) AS cnt, SUM(COUNT(*)) OVER() AS total
FROM users WHERE user_type IN ('driver', 'passenger') GROUP BY user_type;
db<>fiddle demo
Output:
+------------+------+-------+
| user_type | cnt | total |
+------------+------+-------+
| passenger | 58 | 90 |
| driver | 32 | 90 |
+------------+------+-------+
Add a group by clause at the end for user-type, e.g:
select
u.user_type as user,
u.count,
sum(u.count)
FROM
(
select
DISTINCT
user_type,
count(*) as count
FROM
users
where
(user_type = "driver" OR user_type = "passenger")
GROUP BY
user_type
) u GROUP BY u.user_type;
Tom Mac Explain Properly Your answer. Here is the another way you can do that.
I check the query performance and not found any difference within 1000 records
select user_type,Countuser,(SELECT COUNT(*)
FROM users
WHERE user_type IN ('driver','passenger ') )as sum from (
select user_type,count(*) as Countuser from users a
where a.user_type='driver'
group by a.user_type
union
select user_type,count(*) as Countuser from users b
where b.user_type='passenger'
group by b.user_type
)c
group by user_type,Countuser
Try this:
WITH SUB_Q AS (
SELECT USER_TYPE, COUNT (*) AS CNT
FROM USERS
WHERE USER_TYPE = "passenger" OR USER_TYPE = "driver"
GROUP BY USER_TYPE
),
SUB_Q2 AS (
SELECT SUM(CNT) AS SUM_OF_COUNT
FROM SUB_Q
)
SELECT A.USER_TYPE, A.CNT AS COUNT, SUB_Q2 AS SUM
FROM SUB_Q JOIN SUB_Q2 ON (TRUE);
I used postgresql dialect but you can easily change to a subquery.
select
u.user_type as user,
u.count,
sum(u.count)
FROM users group by user

MySQL Query with the count, group by

Table: statistics
id | user | Message
----------------------
1 | user1 |message1
2 | user2 |message2
3 | user1 |message3
I am able to find the count of messages sent by each user using this query.
select user, count(*) from statistics group by user;
How to show message column data along with the count? For example
user | count | message
------------------------
user1| 2 |message1
|message3
user2| 1 |message2
You seem to want to show Count by user, which message sent by user.
If your mysql version didn't support window functions, you can do subquery to make row_number in select subquery, then only display rn=1 users and count
CREATE TABLE T(
id INT,
user VARCHAR(50),
Message VARCHAR(100)
);
INSERT INTO T VALUES(1,'user1' ,'message1');
INSERT INTO T VALUES(2,'user2' ,'message2');
INSERT INTO T VALUES(3,'user1' ,'message3');
Query 1:
SELECT (case when rn = 1 then user else '' end) 'users',
(case when rn = 1 then cnt else '' end) 'count',
message
FROM (
select
t1.user,
t2.cnt,
t1.message,
(SELECT COUNT(*) from t tt WHERE tt.user = t1.user and t1.id >= tt.id) rn
from T t1
join (
select user, count(*) cnt
from T
group by user
) t2 on t1.user = t2.user
) t1
order by user,message
Results:
| users | count | message |
|-------|-------|----------|
| user1 | 2 | message1 |
| | | message3 |
| user2 | 1 | message2 |
select user, count(*) as 'total' , group_concat(message) from statistics group by user;
You could join the result of your group by with the full table (or vice versa)?
Or, depending on what you want, you could use group_concat() using \n as separator.
Use Group_concat
select user, count(0) as ct,group_concat(Message) from statistics group by user;
This will give you message in csv format
NOTE: GROUP_CONCAT has size limit of 1024 characters by default in mysql.
For UTF it goes to 1024/3 and utfmb4 255(1024/4).
You can use group_concat_max_len global variable to set its max length as per need but take into account memory considerations on production environment
SET group_concat_max_len=100000000
Update:
You can use any separator in group_concat
Group_concat(Message SEPARATOR '----')
Try grouping with self-join:
select s1.user, s2.cnt, s1.message
from statistics s1
join (
select user, count(*) cnt
from statistics
group by user
) s2 on s1.user = s2.user

SQL Select Join the same table (sort by most replies)

So I want to select * from "board_b" the thread that has the most replies. My problem is that the replies are actually in the same table. Take a look at this:
+---+-----------+---------+
|ID | name | replyto |
+---+-----------+---------+
| 1 | newthread | |
| 2 | reply | 1 |
+---+-----------+---------+
(NOTE: the name column is not set to those, it is just to demonstrate) As you can see, 1 is a new thread, and 2 is a reply to 1. Now I have a table full of these, and the table has more columns (text, timestamp, etc...) but the general idea is like the one above.
The thing I want to achieve is select all threads, and sort them by most replies (and also limit by 0, 20). I've tried looking in to joining tables but it get's too complicated for me to understand, so a sample code would be great.
Something like this will do it:
SELECT board.id, board.name, COUNT(reply.id)
FROM board_b board INNER JOIN board_b reply ON board.id = reply.replyto
GROUP BY board.id, board.name
ORDER BY COUNT(reply.id) desc
LIMIT 20
You want to use group by:
select replyto as thread, count(*) as cnt
from board_b
group by replyto
order by cnt desc
limit 0, 20;
select c.replyto, c.replycount
from
(
select a.replyto as replyto, count(*) as replycount
from board_b a
inner join (
select id, name, replyto
from board_b
where replyto is null
) b
on b.id = a.replyto
group by a.replyto
) c
where c.replycount between 0 and 20
order by c.replycount desc

Select inner join with n_m table

I have three tables as following:
USERS TABLE
id_user| name |
---------------
1 | ...
2 | ...
SERVICES TABLE
id_service | name |
-------------------
1 | ...
2 | ...
3 | ...
USER_SERVICES TABLE (n-m)
id_user | id_service
--------------------
1 | 1
1 | 2
2 | 1
And I need to do a SELECT starting from "SELECT * FROM users" and then, getting the users by services. Ex. I need to get every user with services = 1 and services = 2 (and maybe he has other more services, but 1 and 2 for sure).
I did the following:
SELECT *
FROM `users`
INNER JOIN user_services ON users.id_user = user_services.id_user
WHERE id_service=1 AND id_service=2
But this, of course dont works since there is not a single record matching service = 1 and service = 2.
What can I do?
Add an extra join for the other service you want to check:-
SELECT *
FROM `users`
INNER JOIN user_services us1 ON users.id_user = us1.id_user AND us1.id_service=1
INNER JOIN user_services us2 ON users.id_user = us2.id_user AND us2.id_service=2
select t.*,
(select count(*) from user_services where id_user = t.id_user) how_much
from users t;
Is this what you want???
It shows the data of the users and how much services are in the services table. Other possibility is this:
select t.*,
(case when (select count(*)
from user_services where id_user = 1) > 0
then 'service1'
else 'null'
end) has_service_1
from users t;
The problem with this select is that you have to repeat this case...end as much times as id_services you have, so it doesn't make sense if the number of services is increasing over time. On the contrary, if it is a somewhat fixed number, and it is not a big number, this could be a solution.