MySQL Query - alternative - php

My query is too long (3-4s). Any idea's how make this faster?
SELECT u.id AS id_uzytkownika,
u.login,
u.ranga,
u.online_light AS online,
(SELECT MAX(id)
FROM uzytkownicy_zdjecia
WHERE id_uzytkownika = u.id
AND prywatna =0) AS id_fotki,
(SELECT fotka
FROM uzytkownicy_zdjecia
WHERE id = id_fotki) AS fotka ,
(SELECT srednia_ocen
FROM uzytkownicy_zdjecia
WHERE id = id_fotki) AS srednia_ocen,
(SELECT ile_ocen
FROM uzytkownicy_zdjecia
WHERE id = id_fotki) AS ile_ocen
FROM uzytkownicy u
WHERE u.foto =1
AND u.plec = "mezczyzna"
ORDER BY srednia_ocen DESC,
ile_ocen DESC,
id_fotki DESC LIMIT 42

Could you maybe explain your table structure and what this query is about? Since the column names are not in English, most readers will probably have a problem understanding what you are trying to do here...
In general, it looks like you have a LOT of nested SELECTS here to the same table - is there any special reason for that?

Try this:
SELECT u.id AS id_uzytkownika,
u.login,
u.ranga,
u.online_light AS online,
(SELECT MAX(id)
FROM uzytkownicy_zdjecia
WHERE id_uzytkownika = u.id
AND prywatna =0) AS id_fotki,
z.fotka,
z.srednia_ocen,
z.ile_ocen
FROM uzytkownicy u
JOIN uzytkownicy_zdjecia z
ON u.id_fotki = z.id
WHERE u.foto =1
AND u.plec = "mezczyzna"
ORDER BY srednia_ocen DESC,
ile_ocen DESC,
id_fotki DESC LIMIT 42
I've substituted 3 subqueries with a JOIN. The first subquery has a different condition, if you can merge that condition withn others queries you can remove that.
Pay attention You have named id_fokta the result of subquery, but the same name has the primary key of uzytkownicy table

You don't have to query the same table with the same criteria again and again. Replace your sub-selects with a simple join. A LEFT JOIN, if it is possible that no matching record exists.
SELECT
u.id AS id_uzytkownika,
u.login,
u.ranga,
u.online_light AS online,
(
SELECT MAX(id)
FROM uzytkownicy_zdjecia
WHERE id_uzytkownika = u.id
AND prywatna =0
) AS id_fotki,
uz.fotka,
uz.srednia_ocen,
uz.ile_ocen
FROM uzytkownicy u
LEFT JOIN uzytkownicy_zdjecia uz ON uz.id = u.id_fotki
WHERE u.foto = 1
AND u.plec = "mezczyzna"
ORDER BY u.srednia_ocen DESC,
u.ile_ocen DESC,
u.id_fotki DESC LIMIT 42
An alternative way to write the query above is to aggregate first and then join:
SELECT
u.id AS id_uzytkownika,
u.login,
u.ranga,
u.online_light AS online,
uzz.id_fotki,
uz.fotka,
uz.srednia_ocen,
uz.ile_ocen
FROM uzytkownicy u
LEFT JOIN uzytkownicy_zdjecia uz ON uz.id = u.id_fotki
LEFT JOIN
(
SELECT id_uzytkownika, MAX(id) AS id_fotki
FROM uzytkownicy_zdjecia
WHERE prywatna =0
GROUP BY id_uzytkownika
) uzz ON uzz.id_uzytkownika = u.id
WHERE u.foto = 1
AND u.plec = "mezczyzna"
ORDER BY u.srednia_ocen DESC,
u.ile_ocen DESC,
u.id_fotki DESC LIMIT 42
By the way: What is "mezczyzna"? A string? Then this should be single quotes.

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 get the most recent row in group in mysql?

