How can you select unique values with MySQL GROUP BY - mysql

I have a table full of messages between users, and I want to select the last message from each user where the userFrom is not my user(4), however if the last message to another user(userTo) is the last message between my user(4) and another user then that should be the value for that msg in the return records.
TABLE messages
id|userFrom|userTo|msg
-------------------------
1 | 4 | 9 |msg 1
2 | 9 | 4 |msg 2
3 | 4 | 63 |msg 1
4 | 63 | 4 |msg 2
5 | 4 | 9 |msg 3
6 | 9 | 4 |msg 4
7 | 9 | 4 |msg 5
8 | 63 | 4 |msg 3
My end goal is to use the data to show a list of messages from unique users where each row is a different user and it shows the last message between my user and that user(for visual reference i'm trying to create something like Facebook messages)
How i would like the above table data returned
id|userFrom|msg
-------------------------
7 | 9 |msg 5
8 | 63 |msg 3
i need the userFrom to be unique so i can extend the query to do additional joins to get the actual varchar username from the users table where the userFrom is some user but not my own user.

Here's one option with least and greatest:
select id, userfrom, userto, msg
from messages m join (
select max(id) maxid
from messages
group by least(userfrom, userto), greatest(userfrom, userto)
) t on m.id = t.maxid
SQL Fiddle Demo
BTW -- I assume your expected results are incorrect. You don't have id = 8 in your sample data.

Try this one:
SELECT messages.id, messages.userFrom, messages.msg
FROM messages INNER JOIN
(SELECT userFrom, max(id) AS mxid
FROM messages
GROUP BY userFrom) sub
ON messages.id = sub.mxid
WHERE messages.UserFrom <> 4

I think this is what you are looking for
CREATE TABLE [dbo].[UserMessage](
[id] [int] NOT NULL,
[userFrom] [int] NOT NULL,
[userTo] [int] NOT NULL,
[msg] [varchar](50) NOT NULL
) ON [PRIMARY]
insert into UserMessage values(1,4,9,'msg 1')
insert into UserMessage values(2,9,4,'msg 2')
insert into UserMessage values(3,4,63,'msg 1')
insert into UserMessage values(4,63,4,'msg 2')
insert into UserMessage values(5,4,9,'msg 3')
insert into UserMessage values(6,9,4,'msg 4')
insert into UserMessage values(7,9,4,'msg 5')
insert into UserMessage values(8,63,4,'msg 3')
SELECT lastMesageFrom.*, um.msg
FROM
(
select max(id) as id,UserFrom from UserMessage
where UserTo = 4
Group by UserFrom
)as lastMesageFrom
Left Outer join UserMessage um on um.id = lastMesageFrom.id
EDIT: Verified with MySQL: http://sqlfiddle.com/#!9/1045b/1

You must read the difference between where and having clause.
First, filter your records by WHERE clause (where userFrom!=4)
Then use the group by
And finally filter the messages that were sent to you by HAVING clause (having userTo=4)

Related

How to get records ordered by joined table field

I have two tables:
chat:
id
name
chat_messages:
id
chat_id
user_id
message
created_at
I want to get a list of chats names ordered by recent activity. In other words, I want to get a list of chats where the first one is that with the bigger created_at field, and the last one is that with the smaller created_at field.
For example:
chat table:
1 General
2 News
chat_messages:
1 | 1 | 20 | Hello everybody | 2020-10 18:00:00
1 | 1 | 23 | this is a me... | 2020-10 18:00:05
1 | 1 | 15 | another message | 2020-10 18:00:15
1 | 2 | 22 | Anybody there? | 2020-10 17:00:00
1 | 2 | 45 | Hello?????????? | 2020-10 16:00:00
The desired result would be: ['News', 'General']
Any help?
Thanks
Try this:
SELECT DISTINCT `chat`.`name`
FROM `chat` JOIN `chat_message` ON `chat`.`id` = `chat_message`.`chat_id`
ORDER BY `chat_message`.`created_at` DESC
Explanation:
You only want the chat name to be returned. And you also want each value only once (that's the DISTINCT).
You ORDER BY the created_at field, and then the only thing left to do is to JOIN the tables.
Edit: you can try / improve here.
Select last Created_at for every chat:
SELECT m.chat_id,
max(m.created_at) as LastActivity
FROM chat_message as m
group by m.chat_id;
Let's find some infos about chats, by joining the chat table:
SELECT c.name,
info.LastActivity
FROM ( SELECT m.chat_id,
max(m.created_at) as LastActivity
FROM chat_message as m
group by m.chat_id) info -- virtual table "info" from first query
JOIN chat as c ON c.id = info.chat_id; -- add table chat to query
Now we want to add some info about the LastActivity
SELECT c.name,
info.LastActivity,
m2.user_id, -- Print Infos of LastActivityMessage
m2.message
FROM ( SELECT m.chat_id,
max(m.created_at) as LastActivity
FROM chat_message as m
group by m.chat_id) info
JOIN chat as c ON c.id = info.chat_id
JOIN chat_message as m2 ON info.LastActivity = m2.created_at; -- Search for Message at LastActivity
In out last query we see a problem: We do not want to search for a messag with it's date!
We're missing a propper primary-Key in table chat_message.
I'd suggest to calculate a unique id:
create table chat_message
(
id int not null auto_increment primary key, -- this is the message-id which will automatically set
chat_id int not null, -- where did the user post?
user_id int not null, -- which user?
message text,
created_at datetime
);
If we insert our Messages now like this:
INSERT INTO chat_message (chat_id, user_id, message, created_at)
VALUES
(2, 45, "Hello?", "2020-10-01 16:00:00"),
(2 , 22 , "Anybody there?" , "2020-10-01 17:00:00"),
(1, 20, "Hello everybody", "2020-10-01 18:00:00"),
(1, 23, "this is a me...", "2020-10-01 18:00:05"),
(1, 15, "another message", "2020-10-01 18:00:15");
..Ids are generated automatically:
id chat_id user_id message created_at
1 2 45 Hello? 2020-10-01T16:00:00Z
2 2 22 Anybody there? 2020-10-01T17:00:00Z
3 1 20 Hello everybody 2020-10-01T18:00:00Z
4 1 23 this is a me... 2020-10-01T18:00:05Z
5 1 15 another message 2020-10-01T18:00:15Z
This leads to a better query without searching for a single message with it's date:
-- Now we want to add some info about the LastActivity
SELECT c.name,
m2.id,
m2.created_at,
m2.user_id,
m2.message
FROM ( SELECT m.chat_id,
max(m.id) as LastActivity -- select last Id
FROM chat_message as m
group by m.chat_id) info
JOIN chat as c ON c.id = info.chat_id
JOIN chat_message as m2 ON info.LastActivity = m2.id; -- search for message with correct id

How to find all the opposite combinations between two columns in SQL

I am making a web dating app that needs to match users and let them chat with each other.
I want to figure out how to find all the matches for a particular user.
Right now I have a table called follows that has 2 columns.
UserID | MatchUserID
--------------------
1 | 2
2 | 1
1 | 3
1 | 4
1 | 5
4 | 1
5 | 4
The idea is that for two users to match they need to follow one another. The table above shows which user follows which.
Assuming that the user who is currently logged on is UserID = 1.
I need a query that will return from the MatchUserID table the following results:
2, 4
In a way, I am looking to find all the opposite combinations between the two columns.
This is the code I use to create the table.
CREATE TABLE Match
(
UserID INT NOT NULL,
MatchUserID INT NOT NULL,
PRIMARY KEY (UserID, MatchUserID)
);
You can do it with a self join:
select m.MatchUserID
from `Match` m inner join `Match` mm
on mm.MatchUserID = m.UserId
where
m.UserId = 1
and
m.MatchUserID = mm.UserId
See the demo.
Results:
| MatchUserID |
| ----------- |
| 2 |
| 4 |
The simplest way possibly is to use EXISTS and a correlated subquery that searches for the other match.
SELECT t1.matchuserid
FROM elbat t1
WHERE t1.userid = 1
AND EXISTS (SELECT *
FROM elbat t2
WHERE t2.matchuserid = t1.userid
AND t2.userid = t1.matchuserid);

sql join with foreign key table

I want to select all users from my database with emails ending #gmail.com which are not already in the group with the groupID 4.
The problem is my user_to_group table looks like this:
userID | groupID
--------------------
1 | 5
1 | 4
1 | 3
2 | 3
2 | 6
Users with the groupID 4 are excluded, but because they are also in other groups, they will be selected anyway. In this example I just need the user with the userID 2.
Is it possible to exclude users which are in group 4 regardless of their other groups?
SELECT * FROM wcf13_user user_table
RIGHT JOIN wcf13_user_to_group ON (wcf13_user_to_group.userID = user_table.userID && groupID != 4 )
WHERE user_table.email LIKE "%#gmail.com"
Yes, you can do it with an EXISTS subquery:
SELECT *
FROM wcf13_user user_table u
WHERE user_table.email LIKE "%#gmail.com" -- Has a gmail account
AND NOT EXISTS ( -- Is not a member of group #4
SELECT *
FROM wcf13_user_to_group g
WHERE u.userID=g.userID AND groupID = 4
)
This is a good place to use the not exists clause:
SELECT ut.*
FROM wcf13_user ut
WHERE not exists (select 1
from wcf13_user_to_group utg
where utg.userID = ut.userID and utggroupID = 4
) and
ut.email LIKE '%#gmail.com';

How to group by rows but with the most recent date?

In my MySQL database I have a table like this used for storing conversation messages from any people
id int(11) id of the message
from member_id int(11) id of the person the message was sent from
to member_id int(11) id of the person the message was sent to
date sent datetime date of when it was sent
active tinyint(1) if the message is deleted
text longtext the text of the message
from_read tinyint(1) boolean to know if the person who sent it read it
to_read tinyint(1) boolean to know if the person who it got sent to read it
So for example, it could have like:
from_member_id to_member_id date sent
1 2 june 12
1 3 june 13
2 3 june 14
3 1 june 9
So we have a conversation between person 1 and 2, 1 and 3, 2 and 3.
I am trying to get a select statement which will give me the most recent message that the current user is involved with from every conversation that user is in. So if 1 is logged in then I would expect to get 2 rows. The first row in the result set would be the second row above (july 13) because its the most recent, then then the second row in the result set would be the first row above (june 12), which are the most recent from 1's two conversations. The result set also needs to be sorted by date sent, so newer conversations are listed on top.
What I am trying to do is like the texting in android phones, where you see the list of conversations, and the most recent message in each listing.
This is my sql query
SELECT *
FROM (
SELECT *
FROM message
WHERE `from member_id`=1 OR `to member_id`=1
ORDER BY IF(`from member_id`=1, `to member_id`, `from member_id`)
) as t
GROUP BY IF(`from member_id`=1, `to member_id`, `from member_id`)
I just hardcoded 1 for now to be the current user. What I am doing is, sorting them by the id of the other person which I can check using the if statement, then grouping that result so I try to get the recent one from each conversation.
The problem is that when grouping, each group can have more than 1 rows, and it just seems to pick some random row. How can I get it to pick the row that has the most recent date sent value?
Are you looking for something like this?
SELECT m.*
FROM message m JOIN
(
SELECT from_member_id, to_member_id, MAX(date_sent) date_sent
FROM message
WHERE from_member_id = 1
GROUP BY from_member_id, to_member_id
) q
ON m.from_member_id = q.from_member_id
AND m.to_member_id = q.to_member_id
AND m.date_sent = q.date_sent
ORDER BY date_sent DESC
Sample output:
| FROM_MEMBER_ID | TO_MEMBER_ID | DATE_SENT |
----------------------------------------------
| 1 | 3 | 2013-06-13 |
| 1 | 2 | 2013-06-12 |
Here is SQLFiddle demo
UPDATE
SELECT m.*
FROM message m JOIN
(
SELECT LEAST(from_member_id, to_member_id) least_id,
GREATEST(from_member_id, to_member_id) greatest_id,
MAX(date_sent) date_sent
FROM message
WHERE from_member_id = 1
OR to_member_id = 1
GROUP BY LEAST(from_member_id, to_member_id),
GREATEST(from_member_id, to_member_id)
) q
ON LEAST(m.from_member_id, m.to_member_id) = q.least_id
AND GREATEST(m.from_member_id, m.to_member_id) = q.greatest_id
AND m.date_sent = q.date_sent
ORDER BY date_sent DESC
Sample output:
| FROM_MEMBER_ID | TO_MEMBER_ID | DATE_SENT |
----------------------------------------------
| 3 | 1 | 2013-06-14 |
| 1 | 2 | 2013-06-12 |
Here is SQLFiddle demo
SELECT
*
FROM message m INNER JOIN
(
SELECT
from_menber_id,
MAX(date_sent) AS sentdate
FROM message s
GROUP BY from_menber_id
) AS a
ON m.date_sent = a.sentdate AND a.from_menber_id = m.from_menber_id

MySQL How can I add values of a column together and remove the duplicate rows?

Good day,
I have a MySQL table which has some duplicate rows that have to be removed while adding a value from one column in the duplicated rows to the original.
The problem was caused when another column had the wrong values and that is now fixed but it left the balances split among different rows which have to be added together. The newer rows that were added must then be removed.
In this example, the userid column determines if they are duplicates (or triplicates). userid 6 is duplicated and userid 3 is triplicated.
As an example for userid 3 it has to add up all balances from rows 3, 11 and 13 and has to put that total into row 3 and then remove rows 11 and 13. The balance columns of both of those have to be added together into the original, lower ID row and the newer, higher ID rows must be removed.
ID | balance | userid
---------------------
1 | 10 | 1
2 | 15 | 2
3 | 300 | 3
4 | 80 | 4
5 | 0 | 5
6 | 65 | 6
7 | 178 | 7
8 | 201 | 8
9 | 92 | 9
10 | 0 | 10
11 | 140 | 3
12 | 46 | 6
13 | 30 | 3
I hope that is clear enough and that I have provided enough info. Thanks =)
Two steps.
1. Update:
UPDATE
tableX AS t
JOIN
( SELECT userid
, MIN(id) AS min_id
, SUM(balance) AS sum_balance
FROM tableX
GROUP BY userid
) AS c
ON t.userid = c.userid
SET
t.balance = CASE WHEN t.id = c.min_id
THEN c.sum_balance
ELSE 0
END ;
2. Remove the extra rows:
DELETE t
FROM
tableX AS t
JOIN
( SELECT userid
, MIN(id) AS min_id
FROM tableX
GROUP BY userid
) AS c
ON t.userid = c.userid
AND t.id > c.min_id
WHERE
t.balance = 0 ;
Once you have this solved, it would be good to add a UNIQUE constraint on userid as it seems you want to be storing the balance for each user here. That will avoid any duplicates in the future. You could also remove the (useless?) id column.
SELECT SUM(balance)
FROM your_table
GROUP BY userid
Should work, but the comment saying fix the table is really the best approach.
You can create a table with the same structure and transfer the data to it with this query
insert into newPriceTable(id, userid, balance)
select u.id, p.userid, sum(balance) as summation
from price p
join (
select userid, min(id) as id from price group by userid
) u ON p.userid = u.userid
group by p.userid
Play around the query here: http://sqlfiddle.com/#!2/4bb58/2
Work is mainly done in MSSQL but you should be able to convert the syntax.
Using a GROUP BY UserID you can SUM() the Balance, join that back to your main table to update the balance across all the duplicates. Finally you can use RANK() to order your duplicate Userids and preserve only the earliest values.
I'd select all this into a new table and if it looks good, deprecate your old table and rename then new one.
http://sqlfiddle.com/#!3/068ee/2