i'm looking for a solution for my query, for improving the performances.
I've just read several threads about that but (maybe to cause me) they aren't fitting for my case.
Anyway, here is my issue.
I've a msg table something like:
IDMSG | THREAD | SENDER | RECEIVER | DATE | MSG | READ
1521 | 2 | 20 | 43 | 05/24/2014 | hello guys | 0
1522 | 3 | 84 | 43 | 05/24/2014 | hi man | 0
1523 | 2 | 20 | 43 | 05/24/2014 | yes, of coure | 0
Now, you'll see that the user 20 has written 2 msg to me (43), so, on my msg index page, i'd like to print the latest msg of the thread, and sort it in a just way.
For instance:
05/24/2014 - (Preview text:) Yes of course - by Erick (thread 2)
05/24/2014 - (Preview text:) Hi man. - by Manuel (thread 3)
05/21/2014 - (Preview text:) I'm female - by Sandra.
etc etc.
Currently, my real query is:
SELECT * FROM
(SELECT * FROM boxmsg
LEFT JOIN user ON sender = id WHERE receiver = '959749'
ORDER BY idmsg DESC)
AS temp_tbl
GROUP BY thread ORDER BY idmsg DESC LIMIT 0,20
So, it works right, but the performance is a real disaster.
Indeed, it scans whole database because the indexes in a derivate table have several problem.
How can i get the same result, having latest msg of a thread?
Thank you at all and sorry for my awful english.
This is your query:
SELECT *
FROM (SELECT *
FROM boxmsg LEFT JOIN
user
ON sender = id
WHERE receiver = '959749'
ORDER BY idmsg DESC
) temp_tbl
GROUP BY thread
ORDER BY idmsg DESC
LIMIT 0, 20;
MySQL specifically warns against this use of * with group by (see here).
For what you want to do, try the following query:
SELECT *
FROM boxmsg bm LEFT JOIN
user
ON sender = id
WHERE receiver = '959749' AND
NOT EXISTS (SELECT 1
FROM boxmsg bm2
WHERE bm2.receiver = bm.receiver and
bm2.thread = bm.thread and
bm2.idmsg > bm.idmsg
)
ORDER BY idmsg DESC
LIMIT 0, 20;
Before running the query, create an index on boxmsg(receiver, thread, idmsg) and an index on user(id) (if one doesn't already exist).
Something like:
SELECT b.*
FROM boxmsg b
JOIN (
SELECT thread, MAX(idmsg) as idmsg
FROM boxmsg x
GROUP BY thread
) a
ON a.thread = b.thread
AND a.idmsg = b.idmsg
An index on thread, idmsg will likely help improve performance
Related
my client was given the following code and he uses it daily to count the messages sent to businesses on his website. I have looked at the MYSQL.SLOW.LOG and it has the following stats for this query, which indicates to me it needs optimising.
Count: 183 Time=44.12s (8073s) Lock=0.00s (0s)
Rows_sent=17337923391683297280.0 (-1), Rows_examined=382885.7
(70068089), Rows_affected=0.0 (0), thewedd1[thewedd1]#localhost
The query is:
SELECT
businesses.name AS BusinessName,
messages.created AS DateSent,
messages.guest_sender AS EnquirersEmail,
strip_tags(messages.message) AS Message,
users.name AS BusinessName
FROM
messages
JOIN users ON messages.from_to = users.id
JOIN businesses ON users.business_id = businesses.id
My SQL is not very good but would a LEFT JOIN rather than a JOIN help to reduce the number or rows returned? Ive have run an EXPLAIN query and it seems to make no difference between the LEFT JOIN and the JOIN..
Basically I think it would be good to reduce the number of rows returned, as it is absurdly big..
Short answer: There is nothing "wrong" with your query, other than the duplicate BusinessName alias.
Long answer: You can add indexes to the foreign / primary keys to speed up searching which will do more than changing the query.
If you're using SSMS (SQL management studio) you can right click on indexes for a table and use the wizard.
Just don't be tempted to index all the columns as that may slow down any inserts you do in future, stick to the ids and _ids unless you know what you're doing.
he uses it daily to count the messages sent to businesses
If this is done per day, why not limit this to messages sent in specific recent days?
As an example: To count messages sent per business per day, for just a few recent days (example: 3 or 4 days), try this:
SELECT businesses.name AS BusinessName
, messages.created AS DateSent
, COUNT(*) AS n
FROM messages
JOIN users ON messages.from_to = users.id
JOIN businesses ON users.business_id = businesses.id
WHERE messages.created BETWEEN current_date - INTERVAL '3' DAY AND current_date
GROUP BY businesses.id
, DateSent
ORDER BY DateSent DESC
, n DESC
, businesses.id
;
Note: businesses.name is functionally dependent on businesses.id (in the GROUP BY terms), which is the primary key of businesses.
Example result:
+--------------+------------+---+
| BusinessName | DateSent | n |
+--------------+------------+---+
| business1 | 2021-09-05 | 3 |
| business2 | 2021-09-05 | 1 |
| business2 | 2021-09-04 | 1 |
| business2 | 2021-09-03 | 1 |
| business3 | 2021-09-02 | 5 |
| business1 | 2021-09-02 | 1 |
| business2 | 2021-09-02 | 1 |
+--------------+------------+---+
7 rows in set
This assumes your basic join logic is correct, which might not be true.
Other data could be returned as aggregated results, if necessary, and the fact that this is now limited to just recent data, the amount of rows examined should be much more reasonable.
So I want to select * from "board_b" the thread that has the most replies. My problem is that the replies are actually in the same table. Take a look at this:
+---+-----------+---------+
|ID | name | replyto |
+---+-----------+---------+
| 1 | newthread | |
| 2 | reply | 1 |
+---+-----------+---------+
(NOTE: the name column is not set to those, it is just to demonstrate) As you can see, 1 is a new thread, and 2 is a reply to 1. Now I have a table full of these, and the table has more columns (text, timestamp, etc...) but the general idea is like the one above.
The thing I want to achieve is select all threads, and sort them by most replies (and also limit by 0, 20). I've tried looking in to joining tables but it get's too complicated for me to understand, so a sample code would be great.
Something like this will do it:
SELECT board.id, board.name, COUNT(reply.id)
FROM board_b board INNER JOIN board_b reply ON board.id = reply.replyto
GROUP BY board.id, board.name
ORDER BY COUNT(reply.id) desc
LIMIT 20
You want to use group by:
select replyto as thread, count(*) as cnt
from board_b
group by replyto
order by cnt desc
limit 0, 20;
select c.replyto, c.replycount
from
(
select a.replyto as replyto, count(*) as replycount
from board_b a
inner join (
select id, name, replyto
from board_b
where replyto is null
) b
on b.id = a.replyto
group by a.replyto
) c
where c.replycount between 0 and 20
order by c.replycount desc
I am designing a Test-System for a school.The table is like this
------------------------------------------------------------------------------
Student_id | Total Questions | Questions Attempted | Correct
------------------------------------------------------------------------------
36 | 60 | 42 | 20
19 | 60 | 38 | 32
119 | 60 | 37 | 31
Now, marking scheme is +3 for correct and -1 for wrong
Number of wrong questions will be calculated as wrong = attempted - correct
Questions
1) I want to give the give the student some points based on their ranks, so I want to sort the table on the decreasing order of their score i.e. score = 3 * correct - wrong.Though,I could have stored the score as well but since it is redundant data I don't want to store it into the table.How can I sort the table using SQL query.
2)When I will be updating the points of students based on their performance into the table student,I am picking student_id from result table and making updations into the student table i.e. 1 query per student.This means that if 4000 students sat for the test ,4000 queries !!! .Can I improve the situation (minimise queries)?
EDIT
Student schema for question 2
------------------------------------------------------------------------------
Student_id | fname | lname | contact | points
------------------------------------------------------------------------------
you can just specify what you want in the sort
select student_id,correct, attempted,field4,field5, (3 * correct - (attempted-correct)) as score
from students sort by score desc
yes, take a look at bulk update of sql, you can prepare the query and update 10 by 10 or 100 by 100, but not too much since sql command have limit on its length
Question 1.
Supposing the table is named Results, and that Student_id is unique, here is a possible solution to your question:
SELECT Results.*, (3*Correct-(Total_Questions-Correct)) AS score
FROM Results
ORDER BY score DESC
Question 2.
Supposing the Students are already added to the table Students, or that they already have a score, this is a possible SQL Query to update the students table without making the 4k queries:
UPDATE StudentsTable AS s
INNER JOIN PointsTable AS p
ON s.Student_id = p.Student_id
SET
s.Points = s.Points + (3 * p.Correct - (p.Questions_Attempted - p.Correct))
If you need to perform more tests in the future you can add a Test_ID column to you Points Table and then add a WHERE clause to the UPDATE query in order to just add up the score from a given test.
Optimization
You can optimize the queries a little bit by changing the way you calculate the score:
SELECT Results.*, (2*Correct-Total_Questions) AS score
FROM Results
ORDER BY score DESC
UPDATE StudentsTable AS s
INNER JOIN PointsTable AS p
ON s.Student_id = p.Student_id
SET
s.Points = s.Points + (2 * p.Correct - p.Questions_Attempted)
To rank students by score you can do
SELECT student_id,
(
SELECT 1 + COUNT(*)
FROM student_results
WHERE 3 * correct - (total - correct) >=
3 * r.correct - (r.total - r.correct)
AND student_id <> r.student_id
) rank
FROM student_results r
Output:
| STUDENT_ID | RANK |
|------------|------|
| 36 | 3 |
| 19 | 1 |
| 119 | 2 |
Now you can update student points in one go using multi-table UPDATE syntax instead of hitting the database with number of update queries.
UPDATE students s JOIN
(
SELECT student_id,
(
SELECT 1 + COUNT(*)
FROM student_results
WHERE 3 * correct - (total - correct) >=
3 * r.correct - (r.total - r.correct)
AND student_id <> r.student_id
) rank
FROM student_results r
) q
ON s.student_id = q.student_id
SET s.points = s.points +
CASE q.rank -- implement your logic of translating ranks to points here
WHEN 1 THEN 100
WHEN 2 THEN 50
WHEN 3 THEN 10
ELSE 0
END;
Here is SQLFiddle demo
I have three tables RESOURCES, SERVERS, ACTIVE_RESOURCES. The HOSTED_RESOURCES table servers as a referential table to list which servers the resources are active on. Currently I use the bellow query to retrieve a resource:
SELECT r.resource_id, r.serve_url, r.title, r.category_id, ar.server_id
FROM active_resources ar
LEFT JOIN resources AS r ON (ar.resource_id = r.id)
WHERE hr.resource_id = (
select id from resources
and id < 311
order by date_added desc
limit 1
);
Because in most cases the resources are available on all servers I end up with duplicate information in the query result, for example:
resource_id | serve_url | Title | category_id | server_id
-----------------------------------------------------------------------------
309 | /b/7514.pdf | Tuesdays with Morrie | 1 | 1
309 | /b/7514.pdf | Tuesdays with Morrie | 1 | 2
All of the data, except for server_id is a duplicate, so I was hoping to concatenate the result to one row displaying the server ids in additional columns, or even just list the server ids as comma separated in one column.
Thank you for looking at this.
SELECT r.resource_id, r.serve_url, r.title, r.category_id,
GROUP_CONCAT(ar.server_id) AS server_id
FROM active_resources ar
LEFT JOIN resources AS r ON (ar.resource_id = r.id)
WHERE hr.resource_id = (
select id from resources
and id < 311
order by date_added desc
limit 1
)
GROUP BY r.resource_id, r.serve_url, r.title, r.category_id
;
I read many topics about this problem but I can't find the solution.
I have a table (called users) with the users of my website. They have points. For example:
+-----------+------------+
| User_id | Points |
+-----------+------------+
| 1 | 12258 |
| 2 | 112 |
| 3 | 9678 |
| 4 | 689206 |
| 5 | 1868 |
+-----------+------------+
On the top of the page the variable $user_id is set. For example the user_id is 4. Now I would like to get the rank of the user by points (output should be 1 if the user_id is 4).
Thank you very much!
SELECT
COUNT(*) AS rank
FROM users
WHERE Points>=(SELECT Points FROM users WHERE User_id=4)
Updated with some more useful stuff:
SELECT
user_id,
points,
(SELECT COUNT(*)+1 FROM users WHERE Points>x.points) AS rank_upper,
(SELECT COUNT(*) FROM users WHERE Points>=x.points) AS rank_lower
FROM
`users` x
WHERE x.user_id = 4
which includes the range of ranks user is at. So for example if the scores for first five places are 5 4 3 3 3, the result would be:
id points rank_upper rank_lower
id 5 1 1
id 4 2 2
id 3 3 5
id 3 3 5
id 3 3 5
This query should do what you want :
SELECT rank FROM (
SELECT User_id, Points,
FIND_IN_SET(
Points,
(SELECT GROUP_CONCAT(
DISTINCT Points
ORDER BY Points DESC
)
FROM users)
) as rank
FROM users )
WHERE User_id = 4;
If you don't want to do it outside mysql you'll need to use variables to compute the rank.
Here's a solution that describes exactly what you want :
http://www.fromdual.ch/ranking-mysql-results
You still need, it you want to have it directly for each record, to store it in the record and to update it yourself. There is no reasonable query that will give you directly the rank without storage on a real table (I mean not just a few hundreds records).
There's already a simple solution, just suited for your purpose.
This may help
SELECT #rank:=#rank+1 AS rank,`User_id`,`Points` FROM `users` u JOIN (SELECT #rank:=0) r ORDER BY u.Points DESC