Trying to simplify a MySQL query - php

I would really appreciate any help trying to simplify a MySQL query. The purpose of the query is to retrieve messages from a messages table (users_messages) which has the following columns: message_id, from_id, to_id, message_content, date_sent.
The from_id and to_id need to join a users table (users) which has these columns: user_id, user_username.
Also I should mention that there is a blocked users table (users_blocked) which filters out any messages should the user_id feature in this table.
All this works fine and messages are ordered with the newest first which is what I want. My only problem is that it's not pulling the corresponding 'message_content'. i.e. it's pulling the most recent date, but not the most recent message.
Perhaps I need a different approach (e.g. subqueries) but I can not get my head around it.
Here is the query:
select m.message_content,
if(from_id < to_id, concat(from_id,to_id), concat(to_id,from_id)) as ft,
if (from_id = $my_id, to_id, from_id) as other_id,
max(date_sent) as most_recent
from users_messages m
left join users_blocked ub1 on (from_id = ub1.blocked_id and ub1.user_id = $my_id)
left join users_blocked ub2 on (to_id = ub2.blocked_id and ub2.user_id = $my_id)
where
(from_id = $my_id or to_id = $my_id)
and ub1.blocked_id is null
and ub2.blocked_id is null
group by
ft
order by
most_recent desc
Sorry, here are the table structures:
users
user_id user_username
1 Simon
2 Amber
3 Tom
users_messages
message_id from_id to_id date_sent message_content
1 1 2 2012-07-04 11:52:12 Hello
2 1 2 2012-07-04 12:32:24 Another message
3 1 2 2012-07-04 14:00:00 Hello again
users_blocked
user_id blocked_id
1 3

Try:
select m.message_content,
x.ft,
x.other_id,
x.most_recent
from (select if(from_id < to_id, concat(from_id,to_id), concat(to_id,from_id)) as ft,
if(from_id = $my_id, to_id, from_id) as other_id,
max(date_sent) as most_recent
from users_messages um
left join users_blocked ub1
on (um.from_id = ub1.blocked_id and ub1.user_id = $my_id)
left join users_blocked ub2
on (um.to_id = ub2.blocked_id and ub2.user_id = $my_id)
where ub1.blocked_id is null and ub2.blocked_id is null and
(um.from_id = $my_id or um.to_id = $my_id)
group by ft) x
join users_messages m
on m.date_sent = x.most_recent and
m.from_id in ($my_id, x.other_id) and
m.to_id in ($my_id, x.other_id)
order by
x.most_recent desc
SQLFiddle here.

Here I am assuming SimonKing wants, message content from users_messages table which includes following condition,
Users should not blocked either directions,
The most recent messages transferred between the users
So that, I modified Mark Bannister query as follows,
SELECT temp.* FROM (
SELECT um.*, concat(um.from_id,to_id) as direction FROM userMessages um
LEFT JOIN userBlocked ub1 ON um.from_id = ub1.user_id AND um.to_id = ub1.blocked_id
LEFT JOIN userBlocked ub2 ON um.to_id = ub2.user_id AND um.from_id = ub2.blocked_id
WHERE ub1.user_id is null AND ub1.blocked_id is null AND ub2.user_id is null AND ub2.blocked_id is null
ORDER BY um.date_sent DESC
) temp
GROUP BY direction
SQL fiddle is http://sqlfiddle.com/#!2/bdc77/1/0

As i understand, the main problem of this request is that result contains only first dates, not messages. To fix this you can do this:
make prepared data set which will have most recent dates:
select to_id, from_id, max(date_sent) as most_recent
from users_messages m
left join users_blocked ub on ub.user_id = $my_id
and ub.blocked_id in (to_id, from_id)
where
(from_id = $my_id or to_id = $my_id)
and ub.blocked_id is null
group by
to_id, from_id
order by
most_recent desc
I see you groping data by two collumns to_id, from_id. This subquery isn't best place to calculate things like:
if(from_id < to_id, concat(from_id,to_id), concat(to_id,from_id)) as ft
Then just select other needed data from users_messages, which matches our to_id, from_id and recent_date from prepared table:
select um.* from
(
select to_id, from_id, max(date_sent) as most_recent
from users_messages m
left join users_blocked ub on ub.user_id = 1
and ub.blocked_id in (to_id, from_id)
where
(from_id = 1 or to_id = 1)
and ub.blocked_id is null
group by
to_id, from_id
order by
most_recent desc
) as prepared_messages
left join users_messages um on um.from_id = prepared_messages.from_id
and um.to_id = prepared_messages.to_id
and um.date_sent = prepared_messages.most_recent

