Listing last (N) message of each conversation - mysql

I am creating a mobile application that need to synchronize with the server, and In order to do so, I need to get last (N) messages in each conversation.
note that this query was worked but get only last message in each conversation.
SELECT users.user_id AS user_id,
users.username,
users.picture,
users.last_seen,
me.message,
me.created_on
FROM messages me,
users
WHERE (me.sender_id=1
OR me.recipient_id=1)
AND ((me.sender_id=user_id
AND me.sender_id<>1)
OR (me.recipient_id=user_id
AND me.recipient_id<>1))
AND NOT exists
(SELECT 1
FROM messages me2
WHERE me2.id>me.id
AND ((me.sender_id=me2.sender_id
AND me.recipient_id=me2.recipient_id)
OR (me.sender_id=me2.recipient_id
AND me.recipient_id=me2.sender_id)))
ORDER BY me.created_on DESC

First we get all messages of all users ordered by user and date. We introduce artificial vars to number messages of user. When user id is the same we just increase message number. If it's different reset it to 0.
Thus subquery returns us
user_id, mess_n
1 0
1 1
1 2
2 0
2 1
2 2
3 0
3 1
3 2
Then in the query just leave messages with number <10 (first 10)
select *
from (
select u.*, m.*,
#mess_n_for_user:=if(u.user_id!=#curr_user,0,#mess_n_for_user+1) as mess_n,
#curr_user:=u.user_id
from (SELECT #mess_n_for_user:=0, #curr_user:=-1) sess_var,
users u join messages m on (u.user_id=m.sender_id
or u.user_id=m.recipient_id)
order by u.user_id, m.created_on DESC) all_messages_ordered
where all_messages_ordered.mess_n<10
Just add all filters conditions to the query
UPDATED
FROM the sqlfiddle
select * from (
select all_messages_ordered.*,
#mess_n_for_user:=if(u_id!=#curr_user,0,#mess_n_for_user+1) as mess_n,
#curr_user:=u_id
from (SELECT #mess_n_for_user:=0, #curr_user:=-1) sess_var,
(
select u.id as u_id, u.first_name, m.*
from
accounts u join messages m on (u.id=m.from or u.id=m.to)
and (m.to=1 or m.from=1)
and u.id<>1
order by u.id, m.date_time DESC) all_messages_ordered) a
where mess_n<3

You need a recursive query to select conversations. Following the approach from this answer, substituting recipient_id / sender_id for col3 / col1 :
select id, recipient_id, #pv:=sender_id as 'recipient_id' from messages
join
(select #pv:=2)tmp
where recipient_id=#pv
I have not tested this but the principle should be correct. Of course you will need to expand this to fully solve your problem...but hope this helps.

Related

How to show username from another table on the basis of max id in sqql

I am trapped in a sql query, I know it may be common but not getting any proper solution.
From my table messages, I have successfully fetch max id by joining from and to columns, now what I am trying to do is, I want to pull the name of that max id from another table naming users,
Here is my working query to find max id,
select m.*
from messages m
where m.id in (select max(m.id) as max_id
from messages m
where m.`from` = 7
or m.`to` = 7
group by least(m.`to`, m.`from`), greatest(m.`to`, m.`from`))
I have tried something like this but it matches name with from column of messages table.
select messages.*, users.name
from messages
left join users on messages.`from` = users.id
where messages.id in (select max(id) as max_id
from messages
where `from` = 7
or `to` = 7
group by least(`to`, `from`), greatest(`to`, `from`))
I want that name will be shown according to max id that I am getting.
columns of messages table : id , from, to, created_at
columns of users table : id, name, email, created_at
Please help me out,
One method is to join, but only to the column that is not 7:
select m.*, u.name
from messages m join
users u
on u.id in (m.`from`, m.`to`) and u.id <> 7
where m.id in (select max(m.id) as max_id
from messages m
where 7 in (m.`from`, m.`to`)
group by least(m.`to`, m.`from`), greatest(m.`to`, m.`from`)
);

SQL intermediate table having column = max(column)

I have 2 tables: user and review, a one-to-many relationship.
When I execute the following query:
SELECT
user_id,
count(*) totalReviews,
USER . NAME
FROM
review,
USER
WHERE
USER .id = review.user_id
GROUP BY
user_id
I get:
1 2 marius
2 2 daniela
3 1 alin
What I want to do now is to display first 2 users because they have given the most reviews(2).
I tried adding having, if I hardcode having totalReviews=2 it works, but if I write having total = max(total) I get 0 results, while if I'm trying with,
SELECT
*
FROM
(
SELECT
user_id,
count(*) total,
USER . NAME
FROM
review,
USER
WHERE
USER .id = review.user_id
GROUP BY
user_id
) A
WHERE
total = (SELECT max(total) FROM A) `
I get an error (table A doesn't exist)
You would do this with ORDER BY and LIMIT:
SELECT u.id, count(*) as totalReviews, u.name
FROM review r JOIN
user u
ON u.id = r.user_id
GROUP BY u.id, u.name
ORDER BY totalReviews DESC
LIMIT 2;
Notes:
Never use commas in the FROM clause. Always use proper, explicit JOIN syntax.
Table aliases make the query easier to write and read.
EDIT:
If occurs to me that you want all users with the maximum number of reviews, not exactly 2. Here is one method:
SELECT u.id, COUNT(*) as totalReviews, u.name
FROM review r JOIN
user u
ON u.id = r.user_id
GROUP BY u.id, u.name
HAVING totalReviews = (SELECT COUNT(*)
FROM review r2
GROUP BY r2.user_id
ORDER BY COUNT(*) DESC
LIMIT 1
);
Note that the subquery in the HAVING clause is simpler than the outer query. There is no need to bring in the user name.

Comparing counts in a SQL where clause

I have two tables: users and photos.
Users have many photos. Photos have a column called user_id, photos have one user. Photos also have a column called reported which is 0 or 1.
I need to know the number of users who have at least 1 photo with reported = 1. I also need to get the number of users who have at least 2 photos with reported = 1.
How would I do this? Here's what I'd like to do, but it obviously doesn't work:
select count(*)
from users join
(select * from photos where photos.reported = 1) as p2
on users.photo_id = p2.id;
This is at least 1
select count(distinct userid)
from photos
where reported = 1
This is at least 2.
select count(distinct userid)
from photos
where reported = 1
group by userid
having count(userid) > 2
Just get a histogram of the counts:
select numreported, count(*), min(user_id), max(user_id)
from (select p.user_id, sum(p.reported = 1) as numreported
from photos p
group by p.user_id
) p
group by numreported
order by numreported;
This gives you the number of users that have all counts of numreported, including 0.
Something like the following should work
select count(hasOne) cntHasOne, count(hasTwo) cntHasTwo from
(select users.user_id, 1 hasOne,
case when count(*) > 1 then 1 else 0 end hasTwo
from users inner join solution on(users.user_id = solution.user_id)
where solution.winning_status = 1
group by user_id) T1

MySQL nested query counting

A bit of background info; this is an application that allows users to created challenges and then vote on those challenges (bog standard userX-vs-userY type application).
The end goal here is to get a list of 5 users sorted by the number of challenges they have won, to create a type of leaderboard. A challenge is won by a user if it's status = expired and the user has > 50 votes for that challenge (challenges expire after 100 votes in total).
I'll simplify things a bit here, but essentially there are three tables:
users
id
username
...
challenges
id
issued_to
issued_by
status
challenges_votes
id
challenge_id
user_id
voted_for
So far I have an inner query which looks like:
SELECT `challenges`.`id`
FROM `challenges_votes`
LEFT JOIN `challenges` ON (`challenges`.`id` = `challenges_votes`.`challenge_id`)
WHERE `voted_for` = 1
WHERE `challenges`.`status` = 'expired'
GROUP BY `challenges`.`id`
HAVING COUNT(`challenges_votes`.`id`) > 50
Which in this example would return challenge IDs that have expired and where the user with ID 1 has > 50 votes for.
What I need to do is count the number of rows returned here, apply it to each user from the users table, order this by the number of rows returned and limit it to 5.
To this end I have the following query:
SELECT `users`.`id`, `users`.`username`, COUNT(*) AS challenges_won
FROM (
SELECT `challenges`.`id`
FROM `challenges_votes`
LEFT JOIN `challenges` ON (`challenges`.`id` = `challenges_votes`.`challenge_id`)
WHERE `voted_for` = 1
GROUP BY `challenges`.`id`
HAVING COUNT(`challenges_votes`.`id`) > 0
) AS challenges_won, `users`
GROUP BY `users`.`id`
ORDER BY challenges_won
LIMIT 5
Which is kinda getting there but of course the voted_for user ID here is always 1. Is this even the right way to go about this type of query? Can anyone shed any light on how I should be doing it?
Thanks!
I guess the following script will solve your problem:
-- get the number of chalenges won by each user and return top 5
SELECT usr.id, usr.username, COUNT(*) AS challenges_won
FROM users usr
JOIN (
SELECT vot.challenge_id, vot.voted_for
FROM challenges_votes vot
WHERE vot.challenge_id IN ( -- is this check really necessary?
SELECT cha.id -- if any user is voted 51 he wins, so
FROM challenges cha -- why wait another 49 votes that won't
WHERE cha.status = 'expired' -- change the result?
) --
GROUP BY vot.challenge_id
HAVING COUNT(*) > 50
) aux ON (aux.voted_for = usr.id)
GROUP BY usr.id, usr.username
ORDER BY achallenges_won DESC LIMIT 5;
Please allow me to propose a small consideration to the condition to close a challenge: if any user wins after 51 votes, why is it necessary to wait another 49 votes that will not change the result? If this constraint can be dropped, you won't have to check challenges table and this can improve the query performance -- but, it can worsen too, you can only tell after testing with your actual database.

MySQL: Getting a row number (ranking) for a specific row

I have a users table that has a column called money_sent. I want to order this table by money_sent in descending order, and then find out what "rank" a specific user has.
For example, only 111 people have spent more money than User 12392, so they would be rank 112.
How could I query this?
How about:
SELECT count(*) FROM users WHERE money_sent < (
SELECT money_sent FROM users WHERE user = 'joe'
);
SELECT Row,user, money_sent
FROM (SELECT #row := #row + 1 AS Row, user, money_sent
FROM table1 order by money_sent desc)
As derived1
If you also want to get the user's row along with that user's rank, you can use something like this:
SELECT u1.*, COUNT(u2.user)
FROM users u1
LEFT OUTER JOIN users as u2 ON (u1.money_sent < u2.money_sent)
GROUP BY u1.user;