In my mysql query, I try to get all threads with their most recent row.
$query = "SELECT th.id, tm.message, tm.date_sent, tm.date_sent>tu.last_read_date AS new
FROM thread th
JOIN thread_user tu ON th.id=tu.thread_id AND tu.user_id={$user_id}
JOIN thread_message tm ON th.id=tm.thread_id
JOIN (
SELECT thread_id, MAX(date_sent) date_sent
FROM thread_message
GROUP BY thread_id
) q ON tm.thread_id = q.thread_id AND tm.date_sent = q.date_sent
ORDER BY tm.date_sent DESC";
This works, but the problem is, if there is two or more rows who's date is the most recent and they are the same date, then it will join with both of them. I need that third join statement to join with at most 1 row.
I also don't want to assume that the biggest id implies its the most recent row, because I could always change the dates manually later.
Does anyone know how to fix this?
Thanks
One way to do this is to establish a row number per group, in this case your group is thread_id and date_sent. With MySql, you need to use user-defined variables to do this:
SELECT th.id,
tm.message,
tm.date_sent,
tm.date_sent>tu.last_read_date AS new
FROM thread th
JOIN thread_user tu ON th.id=tu.thread_id AND tu.user_id={$user_id}
JOIN (
SELECT id,
thread_id,
message,
date_sent,
#rn:=IF(#prevthread_id=thread_id, #rn+1, 1) rn,
#prevthread_id:=thread_id
FROM thread_message, (SELECT #rn:=1, #prevthread_id:=0) t
ORDER BY thread_id, date_sent DESC, id
) tm ON th.id=tm.thread_id
AND tm.rn = 1
ORDER BY tm.date_sent DESC
Perhaps this is easier for you (but only because you're using mysql):
SELECT th.id,
tm.message,
tm.date_sent,
tm.date_sent>tu.last_read_date AS new
FROM thread th
JOIN thread_user tu ON th.id=tu.thread_id AND tu.user_id={$user_id}
JOIN thread_message tm ON th.id=tm.thread_id
JOIN (
SELECT thread_id,
id,
MAX(date_sent) date_sent
FROM thread_message
GROUP BY thread_id
) q ON tm.thread_id = q.thread_id
AND q.id = tm.id
AND tm.date_sent = q.date_sent
ORDER BY tm.date_sent DESC
This will return an arbitrary id to join on.
Seems to me that if the query produces what you expect, with exception of the last JOIN you can just modify the GROUP BY which will only return one row.
JOIN (
SELECT thread_id, MAX(date_sent) date_sent
FROM thread_message
GROUP BY thread_id
) q ON tm.thread_id = q.thread_id AND tm.date_sent = q.date_sent
GROUP BY tm.date_sent
ORDER BY tm.date_sent DESC";

SQL query GROUP BY id filter?

THIS IS THE OUTPUT OF THE QUERY:
SELECT * FROM
((SELECT
privatemsgs.id,
privatemsgs.useradn,
privatemsgs.useraid,
privatemsgs.title,
privatemsgs.created,
privatemsgs.timee,
privatemsgs.isread,
u.photo AS creatorphoto,
privatemsgs.relatedto
FROM privatemsgs
LEFT JOIN
users AS u ON(privatemsgs.useraid = u.id)
WHERE userbid='$myid'
AND relatedto=0 and bdel=1)
UNION
(SELECT
privatemsgs.id,
privatemsgs.useradn,
privatemsgs.useraid,
privatemsgs.title,
privatemsgs.created,
privatemsgs.timee,
privatemsgs.isread,
u.photo AS creatorphoto,
rel.relatedto
FROM privatemsgs AS rel
JOIN privatemsgs ON(rel.relatedto = privatemsgs.id)
LEFT JOIN
users AS u ON(rel.useraid = u.id)
WHERE rel.userbid='$myid'
)) privatemsgs
GROUP BY id
ORDER BY timee DESC
I got double id "2". first 1 with "isread = 0", second with "isread = 1".
When I added "group by id", I've got (line 2)
But i need the output to show that isread = 1 (like line 3)
How do i fix it?
add MAX(isread) near the *. GROUP BY works only on Aggregate functions

MySQL Query Multiple LEFT Joins problem

I am trying to get the required result from the following query but it doesnt seem to work...
SELECT DISTINCT
u.user_name as user_name,
u.total_points as total_points,
u.user_id as user_id,
COUNT(a.id) as user_total_articles_published,
COUNT(r.id) as user_total_replies_published,
COUNT(v.id) as user_total_votes_done
FROM users as u
LEFT JOIN articles as a ON u.user_id=a.user_id
LEFT JOIN replies as r ON u.user_id=r.user_id
LEFT JOIN votes as v ON u.user_id=v.user_id
GROUP BY u.user_id
ORDER BY u.total_points DESC
LIMIT 10
If i remove the last 2 LEFT jOINS the query will work... whats wrong with the other 2? Do i have to use another method for this to work?
thanks
I think by 'not working' you mean that the query returns too many records? That is because of the combination of joins. You return each reply for each article record, so the numbers are multiplied. You can solve this by using DISTINCT in the COUNT. That way, you count the unique id's, so you count each article only once:
COUNT(distinct a.id) as user_total_articles_published,
COUNT(distinct r.id) as user_total_replies_published,
COUNT(distinct v.id) as user_total_votes_done
[edit]
A possibly faster solution, eliminating the need for DISTINCT and GROUP BY:
SELECT
u.user_name as user_name,
u.total_points as total_points,
u.user_id as user_id,
(SELECT COUNT(a.id) FROM articles a
WHERE a.user_id = u.user_id) as user_total_articles_published,
(SELECT COUNT(r.id) FROM replies r
WHERE r.user_id = u.user_id) as user_total_replies_published,
(SELECT COUNT(v.id) FROM votes v
WHERE v.user_id = u.user_id) as user_total_votes_done
FROM users as u
ORDER BY u.total_points DESC
LIMIT 10

Ordering by posts in another table

I have created a feature where users can start new topics (similar to forums).
At the moment on a page, the query for the topics are as follows:
$q = "SELECT ".TBL_COMMUNITYTHREADS.".title, ".TBL_COMMUNITYTHREADS.".id,
".TBL_COMMUNITYTHREADS.".date, ".TBL_COMMUNITYTHREADS.".author, ".TBL_USERS.".username FROM ".TBL_COMMUNITYTHREADS."
INNER JOIN ".TBL_USERS." ON ".TBL_COMMUNITYTHREADS.".author = ".TBL_USERS.".id
WHERE type = '$type'
ORDER BY date DESC LIMIT $offset, $rowsperpage ";
The tables are constants and the offset and rowsperpage are variables passed in to limit how many posts are on a page.
At the moment though, all the topics are ordered by the date.
I want them to be ordered by the latest response.
Similarly to forums, when the reponse inside the topic is newest, that topic will go to the top.
The topics are stored in tbl_communitythreads and the replies in tbl_communityreplies.
How can I ordered them by the latest repsonse.
They are linked by the threadid in tbl_communityreplies. Also in that one is the date column.
Thankyou for reading, I just cant think of how to do this.
This:
SELECT c.title, c.id, c.date, c.author, u.username,
(
SELECT MAX(reply_date)
FROM tbl_communityreplies cr
WHERE cr.thread = c.id
) AS last_reply
FROM TBL_COMMUNITYTHREADS c
JOIN TBL_USERS u
ON u.id = c.author
ORDER BY
last_reply DESC, c.id DESC
LIMIT $offset, $rowsperpage
or this:
SELECT c.title, c.id, c.date, c.author, u.username
FROM (
SELECT cr.thread, cr.reply_date, cr.id
FROM tbl_communityreplies cr
WHERE (cr.last_reply, cr.id) =
(
SELECT last_reply, id
FROM tbl_communityreplies cri
WHERE cri.thread = cr.thread
ORDER BY
thread DESC, last_reply DESC, id DESC
)
ORDER BY
last_reply DESC, id DESC
LIMIT $offset, $rowsperpage
) q
JOIN TBL_COMMUNITYTHREADS c
ON c.id = q.thread
JOIN TBL_USERS u
ON u.id = c.author
ORDER BY
q.reply_date DESC, q.id DESC
The former query is more efficient for large values of $offset, or if you have few threads with lots of replies.
Issue the following commands:
CREATE INDEX ix_communitythreads_replydate_id ON TBL_COMMUNITYTHREADS (reply_date, id)
CREATE INDEX ix_communitythreads_thread_replydate_id ON TBL_COMMUNITYTHREADS (thread, reply_date, id)
for this to work fast.
by join with another table , then you can order by column from this table.

Categories