How to optimize mysql query with an inner query - mysql

I'm always getting a timed out on the following query:
select * FROM `products`
where
`active`=1
and `group`=6
and `id` not in (select `id` from `purcheased` where `userId`=14 and `price`>100
and `reversed`=0)
order by `price` asc limit 0,500
This takes 0.01s to execute, and in this particular case returns 0 results:
select `id` from `purcheased` where `userId`=14 and `price`>100 and `reversed`=0
This executes in .02s:
select * FROM `products`
where
`active`=1
and `group`= 6
order by `price` asc limit 0,500
The full query
select * FROM `products`
where
`active` = 1
and `group` = 6
and `id` not in (
select `id` from `purcheased`
where
`userId`=14
and `price` > 100
and `reversed`=0
)
order by `price` asc limit 0,500
executes it 60 seconds!
I think this is happening because select id from purcheased ... is being executed for each row of the products.
I'm running the queries in mysql.
How do I tell mysql to execute the select id from purcheased once and than to re-use the result?

MySQL misoptimizes IN and NOT IN with subqueries. You can rewrite the query as a correlated subquery:
select *
FROM `products`
where `active`=1 and `group`=6 and
not exists (select `id`
from `purchased`
where `userId`=14 and `price`>100 and `reversed`=0 and
purchased.id = products.id
)
order by `price` asc
limit 0,500
This will also work better if you have an index on purchased.id. Actually, if this is the form of your table, then an index on purchased for (userid, reversed, id, price) should make this go much faster.

A LEFT OUTER JOIN is probably your best bet here:
select p.*
from `products` p
left outer join (
select `id`
from `purcheased`
where `userId` = 14
and `price` > 100
and `reversed` = 0
) pu on p.id = pu.id
where p.`active` = 1
and p.`group` = 6
and pu.id is null
order by p.`price`
limit 0, 500

Related

Mysql query dosen't provide the result needed

I've been trying to figure this one out for days but can't come up with a solution.
Here are the table schemes.
This is my current query.
SELECT DISTINCT `address`, `order`.`id`
FROM `order`, `ordered_articles`
WHERE `order`.`id` = `f_order_id`
AND `Status` > 1
AND `Status` <4;
The problem is that the query returns as long as there is one article with status bigger than 1. I need a query where all the articles of that order have a status bigger than 1.
You can do it with NOT EXISTS:
SELECT o.`address`, o.`id`
FROM `order` o
WHERE NOT EXISTS (
SELECT 1 FROM `ordered_articles`
WHERE `f_order_id` = o.`id`
AND (`Status` <= 1 OR `Status` >= 4 )
);
or:
SELECT o.`address`, o.`id`
FROM `order` o INNER JOIN `ordered_articles` i
ON i.`f_order_id` = o.`id`
GROUP BY o.`address`, o.`id`
HAVING SUM(`Status` <= 1 OR `Status` >= 4) = 0;

Unknown column due to second-level subquery nesting

I want to retrieve a user's rank based on how many points the given user has compared to other users (simply counting users with more points than the given user).
However, with all the queries I have tried, I always end up with Column not found: users.id. From what I can read there is a limit from referencing correlated parent columns more than one level up.
Can I refactor my query, or do I really need to use SET #rownum := 0 style of queries?
SELECT
`users`.*,
(
SELECT COUNT(*) + 1
FROM (
SELECT SUM(`amount`) AS `all_points`
FROM `points`
WHERE `type` = ?
GROUP BY `user_id`
HAVING `all_points` > (
SELECT SUM(`amount`)
FROM `points`
WHERE `type` = ? and `user_id` = `users`.`id`
)
) `points_sub`
) as `rank`
FROM `users`
WHERE `users`.`id` = ?
LIMIT 1
You can move your sub clause one level up, Remove having filter and use where filter
SELECT
`users`.*,
(
SELECT COUNT(*) + 1
FROM (
SELECT user_id,SUM(`amount`) AS `all_points`
FROM `points`
WHERE `type` = ?
GROUP BY `user_id`
) `points_sub`
WHERE `all_points` >
SELECT SUM(`amount`)
FROM `points`
WHERE `type` = ? and `user_id` = `users`.`id`
) as `rank`
FROM `users`
WHERE `users`.`id` = ?
LIMIT 1
I think the below query should work for you. You can pass the user_id of user whose rank you want to compute in both the arguments.
SELECT
`users`.*,
(
SELECT COUNT(*) + 1
FROM (
SELECT SUM(`amount`) AS `all_points`
FROM `points`
WHERE `type` = ?
GROUP BY `user_id`
HAVING `all_points` > (
SELECT COALESCE(SUM(`amount`),0)
FROM `points`
WHERE `type` = ? and `user_id` = ?
)
) `points_sub`
) as `rank`
FROM `users`
WHERE `users`.`id` = ?
LIMIT 1

How to join / union multiple results from same table?

