query with dependent subqueries too slow - php

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.

Related

LIMIT LEFT join to last updated row from multiple rows

This is my code i am trying to left join the latest team data, not every piece of data. i have tried just using limit 1 but doesnt return anything
ORDER BY updated DESC LIMIT 1
this doesnt work
Any ideas?
$sql = "SELECT
events.id, events.time,events.status, events.home_team,events.away_team,events.league,
ht.id as home_id,ht.name as home_name,at.name as away_name,
statistics.home_goals,statistics.away_goals,statistics.time as game_time,
leagues.id as league_id,leagues.name as league_name,leagues.type as league_type,
country.name as country_name,country.logo,
hts.home_scored, ats.away_scored,
hts.home_conceeded,ats.away_conceeded,
hts.home_win,ats.away_win,
hts.home_15,ats.away_15,
hts.home_25,ats.away_25,
hts.home_btts, ats.away_btts,
hts.home_fts, ats.away_fts,
hts.home_cs, ats.away_cs,
hts.home_corners_for, ats.away_corners_for,
hts.home_corners_against, ats.away_corners_against,
hts.home_cards, ats.away_cards
FROM events
LEFT JOIN teams ht
ON ht.id = events.home_team
LEFT JOIN teams at
ON at.id = events.away_team
LEFT JOIN leagues
ON leagues.id = events.league
LEFT JOIN country
ON country.id=leagues.country
LEFT JOIN ( SELECT team,home_scored,home_conceeded,home_win,home_15,home_25,home_btts,home_fts,home_cs,home_corners_for,home_corners_against,home_cards FROM team_quick_stats ORDER BY updated DESC) hts
ON ht.id=hts.team
LEFT JOIN ( SELECT team,away_scored,away_conceeded,away_win,away_15,away_25,away_btts,away_fts,away_cs,away_corners_for,away_corners_against,away_cards FROM team_quick_stats ORDER BY updated DESC) ats
ON at.id=ats.team
LEFT JOIN statistics
ON statistics.event_id=events.id
WHERE (events.time BETWEEN $start AND $end) ORDER BY country.list_order, leagues.country ASC , leagues.id ASC, events.time ASC, home_name ASC";
Here's one way. Replace LEFT JOIN (SELECT team... etc....) ats with...
LEFT
JOIN
( SELECT x.team
, x.etc...
FROM team_quick_stats x
JOIN
( SELECT team
, MAX(updated) updated
FROM team_quick_stats
GROUP
BY team
) y
ON y.team = x.team
AND y.updated = x.updated
) ats...

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";

MySQL Query - alternative

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.

mysql long time query

