Enhancing performance of SQL query - mysql

I am running a query on three tables messages, message_recipients and users.
Table structure of messages table:
id int pk
message_id int
message text
user_id int
...
Index for this table is on user_id, message_id and id.
Table structure of message_recipients table:
id int pk
message_id int
read_date datetime
user_id int
...
Index is on id, message_id and user_id.
Table structure of users table:
id int pk
display_name varchar
...
Index is on id.
I am running the following query against these tables:
SELECT
m.*,
if(m.user_id = 0, 'Campus Manager', u.display_name) AS name,
mr.read_date,
IF(m1.message_id > 0 and m1.user_id=1, true, false) as replied
FROM
messages m
JOIN
message_recipients mr
ON
mr.message_id = m.id
LEFT JOIN
users u
ON
u.UID = m.user_id
LEFT JOIN
messages m1
ON
m1.message_id = m.id
WHERE
mr.user_id = 1
AND
m.published = 1
GROUP BY
mr.message_id
ORDER BY
m.created DESC
EXPLAIN returns the following data for this query:
UPDATE
As suggested by #e4c5, I added new composite index on (published,user_id,created) and now the explain query shows this:
How can this query be optimized by adding required indexes (if any) as it is taking lot of time?

GROUP BY needs to list all the non-aggregated columns. I suspect that would be a mess. Why do you need GROUP BY at all?
Why are you linking messages.id to messages_id? Is this a hierarchical table, but the column names aren't like 'parent_id'?
"Index is on id, message_id and user_id" -- is that one composite index or 3 single-column indexes? (It makes a big difference.) It would be better to show us SHOW CREATE TABLE instead of ambiguously paraphrasing.
Is user_id=1 prolific? That is, are you expecting thousands of rows? Is this query only a problem for him?
Using LEFT JOIN implies that m1.message_id could be NULL, yet the reference to it seems to ignore that possibility.
If this is a single table that contains a message thread -- both the main info about the thread and the individual responses, then I suggest it is a bad design. (I made this mistake once upon a time.) I think it iis better to have a table with one row per thread and another table with one row per comment. 1 thread : many comments. So there would be a thread_id in the comment table.

I was able to bring down the query time from 3 seconds to 0.1 second by adding a new index to messages and message_recipients table and changing the database engine of messages table to MyISAM from InnoDB.
Composite index composite added on these columns with respective order on messages table - published, user_id, created
Composite index message_id_2 added on two columns on message_recipients table - message_id, user_id
EXPLAIN Query now shows

Related

MySql - Slow Query when adding Order by