Related

query with dependent subqueries too slow

select mt.from_user, mt.to_user, mt.group_id, g.name, g.created_by as adminuser,
msg.*,
(
SELECT id
from messages
where t.thread_id = thread_id
and id NOT IN (
SELECT message_id from message_deleted
where user_id=275 and status='deleted' )
order by CreatedDate DESC
limit 1
) as msgid,
(
SELECT CreatedDate
from messages
where t.thread_id = thread_id
and id NOT IN (
SELECT message_id from message_deleted
where user_id=275 and status='deleted' )
order by CreatedDate DESC
limit 1
) as msgDate
from user_thread as t
left join message_thread as mt ON t.thread_id = mt.id
left join group_master as g ON mt.group_id = g.id
left join group_member as gm ON gm.group_id = g.id
left join messages as msg ON t.thread_id = msg.thread_id
where ( gm.user_id=275
or msg.from_id=275
or msg.to_id=275
)
and t.status = 'Active'
group by mt.id
order by msgDate DESC
This takes about 50 sec.
In above code, I have try to split above query and note that below subquery take too much time to execute. Can I convert subquery into join. please help me. I am stuck.please note that all tables which are joined are necessary.
(
SELECT id
from messages
where t.thread_id = thread_id
and id NOT IN (
SELECT message_id from message_deleted
where user_id=275 and status='deleted' )
order by CreatedDate DESC
limit 1
) as msgid,
(
SELECT CreatedDate
from messages
where t.thread_id = thread_id
and id NOT IN (
SELECT message_id from message_deleted
where user_id=275 and status='deleted' )
order by CreatedDate DESC
limit 1
) as msgDate
First, You are misusing a notorious MySQL extension to GROUP BY. This will probably cause your results to be unpredictable. Read this. https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html
Second, You have a couple of nested dependent subqueries. The first of them is this.
(select id
from messages
where t.thread_id = thread_id
and id NOT IN (select message_id
from message_deleted
where user_id=275
and status='deleted')
order by CreatedDate DESC limit 1) as msgid
Such nested dependent subqueries perform notoriously badly. They're even worse when they contain LIMIT clauses. Your route to fixing this is refactoring into an independent query and then JOINing it.
This may work as a replacement for the query to find the most recent undeleted message on the thread.
SELECT MAX(m.id) id, m.thread_id
FROM messages m
LEFT JOIN message_deleted d
ON m.id = d.id
AND d.user_id = 275
AND d.status = 'deleted'
WHERE d.id IS NULL
GROUP BY m.thread_id
This uses the LEFT JOIN .... IS NULL pattern in place of NOT IN. It's faster. It uses the MAX(id) method of finding the most recent row in a table in place of the ORDER BY CreatedDate DESC LIMIT 1 method, which is also much faster. It's good because it's guaranteed to generate either 0 or 1 row per value of thread_id. That means you can use it in a LEFT JOIN ... ON ... thread_id operation and not add any rows to your result set.
You can test this subquery by running it. Then you JOIN it, as if it were a table, to the rest of your query, something like this.
SELECT whatever,
q.id, r.CreatedDate
FROM whatever
LEFT JOIN (
SELECT MAX(m.id) id, m.thread_id
FROM messages m
LEFT JOIN message_deleted d
ON m.id = d.id
AND d.user_id = 275
AND d.status = 'deleted'
WHERE d.id IS NULL
GROUP BY m.thread_id
) q ON q.id = t.id
LEFT JOIN messages r ON r.id = q.id
The second LEFT JOIN operation here is used to retrieve the CreatedDate value of the newest undeleted message from the messages table.

How to fetch last message from all conversations with every other user from messages table?

