I have two mysql tables Users & Messages. Please will you help me to create an sql query which returns only the most recent message either sent or received by the logged in user Mike ( user_id=1 ) along with the message_id, message_text & first_name of the person who the message was sent to or received from. Ordered by message_id in descending order.
So far i have the query:-
SELECT message_id, first_name, message_text FROM tbl_users, tbl_messages
WHERE
(tbl_users.user_id = tbl_messages.sender_id AND tbl_messages.receiver_id = 1
OR tbl_users.user_id = tbl_messages.receiver_id AND tbl_messages.sender_id = 1)
GROUP BY tbl_users.first_name
ORDER BY message_id DESC
Which gives the result in the image below. It seems the ORDER BY is being ignored
Thank you in advance
tbl_users
---------------------------
| user_id | first_name |
---------------------------
| 1 | Mike |
| 2 | John |
| 3 | George |
| 4 | Peter |
| 5 | Sarah |
---------------------------
tbl_messages
----------------------------------------------------------------
| message_id | sender_id | receiver_id | message_text |
----------------------------------------------------------------
| 1 | 2 | 1 | Hello |
| 2 | 3 | 1 | How are you |
| 3 | 1 | 5 | Hi there |
| 4 | 2 | 1 | Greetings |
| 5 | 1 | 4 | Good day |
| 6 | 3 | 1 | Hi |
| 7 | 5 | 1 | A message |
| 8 | 5 | 4 | Good morning |
| 9 | 1 | 5 | Hello dear |
| 10 | 1 | 3 | Howdy |
----------------------------------------------------------------
Desired result
----------------------------------------------
| message_id | first_name | message_text |
----------------------------------------------
| 10 | George | Howdy |
| 9 | Sarah | Hello dear |
| 5 | Peter | Good day |
| 4 | John | Greetings |
----------------------------------------------
Database is available here
Just adding a GROUP BY clause may solve your problem, try this...
SELECT message_id, first_name, message_text FROM tbl_users, tbl_messages
WHERE
(tbl_users.user_id = tbl_messages.sender_id AND tbl_messages.receiver_id = 1
OR tbl_users.user_id = tbl_messages.receiver_id AND tbl_messages.sender_id = 1)
GROUP BY tbl_users.user_id
ORDER BY message_id DESC
UPDATE: Changing the whole query
SELECT t2.message_id, t2.first_name, t2.message_text FROM
(SELECT u.user_id, m.message_id, u.first_name, m.message_text FROM `tbl_users` u
JOIN `tbl_messages` m ON (u.user_id = m.sender_id AND m.receiver_id = 1)
OR (u.user_id = m.receiver_id AND m.sender_id = 1)
ORDER BY m.message_id DESC
) t2
GROUP BY t2.user_id
ORDER BY t2.message_id DESC
RESULT:
+------------+------------+--------------+
| message_id | first_name | message_text |
+------------+------------+--------------+
| 10 | George | Howdy |
| 9 | Sarah | Hello dear |
| 5 | Peter | Good Day |
| 4 | John | Greetings |
If you GROUP BY the field which you want to de-duplicate on. In this case GROUP BY tbl_users.first_name should do the trick.
You are missing the table name in the ORDER BY clause. Also I suggest using aliases on the tables. Thus, your query might look like this:
/* All Mike's messages */
SELECT
m.message_id,
u.first_name,
m.message_text
FROM
tbl_users AS u,
tbl_messages AS m
WHERE
(
u.user_id = m.sender_id AND
m.receiver_id = 1
) OR (
u.user_id = m.receiver_id AND
m.sender_id = 1
)
ORDER BY
m.message_id DESC;
However, I think it would be better to get the sent and received messages results separately. So:
/* Messages sent my Mike */
SELECT
m.message_id,
u.first_name,
m.message_text
FROM
tbl_users AS u
INNER JOIN tbl_messages AS m ON m.receiver_id = u.user_id
WHERE
m.sender_id = 1
ORDER BY
m.message_id DESC;
/* Messages received my Mike */
SELECT
m.message_id,
u.first_name,
m.message_text
FROM
tbl_users AS u
INNER JOIN tbl_messages AS m ON u.user_id = m.sender_id
WHERE
m.receiver_id = 1
ORDER BY
m.message_id DESC;
I am not sure you can achieve this through join. A sub select query may help you.
select a.first_name,
(select message_text from tbl_messages b
where (a.user_id = b.sender_id and b.sender_id = 1)
or (a.user_id = b.receiver_id and b.receiver_id = 1)
order by message_id DESC
LIMIT 1)
from tbl_users a;
Related
I have a table structure similar to this:
users
| id | name |
|----|--------|
| 1 | jane |
| 2 | john |
client
| id | name | user_id |
|----|------------|---------|
| 1 | client 1 | 2 |
| 2 | client 2 | 2 |
| 3 | client 3 | 2 |
| 4 | client 4 | 1 |
| 5 | client 5 | 1 |
products
| id | name | user_id |
|----|------------|---------|
| 1 | product 1 | 1 |
| 2 | product 2 | 1 |
| 3 | product 3 | 1 |
| 4 | product 4 | 2 |
| 5 | product 5 | 2 |
and I also created two views
client_total
SELECT user_id, COUNT(id) AS client_total FROM client GROUP BY user_id
| user_id | client_total |
|---------|--------------|
| 1 | 2 |
| 2 | 3 |
products_total
SELECT user_id, COUNT(id) AS products_total FROM products GROUP BY user_id
| user_id |products_total|
|---------|--------------|
| 1 | 3 |
| 2 | 2 |
and that was a result
SELECT
users.*,
client_total.client_total,
products_total.products_total
FROM
users
LEFT JOIN client_total ON users.user_id = client_total.user_id
LEFT JOIN products_total ON users.user_id = products_total.user_id
| id | name |products_total| client_total |
|----|--------|--------------|--------------|
| 1 | jane | 3 | 2 |
| 2 | john | 2 | 3 |
My question is:
Can I get this same result without using these two views?
GROUP BY in derived tables (the subqueries), before you LEFT JOIN:
SELECT
u.id,
u.name,
c.client_total,
p.products_total
FROM users u
LEFT JOIN
(SELECT user_id, COUNT(id) AS client_total FROM client GROUP BY user_id) c
ON u.id = c.user_id
LEFT JOIN
(SELECT user_id, COUNT(id) AS products_total FROM products GROUP BY user_id) p
ON u.id = p.user_id
(Doesn't even some people call these derived tables inline views?)
You may try the following direct aggregation join query:
SELECT
u.id,
u.name,
COUNT(DISTINCT p.id) AS products_total,
COUNT(DISTINCT c.id) AS client_total
FROM users u
LEFT JOIN client c
ON u.id = c.user_id
LEFT JOIN products p
ON u.id = p.user_id
GROUP BY
u.id,
u.name;
Demo
Often the most performant way to accomplish this uses correlated subqueries:
SELECT u.id, u.name,
(SELECT COUNT(*)
FROM client c
WHERE c.user_id = u.id
) as client_total,
(SELECT COUNT(*)
FROM products p
WHERE p.user_id = u.id
) as products_total
FROM users u;
In particular, this can take advantage of indexes on client(user_id) and products(user_id).
I've been having hard times figuring out how to select the following...
I have two tables
Table_users Table_followers
| id | name | | follow_id | user_id |
| 1 | John | | 1 | 2 |
| 2 | Max | | 3 | 1 |
| 3 | Mary | | 2 | 1 |
| 4 | Robert | | 6 | 1 |
| 5 | Robin | | 1 | 5 |
| 6 | Sarah | | 1 | 6 |
I'd like to return in a single query users who are following John and John is following them back, so that would be called MATCH.
Then users who are following John, FOLLOWERS
And finally users followed by John, FOLLOWING
I've used the following query, but it returns duplicates and it's far from what I'm looking for
SELECT u.id, u.name, f.follower_id, f.user_id
FROM table_users u
LEFT JOIN table_followers f ON f.follower_id = u.id OR f.user_id = u.id
WHERE (f.user_id != 1 OR f.follower_id != 1) AND u.id != 1
ORDER BY u.id ASC";
Desired result would be like...
| uid | name | match | follower | following |
| 2 | Max | 1 | null | null |
| 6 | Sarah | 1 | null | null |
| 3 | Mary | null | 1 | null |
| 5 | Robin | null | null | 1 |
Would it be possible at all with SQL?
One way to solve this is to join the follower table twice (once for followers, once for following) and do a query like this:
select
u.id,
u.name,
case when follow_id and user_id then 1 end as `match`,
case when follow_id and user_id is null then 1 end as follower,
case when user_id and follow_id is null then 1 end as following
from Table_users u
left join (select user_id from Table_followers where follow_id = 1) followers
on u.id = followers.user_id
left join (select follow_id from Table_followers where user_id = 1) following
on u.id = following.follow_id
where u.id <> 1 and (follow_id or user_id)
order by `match` desc, follower desc, following desc, u.id;
I'm sure there are more efficient and cleaner ways to do this, but it's late and the old brain is only working at half speed ;)
Sample SQL Fiddle
With MySQL the select part can be further reduced to this:
select
u.id,
u.name,
ifnull((follow_id and user_id),0) as `match`,
(follow_id and user_id is null) as follower,
(user_id and follow_id is null) as following
from Table_users u
But this would give you 0 instead of null for the missing values. (Sample).
Here is how my database looks like:
table: conversations
+----+--------+--------+
| id | user_1 | user_2 |
+----+--------+--------+
| 1 | 1 | 2 |
| 2 | 2 | 3 |
| 3 | 1 | 3 |
+----+--------+--------+
table: messages
+----+--------------+------+
| id | conversation | text |
+----+--------------+------+
| 1 | 1 | hej |
| 2 | 1 | test |
| 3 | 2 | doh |
| 4 | 2 | hi |
| 5 | 3 | :) |
| 6 | 3 | :D |
+----+--------------+------+
Then when I run the followin query:
SELECT
*
FROM `messages`
INNER JOIN `conversations`
ON `conversations`.`id` = `messages`.`convesations`
GROUP BY `conversations`.`id`
ORDER BY `messages`.`id` DESC
Then I get those out from messages:
+----+--------------+------+
| id | conversation | text |
+----+--------------+------+
| 1 | 1 | hej |
| 3 | 2 | doh |
| 5 | 3 | :) |
+----+--------------+------+
But, is it somehow possible to do so that I will get the messages with the highest messages.id from that group, instead of the lowest?
EDIT: Here is the output I want from messages:
+----+--------------+------+
| id | conversation | text |
+----+--------------+------+
| 2 | 1 | test |
| 4 | 2 | hi |
| 6 | 3 | :D |
+----+--------------+------+
As those are the messages in same conversation with the highest id.
SELECT *
FROM conversations c
JOIN messages m
ON m.id =
(
SELECT id
FROM messages mi
WHERE mi.conversation = c.id
ORDER BY
mi.conversation DESC, mi.id DESC
LIMIT 1
)
Create an index on messages (conversation, id) for this to work fast.
You simply need to use nested query like this:
SELECT * FROM Messages
WHERE ID IN(
SELECT Max(m.ID) FROM Messages m
INNER JOIN conversations c
ON c.id = m.conversation
GROUP BY m.conversation
);
Output:
| ID | CONVERSATION | TEXT |
----------------------------
| 2 | 1 | test |
| 4 | 2 | hi |
| 6 | 3 | :D |
If you want data from both tables try this:
SELECT * FROM Messages m
JOIN conversations c
ON c.id = m.conversation
WHERE m.ID IN (
SELECT MAX(ID) FROM Messages
GROUP BY conversation
)
GROUP BY m.conversation;
Output:
| ID | CONVERSATION | TEXT | USER_1 | USER_2 |
----------------------------------------------
| 2 | 1 | test | 1 | 2 |
| 4 | 2 | hi | 2 | 3 |
| 6 | 3 | :D | 1 | 3 |
See this SQLFiddle
You are making your Join on the wrong column. 'Id' in Conversation cannot be equal to 'Id' in messages.
I thin, 'Conversation' in table messsages is 'id_conversation' right?
So, if I understood well :
SELECT *
FROM messages
INNER JOIN conversations
ON conversations.id = messages.conversation
GROUP BY conversations.id
ORDER BY messages.id DESC
I think you just have an incorrect table join:
SELECT *
FROM `messages`
INNER JOIN `conversations`
ON `conversations`.`id` = `messages`.`conversation`
GROUP BY `conversations`.`id`
ORDER BY `messages`.`id` DESC
Edit
You can try this:
SELECT *
FROM `messages`
WHERE `messages`.`id` IN (
SELECT MAX(id)
FROM messages
GROUP BY conversation
)
A couple of different approaches:
This approach relies on known but undocumented behaviour within MySQL, where the unaggregated, ungrouped values returned in a grouped query are the first in the sort order - it's fast, but should not be viewed as reliable:
SELECT * FROM
(SELECT * FROM messages
ORDER BY conversation, id desc) a
GROUP BY conversation
Alternatively, an approach that should always be reliable:
SELECT m.*, c.user_1, c.user_2 FROM messages m
JOIN (select conversation, max(id) max_id from messages group by conversation) l
ON m.id = l.max_id
JOIN conversations c
ON c.id = m.conversation
GROUP BY conversation
SQLFiddle here.
Hey again, im still getting a hang of Queries and stuff so please excuse all the frequent sql questions 8)
Anyways im trying to select rows after a certain value.
Dont understand? Ok this is how it actually looks.
Table: messages
+------------+-----------+---------+-------+---------------------------------------+----------------+------------------+
| message_id | thread_id | user_id | to_id | body | message_status | uid_sent_deleted |
+------------+-----------+---------+-------+---------------------------------------+----------------+------------------+
| 1 | 1 | 1 | 7 | How are you bro? | read | 1 |
| 2 | 1 | 7 | 1 | IM good what about you kenny? | read | 0 |
| 3 | 1 | 1 | 7 | Same just chilling how is your sister | read | 1 |
| 4 | 1 | 7 | 1 | Shes coool u know just doin great | read | 0 |
| 7 | 1 | 1 | 7 | Thats nice | read | 1 |
| 8 | 1 | 7 | 1 | Yupp | read | 0 |
| 9 | 1 | 1 | 7 | hhahaha | read | 1 |
| 10 | 1 | 7 | 1 | anyways.... | unread | 0 |
+------------+-----------+---------+-------+---------------------------------------+----------------+------------------+
Where the message id is 9, is has the value of '1' in the column uid_sent_deleted.
Im trying to select rows after the last row that has a value of '1' in the column uid_sent_deleted.
here is the sql that im currently working with. Thanks for the help!!
SELECT message_id, thread_id, messages.user_id, to_id, body, message_status, uid_sent_deleted
FROM messages
INNER JOIN messages_thread ON messages_thread.id = messages.thread_id
WHERE thread_id =1
GROUP BY messages.message_id
ORDER BY message_id ASC
... WHERE thread_id = 1 AND id >
(SELECT MAX(id) FROM messages WHERE uid_sent_deleted = 1)
SELECT message_id, thread_id, messages.user_id, to_id, body, message_status, uid_sent_deleted
FROM
(SELECT thread_id t_id, MAX(message_id) m_id
FROM messages
WHERE thread_id =1 and uid_sent_deleted = 1
GROUP BY thread_id) n
inner join messages on messages.message_id > n.m_id and messages.thread_id = n.t_id
INNER JOIN messages_thread ON messages_thread.id = messages.thread_id
ORDER BY message_id ASC
LIMIT 1
A subquery might work, you select the MAX(message_id) WHERE uid_sent_deleted=1, then do you join ... little hazy on the syntax.
Say, I have two tables like these:
Table group Table user
+----+-----------+ +----+----------+------+----------+
| id | groupname | | id | username | rank | group_id |
+----+-----------+ +----+----------+------+----------+
| 1 | Friends | | 1 | Frank | 1 | 1 |
| 2 | Family | | 2 | Mike | 3 | 1 |
+----+-----------+ | 3 | Steve | 2 | 1 |
| 4 | Tom | 1 | 2 |
+----+----------+------+----------+
And I want to select all groups and get the user with the highest rank (the highest number) for each group. So basically I want to get this result:
+-----------------+----------+---------+---------------+
| group.groupname | group.id | user.id | user.username |
+-----------------+----------+---------+---------------+
| Friends | 1 | 2 | Mike |
| Family | 2 | 4 | Tom |
+-----------------+----------+---------+---------------+
How has the select to be like?
It maybe very simple, but I'm not getting it right now....
Edit 2:
My previous answer was wrong, the max() call destroyed the result. Here's a correct solution:
SELECT g.groupname, g.id AS group_id, u.id AS user_id, u.username
FROM `user` u
LEFT JOIN `group` g ON (u.group_id=g.id)
WHERE u.rank=(
SELECT MAX(rank)
FROM `user` u2
WHERE u.group_id=u2.group_id
)
The check in the WHERE clause should be more understandable, too.
mysql> SELECT g.groupname, g.id AS group_id, u.id AS user_id, u.username
-> FROM `user` u
-> LEFT JOIN `group` g ON (u.group_id=g.id)
-> WHERE u.rank=(
-> SELECT MAX(rank)
-> FROM `user` u2
-> WHERE u.group_id=u2.group_id
-> );
+-----------+----------+---------+----------+
| groupname | group_id | user_id | username |
+-----------+----------+---------+----------+
| Friends | 1 | 2 | Mike |
| Family | 2 | 4 | Tom |
+-----------+----------+---------+----------+
2 rows in set (0.00 sec)
select g.groupname, u.group_id, u.id as user_id, u.username
from group g
inner join (
select group_id, max(rank) as MaxRank
from user
group by group_id
) um on g.id = um.group_id
inner join user u on um.group_id = u.group_id and um.MaxRank = u.rank