Question

I need help optimizing this query below. I have table pt_votes with 30+k records which holds each vote(-1 or 1) for photo and I want to select all photos and their vote sum so I have query like this below but it takes about 9 seconds to execute. How I can optimize it?

 SELECT *, ifnull((SELECT SUM(vote) FROM pt_votes vo WHERE vo.pID = ph.pID),0) points,
                        (SELECT CONCAT(name, " ", surname) FROM pt_users us WHERE us.uID = ph.uID) name_surname
                    FROM pt_photos ph
                    WHERE 1
Was it helpful?

Solution

The biggest efficiency killer here is the correlated subqueries:

(SELECT CONCAT(name, " ", surname)
  FROM pt_users us
  WHERE us.uID = ph.uID) name_surname

... and:

ifnull((SELECT SUM(vote)
  FROM pt_votes vo
  WHERE vo.pID = ph.pID),0) points,

Each of these will run once for every row that makes it past the WHERE clause.

To eliminate the correlated subqueries you need to join to the pt_votes and pt_users tables. Also, because you're summing votes you'll need to GROUP BY, which means you really need to get rid of that SELECT * as was already recommended in the comments.

The query will look something like this. When you determine which pt_photos columns you need be sure to add them to the GROUP BY list:

SELECT
  pt_photos.pID,
  pt_photos.uID,
  pt_photos.this,
  pt_photos.that,
  CONCAT(pt_users.name, ' ', pt_users.surname) AS name_surname,
  IFNULL(SUM(pt_votes.vote), 0) AS points
FROM pt_photos
JOIN pt_users ON pt_photos.uID = pt_users.uID
LEFT JOIN pt_votes ON pt_photos.pID = pt_votes.pID
WHERE 1
GROUP BY
  pt_photos.pID,
  pt_photos.uID,
  pt_photos.this,
  pt_photos.that

And if your query really has a WHERE 1 clause you can drop it.

OTHER TIPS

It's not tested in any way, but try and see if this helps

SELECT *, SUM(ifnull(vo.vote,0)) points, CONCAT(us.name, " ", us.surname) name_surname
FROM pt_photos ph
LEFT JOIN pt_votes vo ON vo.pID = ph.pID
JOIN pt_users us ON us.uID = ph.uID

Use JOINs, not correlated subqueries.

SELECT ph.*,
       IFNULL(SUM(vo.vote), 0) points,
       CONCAT(us.name, " ", us.surname) name_surname
FROM photos ph
LEFT JOIN pt_votes vo ON ph.pId = vo.pID
INNER JOIN pt_users us ON us.uID = ph.uID
GROUP BY ph.uID

As far as I know, there's no need for a WHERE 1 clause to select all rows. Just omit the WHERE clause.

Correlated subqueries make for light dimming queries on large sets, due to the number of times that subquery is executed.

Here's an alternative that should perform better:

SELECT ph.*
     , IFNULL(vs.points,0) AS points
     , CONCAT(us.name," ",us.surname) AS name_surname
  FROM pt_photos ph
  LEFT
  JOIN ( SELECT vo.pID
              , SUM(vo.vote) AS points
           FROM pt_votes vo
          GROUP BY vo.pID
       ) vs
    ON vs.pID = ph.pID
  LEFT
  JOIN pt_users us 
    ON us.uID = ph.uID
 WHERE 1

A suitable index (ideally a covering index) on pt_votes will improve performance of the GROUP BY... on pt_votes (pID, vote).

I'm going to assume that uID is the pirmary key of pt_users.

I would try rewriting the third select as an inner join, and possibly move the concat function to when you're outputting the results. This is a t-sql solution, but it should be similar enough:

select ph.*, 
concat(pu.name, " ", pu.surname) as name_surname,
isnull((select sum(vote) from pt_votes vo where vo.pid = ph.pid),0) as points
from pt_photos ph
inner join pt_users pu on ph.uid = pu.uid

You could also do a left join to the pt_votes table for the sum, but then you'd have to play with 'group by' clauses to get it all working, which might be worth it for the processing time.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top