I am using laravel framework 5.3. I am getting a wrong result after executing query. I have two table one is user and other is message.Here is my scenario
enter code here
User table-
id name
1 a
2 b
3 c
Message table:
id sender_id receiver_id message
1 1 2 hii
2 2 1 hello
3 1 3 hiiii
Now I want to fetch the last message between those users having sender_id and receiver_id
here is my code:-
enter code here
$coreQueryUser=DB::select(
'
select m.* ,u.*
from
messages m
inner join (
select max(id) as maxid
from messages
where messages.sender_id = 1 // here i am sending userid is 1
group By (if(sender_id > receiver_id, sender_id, receiver_id)),
(if(sender_id > receiver_id, receiver_id, sender_id))
) t1 on m.id=t1.maxid
join
users u ON u.id = (CASE WHEN m.sender_id = 1
THEN m.sender_id
ELSE m.receiver_id
END)
'
);
Note- I want to find all the last messages that been with userid (1)
Thanks in advance :)
Use the ORDER BY phrase and limit your results to 1 using LIMIT.
My answer is assuming that id row is your primary key and it is auto incremented
Edit of your code \
enter code here
$coreQueryUser=DB::select(
'
select m.* ,u.*
from
messages m
inner join (
select max(id) as maxid
from messages
where messages.sender_id = 1 // here i am sending userid is 1
group By (if(sender_id > receiver_id, sender_id, receiver_id)),
(if(sender_id > receiver_id, receiver_id, sender_id))
) t1 on m.id=t1.maxid
join
users u ON u.id = (CASE WHEN m.sender_id = 1
THEN m.sender_id
ELSE m.receiver_id
END)
ORDER BY m.id DESC LIMIT 1
'
);

How to get last data using two join

I have one chat table there is reference id of user table use in user_from and user_to.
Now I have to a query for get a chat member list from chat table like facebook caht list.
with whom I have chat and I have to find name of user from user table and unread counter
Please give me solution for this
Thanks
I think your subquery needs to be correlated, which will be slow.
Use JOIN instead.
SELECT
users.id AS userid,
chat_box.id AS msgid,
first_name,
last_name,
profile_pic,
LEFT(message, 50) AS message,
u.unreadcounter
FROM
chat_box
LEFT JOIN
users ON (chat_box.user_from = users.id)
LEFT JOIN
(SELECT
u.id, COUNT(c.id) unreadcounter
FROM
chat_box c
JOIN users u ON (c.user_from = u.id)
WHERE
c.read = 0 && c.user_to = 45
GROUP BY u.id) u ON users.id = u.id
WHERE
(user_from = 45 OR user_to = 45)
&& users.id != 45
GROUP BY users.id
UNION SELECT
users.id AS userid,
chat.id AS msgid,
first_name,
last_name,
profile_pic,
LEFT(message, 50) AS message,
0 AS unreadcounter
FROM
chat_box AS chat
LEFT JOIN
users ON (chat.user_to = users.id)
WHERE
(user_from = 45 OR user_to = 45)
&& users.id != 45
GROUP BY users.id
ORDER BY msgid DESC

SQL error using a CTE

I'm getting this error :
Query Error : You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ';WITH convs AS ( select c.id, c.title, c.seen, c.id_receiver, c.id_send' at line 1
when i use this query :
$query = ";WITH convs AS (
select c.id, c.title, c.seen, c.id_receiver, c.id_sender
from conversations c
)
select id, title, seen, id_receiver, id_sender
from convs
where id_receiver = '5'
order by title desc limit 0,25";
$res = mysqli_query($connection ,$query);
Am i missing something ?
Your help would be much appreciated.
PS : i minimised the query to make it simple for this context, if you help me find the solution, i may have another problem with the full query. So i might come back to you for more help. Thank's in advance.
EDIT (WHOLE QUERY)
$query = "WITH convs AS (
select c.id, c.title, c.seen, c.id_receiver, c.id_sender,
(select max(date) from messages where id_conversation = c.id and id_user <> '$iduser') as last_msg,
(select top 1 id_user from messages where id_conversation = c.id and id_user <> '$iduser' order by date desc) as last_user,
(select count(distinct id_user) from messages where id_conversation = c.id) as nbruser,
(select count(*) from messages where id_conversation = c.id) as nbrmsg,
(select username from users where id = c.id_sender) as sender, (select username from users where id = c.id_receiver) as receiver,
(select count(*) from deleted_conversations where id_user='$iduser' and id_conversation=c.id) as deleted,
from conversations c
)
select id, title, seen, id_receiver, id_sender, receiver, sender, last_msg, last_user, deleted, nbruser, nbrmsg
from convs
where (id_receiver = '$iduser' or (id_sender == '$iduser' and nbruser > 1)) and deleted = 0
order by last_msg desc limit $pageLimit,$REC_PER_PAGE";
What pushed me to use CTE is the need of using aliases in where clause. And as you can see i have many of them.
Can you give me an example of how to use views/temporary tables to achieve my purpose ?
MySQL/MariaDB doesn't support CTEs. Plus, it is entirely unnecessary in this case:
select id, title, seen, id_receiver, id_sender
from conversations c
where id_receiver = '5'
order by ?? desc
limit 0, 25;
Note: You need to specify the column for the order by as well.
For more complex examples, you can use subqueries, views, and/or temporary tables.
CTEs are quite similar to Derived Tables:
select id, title, seen, id_receiver, id_sender, receiver, sender, last_msg, last_user, deleted, nbruser, nbrmsg
FROM
(
select c.id, c.title, c.seen, c.id_receiver, c.id_sender,
(select max(date) from messages where id_conversation = c.id and id_user <> '$iduser') as last_msg,
(select top 1 id_user from messages where id_conversation = c.id and id_user <> '$iduser' order by date desc) as last_user,
(select count(distinct id_user) from messages where id_conversation = c.id) as nbruser,
(select count(*) from messages where id_conversation = c.id) as nbrmsg,
(select username from users where id = c.id_sender) as sender, (select username from users where id = c.id_receiver) as receiver,
(select count(*) from deleted_conversations where id_user='$iduser' and id_conversation=c.id) as deleted,
from conversations c
) as convs
where (id_receiver = '$iduser' or (id_sender == '$iduser' and nbruser > 1)) and deleted = 0
order by last_msg desc limit $pageLimit,$REC_PER_PAGE