I have a query on my PHP script it takes too long time!
When I run the query, server load time increase and server goes down!
Note: these fields are index: t1.submits_id,t4.submit_id,t3.userid,t1.user_id,owner_id,invited_id,agreed,t2.users_id,,t1.sent_timestamp
SELECT
t1.submits_id,t1.user_id,is_html,shared_from,type,contents,url,sent_timestamp,show_type,album_cached_info,
t2.users_id,name,t2.active,page_url,
t3.time,
t4.like_time
FROM iv6_submits as t1 LEFT JOIN iv6_likes as t4 ON (t1.submits_id = t4.submit_id AND t4.user_id=1)
,iv6_users as t2 LEFT JOIN iv6_onlineusers as t3 ON (t2.users_id=t3.userid)
WHERE
t1.submits_id<19000 AND
(t1.user_id=1 OR t1.user_id in
(select IF(owner_id=1,invited_id,owner_id) as id
from iv6_add_lists
where ((owner_id=1 or invited_id=1) AND agreed=1) OR (owner_id=1 AND agreed=2)))
AND t2.users_id=t1.user_id
ORDER BY t1.sent_timestamp DESC LIMIT 10
I'd start by writing the query as:
SELECT t1.submits_id,
t1.user_id,
is_html, /* I'd append all table aliases here */
shared_from,
type,
contents,
url,
sent_timestamp,
show_type,
album_cached_info,
t2.users_id,
name,
t2.active,page_url,
t3.time,
t4.like_time
FROM iv6_submits as t1
JOIN iv6_users as t2
ON t2.users_id=t1.user_id
LEFT JOIN iv6_likes as t4
ON t4.submit_id = t1.submits_id
AND t4.user_id=1
LEFT JOIN iv6_onlineusers as t3
ON t3.userid = t2.users_id
WHERE t1.submits_id<19000
AND (
t1.user_id=1
OR t1.user_id IN (
SELECT IF(owner_id=1,invited_id, owner_id) as id
FROM iv6_add_lists
WHERE ((owner_id=1 OR invited_id=1) AND agreed=1)
OR (owner_id=1 AND agreed=2))
)
ORDER BY t1.sent_timestamp DESC
LIMIT 10
From this and your explain i'd guess that t1.submits_id< 19000 reduces your result set the most. So I'd then try:
...
FROM iv6_submits as t1 USE INDEX (submits_id)
...
Your subquery is also v. strange, i'd rewrite it as something like:
SELECT invited_id
FROM iv6_add_lists
WHERE owner_id = 1
AND agreed BETWEEN 1 AND 2
UNION ALL
SELECT owner_id
FROM iv6_add_lists
WHERE invited_id = 1
AND agreed = 1
AND owner_id != 1
And see if that helps as well, you can hint an index for each FROM. A composite index on (owner_id, agreed, invited_id) and (invited_id, agreed, owner_id) should cover both queries respectively.
You also could write this as
SELECT 1
UNION
SELECT DISTINCT invited_id
FROM iv6_add_lists
WHERE owner_id = 1
AND agreed BETWEEN 1 AND 2
UNION
SELECT DISTINCT owner_id
FROM iv6_add_lists
WHERE owner_id != 1
AND agreed = 1
AND invited_id = 1
And JOIN it to your t1 instead of the AND (t1.user_id=1 OR ...)
UPDATE
SELECT t1.submits_id,
t1.user_id,
is_html, /* I'd append all table aliases here */
shared_from,
type,
contents,
url,
sent_timestamp,
show_type,
album_cached_info,
t2.users_id,
name,
t2.active,page_url,
t3.time,
t4.like_time
FROM iv6_submits as t1
JOIN iv6_users as t2
ON t2.users_id=t1.user_id
JOIN (
SELECT 1 user_id
UNION
SELECT DISTINCT invited_id
FROM iv6_add_lists
WHERE owner_id = 1
AND agreed BETWEEN 1 AND 2
UNION
SELECT DISTINCT owner_id
FROM iv6_add_lists
WHERE owner_id != 1
AND agreed = 1
AND invited_id = 1
) t5
ON t1.user_id = t5.user_id
LEFT JOIN iv6_likes as t4
ON t4.submit_id = t1.submits_id
AND t4.user_id=1
LEFT JOIN iv6_onlineusers as t3
ON t3.userid = t2.users_id
WHERE t1.submits_id<19000
ORDER BY t1.sent_timestamp DESC
LIMIT 10
I suggest you split the query to see where the resources are being consumed.
Execute the subquery in the WHERE section and see if it runs fast
Remove the left joins and then add one by one to see where it all goes down.
Remove all the "WHERE" conditions and add one by one.
Try to run the query without the "ORDER BY" statement.
You did not referred t4.user_id as being indexed, it may be important.
Table iv6_add_lists also should have all the fields present in WHERE statement indexed.
Also suggest you to make an INNER JOIN between t1 and t2:
FROM (iv6_submits as t1
LEFT JOIN iv6_likes as t4 ON (t1.submits_id = t4.submit_id AND t4.user_id=1))
INNER JOIN
(iv6_users as t2
LEFT JOIN iv6_onlineusers as t3 ON (t2.users_id=t3.userid))
ON t2.users_id=t1.user_id
Then ou can remove
AND t2.users_id=t1.user_id
from the WHERE statement.
Try This.
SELECT
t1.submits_id,t1.user_id,is_html,shared_from,type,contents,url,sent_timestamp,show_type,album_cached_info,
t2.users_id,name,t2.active,page_url,
t3.time,
t4.like_time
FROM
iv6_submits as t1
LEFT JOIN iv6_users as t2 ON t2.users_id = t1.user_id
LEFT JOIN iv6_likes as t4 ON t1.submits_id = t4.submit_id AND t4.user_id = 1
LEFT JOIN iv6_onlineusers as t3 ON t2.users_id = t3.userid
WHERE
t1.submits_id < 19000
AND
(t1.user_id = 1 OR t1.user_id in
(
select IF(owner_id=1,invited_id,owner_id) as id from iv6_add_lists where (invited_id=1 AND agreed=1) OR ( owner_id=1 AND agreed IN (1,2) )
)
)
ORDER BY t1.sent_timestamp DESC LIMIT 1