I've an application which reads email messages (I download the emails from gmail inbox in my application's database) from MySql Database. and the Database has following structure
Table1 (Contacts):
ContactID (int)
ContactName (varchar(100)
ContactEmailAddress (varchar(150))
Table2 (Subjects):
SubjectID (int)
ContactID (int)
Subject (varchar(200))
Table3 (Messages):
MessageID (int)
SubjectID (int)
MessageText (varchar(150)
IsRead (tinyint)
IsReceived (tinyint)
MessageDate (DateTime)
And here is my query to fetch most recent 40 records
SELECT * FROM(SELECT ROW_NUMBER()OVER(Order by isRead ASC,MessageDate DESC) RecID,
c.ContactName,s.subject,s.SubjectID,d.MessageDate,d.isRead
from Contacts c
INNER JOIN Subjects s on s.ContactID=c.ContactID
JOIN (
select MAX(MessageID) dtl_id,SubjectID from Messages where IsReceived=1
GROUP BY SubjectID)d_max on (d_max.subjectid=s.subjectid)
JOIN Messages d on (d.MessageID=d_max.dtl_id)
) AS RowConstrainedResult where RecID >=1 and RecID <=40 ORDER BY RecID
but this query takes almost 15 seconds to load. What should be done to improve the query performance. as my all primary key columns and referenced key columns are indexed. And the Messages table has almost 500k records in it.
The EXPLAIN in the link you provided does not seem to refer to the SELECT you provided, so I have to ignore it.
This index may be helpful:
Messages: (IsReceived, SubjectID, MessageID)
I added the tag [groupwise-maximum]. It links to a lot of other Questions that are doing similar things. Very few actually succeed in optimizing the task. I have a comparison of the faster-than-most techniques here: http://mysql.rjweb.org/doc.php/groupwise_max

how to Join only part of my first table to another table?

i have three tables:
account (which contain accounts Registered info and that Primary key is ads_id)
ads_info (which contain my advertising info and that Primary Key is ads_id)
favorite_ads (which that columns is fav_id, acc_id And ads_id) that specifies witch User Favorite which advertising.
Now i want to separate records which have acc_id = 1 from favorite_ads table and then outer-join this records with all of my ads_info table records.
can tell my any sql query do some thins like it for me?
You may try below query -
SELECT *
FROM ads_info AI
LEFT JOIN (SELECT fav_id, acc_id, ads_id
FROM favorite_ads
WHERE acc_id = 1) FA ON AI.ads_id = FA.ads_id

How to get count by combining more than 2 tables in mysql

i have a table 'A' with status column, it can have 4 values. In table A i have table 'B's id, table B have table 'C's id. I want to get the status count FROM table 'A' by joining all these columns. The status column in table A is a foreign key from table 'D'. Table 'D' having status like 1-agreed, 2-not agreed etc
The question is missing some information that might be helpful. Particularly, what exactly you are wanting to count. (i.e. are you just trying to count ALL rows, or are you trying to count the number of rows in table A that have each status). I'll put together an answer that assumes that latter.
I'll also just assume that "id" is the primary key of its own table, and that id will be the id from other tables inside a table.
select A.statusField, count(*)
from A
join B on (A.Bid = B.id)
join C on (B.Cid = C.id)
group by A.statusField
Hope that helps.

MySql: query optimization or table's structure optimization

I need some help with mysql.
Let's show you. I have two tables: nodes(250 000 rows):
id | name
And table groups(~400 000 rows):
id | group_id | node_id - index(node_id) and maybe index(group_id)
Problem query:
select count(*) from nodes n inner join groups g on g.node_id = n.id \
where g.group_id in (1,20, 30...);
Execution time: 0.50 sec and it's problem for me.
How I can optimize query to count rows and then make select?
Where I can put index or new field for benefit?
`SHOW CREATE TABLE` for each table
Don't bother with an id for a many-to-many table. Do be sure to have indexes going both ways in such a table.
Why does your Problem Query need to touch nodes at all? This will count the number of nodes in each group, won't it?
SELECT group_id,
COUNT(*)
FROM groups
WHERE group_id IN (...)
GROUP BY group_id;
groups: PRIMARY KEY(group_id, node_id), INDEX(node_id, group_id)
If that is not what you are looking for, please describe the query.

Database design for a Fantasy league

Here's the basic schema for my database
Table user{
userid numeber primary key,
count number
}
Table player{
pid number primary key,
}
Table user-player{
userid number primary key foreign key(user),
pid number primary key foreign key(player)
}
Table temp{
pid number primary key,
points number
}
Here's what I intend to do...
After every match the temp table is updated which holds the id of players that played the last match and the points they earned.
Next run a procedure that will match the pid from temp table with every uid of user-player table having the same pid.
add the points from temp table to the count of user table for every matching uid.
empty temp table.
My questions is considering 200 players and 10000 users,Will this method be efficient?
I am going to be using mysql for this.
People often seem to be worried about performance for small databases. Let the DBMS do what it is designed to do. If you find in practice - or preferably under load testing - that you have a performance problem, then take steps to deal with it. Otherwise don't pre-optimize!
Instead of using a temporary table to store one batch of player scores, store all player scores in a tranactional table.
Remove the user.count column and replace your temp table with something like this:
Table player_points{
pid number primary key,
match_date datetime primary key,
points number
}
With this you can easily calculate any user's score. You can even recalculate any user's score as of a given date. This is much more powerful and much simpler to maintain. Keeping a current snapshot only makes it impossible to manage should anything go wrong or should one of your users challenge their score.
This query gives you the scores for all users. You can add filters to it to do other things like finding the score for a single user or showing a leader board.
select
U.userid as UserID
, sum(S.points) as TotalScore
from user S
inner join user-player J
on S.userid = J.userid
inner join player_points S
on J.pid = S.pid
group by
U.userid
This query would give you a leader board:
select
U.userid as UserID
, sum(S.points) as TotalScore
from user S
inner join user-player J
on S.userid = J.userid
inner join player_points S
on J.pid = S.pid
group by
U.userid
order by TotalScore desc
limit 10
This query would give you points awarded to a user by date, which you could graph as-is or cumulatively, for example.
select
S.match_date as MatchDate
, sum(S.points) as TotalScore
from user-player J
inner join player_points S
on J.pid = S.pid
where J.userid = 123 -- The user ID you want.
group by
S.match_date
order by S.match_date