Combining two queries into one

I have two type of message, one is private message and another is credit message are inserting into two different table. Now I'm trying to fetch the data.
SELECT * ,(SELECT COUNT(*)
FROM votes
WHERE message_id = m.message_id
AND vote_type = 'like') AS likes,
(SELECT COUNT(*)
FROM votes
WHERE message_id = m.message_id
AND vote_type = 'dislike') AS dislikes
FROM messages m
WHERE 1 #and hidden is null
and recipient_id = 1
ORDER BY datetime DESC
and
SELECT * ,(SELECT COUNT(*)
FROM votes
WHERE message_id = m.message_id
AND vote_type = 'like') AS likes,
(SELECT COUNT(*)
FROM votes
WHERE message_id = m.message_id
AND vote_type = 'dislike') AS dislikes
FROM private_messages m
WHERE 1 #and hidden is null
and recipient_id = 1
ORDER BY datetime DESC
Now want to merge them into one query one extra parameter will show its private message or credit message.
If the existing queries do what you want/need, UNION will make it pretty simple to combine them, something like;
SELECT * FROM (
SELECT is_private 0, <field1>,<field2>,<field3>, ... ,(SELECT COUNT(*)
FROM votes
WHERE message_id = m.message_id
AND vote_type = 'like') AS likes,
(SELECT COUNT(*)
FROM votes
WHERE message_id = m.message_id
AND vote_type = 'dislike') AS dislikes
FROM messages m
WHERE 1 #and hidden is null
and recipient_id = 1
UNION ALL
SELECT 1, <field1>, <field2>, <field3>, ... ,(SELECT COUNT(*)
FROM votes
WHERE message_id = m.message_id
AND vote_type = 'like') AS likes,
(SELECT COUNT(*)
FROM votes
WHERE message_id = m.message_id
AND vote_type = 'dislike') AS dislikes
FROM private_messages m
WHERE 1 #and hidden is null
and recipient_id = 1
)
ORDER BY datetime DESC
Note that you need to select the same number/order of columns from both queries for the union to work. SELECT * makes it hard to verify if/that that is the case. If
You can use UNION to combine mysql query:
SELECT id FROM table_user
UNION
SELECT COUNT(vote) FROM table_vote
You can use multiple UNION query by UNION.
Like,
Query 1
UNION
Query 2
UNION
.....
.....
.....
Query n
I think the following is a much simpler way to express the logic:
select m.*, v.likes, v.dislikes
from (select v.message_id, sum(vote_type = 'like') AS likes, sum(vote_type = 'dislike') AS dislikes
from votes v
group by v.message_id
) v join
(select m.*, 'Message' as which
from messages
union all
select pm.*, 'PrivateMessage' as which
from private_messages pm
) m
on v.message_id = m.message_id;

Categories