Limiting a left join to returning one result?

I currently have this left join as part of a query:
LEFT JOIN movies t3 ON t1.movie_id = t3.movie_id AND t3.popularity = 0
The trouble is that if there are several movies with the same name and same popularity (don't ask, it just is that way :-) ) then duplicate results are returned.
All that to say, I would like to limit the result of the left join to one.
I tried this:
LEFT JOIN
(SELECT t3.movie_name FROM movies t3 WHERE t3.popularity = 0 LIMIT 1)
ON t1.movie_id = t3.movie_id AND t3.popularity = 0
The second query dies with the error:
Every derived table must have its own alias
I know what I'm asking is slightly vague since I'm not providing the full query, but is what I'm asking generally possible?
The error is clear -- you just need to create an alias for the subquery following its closing ) and use it in your ON clause since every table, derived or real, must have its own identifier. Then, you'll need to include movie_id in the subquery's select list to be able to join on it. Since the subquery already includes WHERE popularity = 0, you don't need to include it in the join's ON clause.
LEFT JOIN (
SELECT
movie_id,
movie_name
FROM movies
WHERE popularity = 0
ORDER BY movie_name
LIMIT 1
) the_alias ON t1.movie_id = the_alias.movie_id
If you are using one of these columns in the outer SELECT, reference it via the_alias.movie_name for example.
Update after understanding the requirement better:
To get one per group to join against, you can use an aggregate MAX() or MIN() on the movie_id and group it in the subquery. No subquery LIMIT is then necessary -- you'll receive the first movie_id per name withMIN() or the last with MAX().
LEFT JOIN (
SELECT
movie_name,
MIN(movie_id) AS movie_id
FROM movies
WHERE popularity = 0
GROUP BY movie_name
) the_alias ON t1.movie_id = the_alias.movie_id
LEFT JOIN movies as m ON m.id = (
SELECT id FROM movies mm WHERE mm.movie_id = t1.movie_id
ORDER BY mm.id DESC
LIMIT 1
)
you could try to add GROUP BY t3.movie_id to the first query
Try this:
LEFT JOIN
(
SELECT t3.movie_name, t3.popularity
FROM movies t3 WHERE t3.popularity = 0 LIMIT 1
) XX
ON t1.movie_id = XX.movie_id AND XX.popularity = 0
On MySQL 5.7+ use ANY_VALUE & GROUP_BY:
SELECT t1.id,t1.movie_name, ANY_VALUE(t3.popularity) popularity
FROM t1
LEFT JOIN t3 ON (t3.movie_id=t1.movie_id AND t3.popularity=0)
GROUP BY t1.id
more info
LEFT JOIN only first row
https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html
Easy solution to left join the 1 most/least recent row is using select over ON phrase
SELECT A.ID, A.Name, B.Content
FROM A
LEFT JOIN B
ON A.id = (SELECT MAX(id) FROM B WHERE id = A.id)
Where A.id is the auto-incremental primary key.
LEFT JOIN (
SELECT id,movie_name FROM movies GROUP BY id
) as m ON (
m.id = x.id
)
// Mysql
SELECT SUM(db.item_sales_nsv) as total FROM app_product_hqsales_otc as db
LEFT JOIN app_item_target_otc as it ON
db.id = (SELECT MAX(id) FROM app_item_target_otc as ot WHERE id = db.id)
and db.head_quarter = it.hqcode
AND db.aaina_item_code = it.aaina_item_code AND db.month = it.month
AND db.year = it.year
WHERE db.head_quarter = 'WIN001' AND db.month = '5' AND db.year = '2022' AND db.status = '1'

Categories