I've mysql tables that looks like :
user_messages
id | user_id | phone_number | message | direction | created_at
users
id | name
I want to 'group by' user_messages two times and UNION the result. Why I want to do this? because user_id sometimes has a valid user id (anything but '-1') then I group by it, if it has -1, then group by phone_number.
I also want to left join the result with users table to get the user name in case user_id is set to a valid user
I'm almost done with the query, but the problem is:
- I want the result to have the record that results from group by to be the latest one, which means, the biggest created_at value
select * from (
(
select *, count(*) as `total` from
(select `user_id`, `message`, `created_at`, `phone_number`,`direction` from `users_messages` where `user_id` != -1 order by `created_at` desc)
as `t1` group by `user_id`
)
union
(
select *, count(*) as `total` from
(select `user_id`, `message`, `created_at`, `phone_number`,`direction` from `users_messages` where `user_id` = -1 order by `created_at` desc)
as `t2` group by `phone_number`
)
) as `t3`
left join (select `id`,`name` from `users`) as `t4` on `t3`.`user_id` = `t4`.`id` order by `created_at` desc
What this gets me is the results not sorted by created_at DESC
Update:
The query actually works in my local machine but not on the production server. In my local machine I have 5.5.42 - Source distribution and in server Ver 14.14 Distrib 5.7.17, for Linux (x86_64) using EditLine wrapper ... What could be wrong?
In local machine it correctly returns me the max created_at but in server it returns the FIRST created for the grouped by record
Something like this should work:
SELECT s.`user_id`, um.`phone_number`, s.msgCount
, um.`message`, um.`created_at`, um.`direction`
, u.`name` AS userName
FROM (
SELECT `user_id`, IF(`user_id` = -1, `phone_number`, '') AS altID, MAX(`created_at`) AS lastCreatedAt, COUNT(*) AS msgCount
FROM `users_messages`
GROUP BY user_id, altID
) AS s
INNER JOIN `users_messages` AS um
ON s.user_id = um.user_id
AND s.altID = IF(um.`user_id` = -1, um.`phone_number`, '')
AND s.lastCreatedAt = um.created_at
LEFT JOIN `users` AS u
ON s.user_id = u.user_id
ORDER BY um.created_at DESC
;
The s subquery gets the summary information for each user and userless phone number; the summary information calculated includes the most recent created_at value for use in the following....
The join to um gets the row data for their last messages (by including the lastCreatedAt value from s in the join criteria)
The final join to users is used to get the user.name for the known users (and assumes there will be no -1 user, or that such a user would have an appropriate 'unknown' name.)
Since you're grouping by user_id and phone_number, you can't keep message or direction. Add a max function for created_at in each subquery. I think this would work.
select * from (
(
select user_id
,'' as phone_number
,max('created_at') as 'created_at'
,count(*) as `total` from
(select `user_id`
,`created_at`
from `users_messages`
where `user_id` != -1)
as `t1` group by `user_id`
)
union
(
select '' as user_id
,phone_number
,max('created at') as 'created_at'
,count(*) as `total` from
(select `created_at`
,`phone_number'
from `users_messages`
where `user_id` = -1)
as `t2` group by `phone_number`
)
) as `t3`
left join (select `id`,`name` from `users`) as `t4`
on `t3`.`user_id` = `t4`.`id`
order by `created_at` desc

Need help querying a max value from duplicated data, grouped by multiple columns

I have data with lots of duplicates, and I am trying to update only the rows where the quantity is the highest, grouping by multiple columns. I tried this but it doesn't work. Any help would be appreciated. Thanks
UPDATE `polls` AS `p1`
INNER JOIN (
SELECT *
FROM `polls`
GROUP BY `server_id`, `product_id`, `poll_date`
ORDER BY max(quantity)
HAVING COUNT(*) > 1
) AS `p2`
ON `p2`.`server_id` = `p1`.`server_id`
AND `p2`.`product_id` = `p1`.`product_id`
AND `p2`.`poll_date` = `p1`.`poll_date`
SET `p1`.`updated_by` = 'admin';
try this
....
INNER JOIN (
SELECT * , max(quantity)
FROM `polls`
GROUP BY `server_id`, `product_id`
ORDER BY max(quantity) desc
)
....
use this
INNER JOIN (
SELECT *
FROM 'polls'
GROUP BY `server_id`, `product_id`, `poll_date`
ORDER BY quantity desc limit 1
)

Merge two similar aggregate functions in select statement

Here's a SQL statement:
SELECT DISTINCT `class`, `student_id` , `student_name`,
(
SELECT SUM( `credits` )
FROM `stumgr_scores` B
JOIN `stumgr_courses` USING ( `course_id` )
WHERE `year` =2012 AND A.`student_id` = B.`student_id`
) AS `total_credits`,
(
SELECT SUM( `credits` * `final_score` )
FROM `stumgr_scores` C
JOIN `stumgr_courses` USING ( `course_id` )
WHERE `year` =2012 AND A.`student_id` = C.`student_id`
) AS `total_scores`
FROM `stumgr_scores` A
NATURAL JOIN `stumgr_students`
WHERE `year` =2012 AND `grade` =2011
You may discover that these two select statement which use aggregate functions are similar. So, I want to merge them into one as following:
SELECT DISTINCT `class`, `student_id` , `student_name`,
(
SELECT
SUM( `credits` ) AS `total_credits`,
SUM( `credits` * `final_score` ) AS `total_scores`
FROM `stumgr_scores` B
JOIN `stumgr_courses` USING ( `course_id` )
WHERE `year` =2012 AND A.`student_id` = B.`student_id`
) AS `something`
FROM `stumgr_scores` A
NATURAL JOIN `stumgr_students`
WHERE `year` =2012 AND `grade` =2011
Of course, the SQL statement above doesn't work and I don't know what to do.
Besides, the query is very slow because of large data, do you have any suggestions? Thanks a lot.
I have had to guess at your table structure slightly, but you should be able to simplify this query massively by using JOINs rather than correlated subqueries:
SELECT st.student_id,
st.student_name,
c.class,
SUM(sc.credits) AS total_credits,
SUM(sc.credits * sc.final_score) AS total_scores
FROM stumgr_students st
INNER JOIN stumgr_scores sc
ON sc.student_id = st.student_id
INNER JOIN stumgr_courses c
ON c.course_id = st.course_id
GROUP BY st.student_id, st.student_name, c.class;