How can I optimize a query involving a subselect in MySQL? - mysql

Suppose I have a table like this:
id | price | group1
1 | 6 | some_group
2 | 7 | some_group
3 | 8 | some_group
4 | 9 | some_other_group
If I want to select the lowest price grouped by group1
I can just do this:
SELECT id, min(price), group1 FROM some_table GROUP BY group1;
The problem is when I have a table which is not sorted by price like this:
id | price | group1
1 | 8 | some_group
2 | 7 | some_group
3 | 6 | some_group
4 | 9 | some_other_group
Then my query returns this result set:
id | price | group1
1 | 6 | some_group
4 | 9 | some_other_group
The problem is that I get 1 in the id column but the id of the row with the price of 6 is not 1 but 3.
My question is that how can I get the values from the row which contains the minimum price when I use GROUP BY?
I tried this:
SELECT f.id, min(f.price), f.group1 FROM (SELECT * FROM some_table ORDER BY price) f
GROUP BY f.group1;
but this is really slow and if I have multiple columns and aggregations it may fail.
Please note that the names above are just for demonstration purposes. My real query looks like this:
SELECT depdate, retdate, min(totalprice_eur) price FROM
(SELECT * FROM flight_results
WHERE (
fromcity = 30001350
AND tocity = 30001249
AND website = 80102118
AND roundtrip = 1
AND serviceclass = 1
AND depdate > date(now()))
ORDER BY totalprice_eur) F
WHERE (
fromcity = 30001350
AND tocity = 30001249
AND website = 80102118
AND roundtrip = 1
AND serviceclass = 1
AND depdate > date(now()))
GROUP BY depdate,retdate
and there is a concatenated primary key including website, fromcity, tocity, roundtrip, depdate, and retdate. There are no other indexes.
Explain says:
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY <derived2> ALL NULL NULL NULL NULL 2837 Using where; Using temporary; Using filesort
2 DERIVED flight_results ALL PRIMARY NULL NULL NULL 998378 Using filesort

You can do this instead:
SELECT t1.id, t1.price, t1.group1
FROM some_table AS t1
INNER JOIN
(
SELECT min(price) minprice, group1
FROM some_table
GROUP BY group1
) AS t2 ON t1.price = t2.minprice AND t1.group1 = t2.group1;

Related

How to get n rows of records of given m ids in Mysql by id desc

Suppose i have a table like this
id | user_id | rating
1 | 500 | 5
2 | 501 | 3
3 | 500 | 5
4 | 502 | 4
5 | 502 | 1
How can i write a mysql query to find the last 10 records for each id of given three ids (500, 501,502) by id desc
assuming your id is an auto increment column and the three ids (500, 501,502) are for user_id
then you could use
select *
from my_table
where user_id in (500, 501,502)
order by id desc
limit 10
Being a bit lazy here's a way to get the last 2 per user_id by using a variable to allocate a row number joined to the max occurrences per user
DROP TABLE IF EXISTS T;
CREATE TABLE T
(ID INT AUTO_INCREMENT PRIMARY KEY, USER_ID INT, RATING INT);
INSERT INTO T (USER_ID,RATING) VALUES
(500,1),
(502,1),
(500,2),(500,3),
(502,2),
(600,1);
SELECT U.*
FROM
(
SELECT S.ID,S.USER_ID,S.RATING,
T.MAXROWS
FROM
(
SELECT ID,USER_ID,RATING,
IF(USER_ID <> #P,#R:=1,#R:=#R+1) RN,
#P:=USER_ID
FROM T
ORDER BY USER_ID ASC,ID ASC
) S
JOIN
(SELECT USER_ID,COUNT(*) MAXROWS FROM T GROUP BY USER_ID) T ON T.USER_ID = S.USER_ID
) U
WHERE U.RATING > U.MAXROWS - 2 AND U.USER_ID IN(500,502);
+----+---------+--------+---------+
| ID | USER_ID | RATING | MAXROWS |
+----+---------+--------+---------+
| 3 | 500 | 2 | 3 |
| 4 | 500 | 3 | 3 |
| 2 | 502 | 1 | 2 |
| 5 | 502 | 2 | 2 |
+----+---------+--------+---------+
4 rows in set (0.00 sec)
You can use UNION ALL operator to get 10 result of each id
(select * from test
where user_id =500
order by id desc
limit 10)
UNION ALL
(select * from test
where user_id =501
order by id desc
limit 10)
UNION ALL
(select * from test
where user_id =502
order by id desc
limit 10)
order by id desc;
Check here for DEMO
NOTE: Reviewer are welcome to enhance or improve the query.

Getting the most recent row and linking it with another table?

Im trying to get the most recent row of a table
user_quiz:
+--------+-----------+-------------+-------------------+------------+
|quiz_id |userid | module_id |number_of_questions| user_score |
+--------+-----------+-------------+-------------------+-------- ---+
| 1 | 1 | 1 | 5 | 5 |
| 2 | 2 | 2 | 10 | 9 |
| 3 | 1 | 1 | 10 | 9 |
+--------+-----------+-------------+-------------------+------------+
I have used the query:
SELECT * FROM user_quiz WHERE userid = 1 ORDER BY quiz_id DESC LIMIT 1
which correctly retrieves the last row.
However I want to link the module_id with another table:
module:
+---------+------------+
|module_id|module_name |
+---------+------------+
| 1 | Forces |
| 2 | Electricity|
+---------+------------+
And retrieve the module name.
The result of the query will be used to print out the users most recent quiz:
Most recent quiz: Forces - Number of questions: 10 - User Score: 9
Is this possible using just one query?
You just need a JOIN:
SELECT uq.*, m.module_name
FROM user_quiz uq JOIN
modules m
ON uq.module_id = m.module_id
WHERE uq.userid = 1
ORDER BY uq.quiz_id DESC
LIMIT 1;
A more simple query to achieve the same would be
SELECT
user_quiz.quiz_id,
user_quiz.number_of_questions,
user_quiz.user_score,
modules .module_name
FROM user_quiz JOIN modules
ON user_quiz.module_id = modules.module_id
WHERE user_quiz.userid = 1
ORDER BY user_quiz.quiz_id DESC
LIMIT 1
If you want to get the same results for all the users, you could use a bit more sophisticated query
SELECT
user_quiz_virtual_table.userid,
user_quiz_virtual_table.quiz_id,
user_quiz_virtual_table.number_of_questions,
user_quiz_virtual_table.user_score,
modules.module_name
FROM (
SELECT
user_quiz.userid
user_quiz.quiz_id,
user_quiz.module_id
user_quiz.number_of_questions,
user_quiz.user_score
FROM user_quiz
ORDER BY user_quiz.quiz_id DESC
GROUP BY userid
) AS user_quiz_virtual_table
JOIN modules ON user_quiz_virtual_table.module_id = modules.module_id

mysql subquery not producing all results

I have two tables: contacts and client_profiles. A contact has many client_profiles, where client_profiles has foreign key contact_id:
contacts:
mysql> SELECT id,first_name, last_name FROM contacts;
+----+-------------+-----------+
| id | first_name | last_name |
+----+-------------+-----------+
| 10 | THERESA | CAMPBELL |
| 11 | donato | vig |
| 12 | fdgfdgf | gfdgfd |
| 13 | some random | contact |
+----+-------------+-----------+
4 rows in set (0.00 sec)
client_profiles:
mysql> SELECT id, contact_id, created_at FROM client_profiles;
+----+------------+---------------------+
| id | contact_id | created_at |
+----+------------+---------------------+
| 6 | 10 | 2014-10-09 17:17:43 |
| 7 | 10 | 2014-10-10 11:38:01 |
| 8 | 10 | 2014-10-10 12:20:41 |
| 9 | 10 | 2014-10-10 12:24:19 |
| 11 | 12 | 2014-10-10 12:35:32 |
+----+------------+---------------------+
I want to get the latest client_profiles for each contact. That means There should be two results. I want to use subqueries to achieve this. This is the subquery I came up with:
SELECT `client_profiles`.*
FROM `client_profiles`
INNER JOIN `contacts`
ON `contacts`.`id` = `client_profiles`.`contact_id`
WHERE (client_profiles.id =
(SELECT `client_profiles`.`id` FROM `client_profiles` ORDER BY created_at desc LIMIT 1))
However, this is only returning one result. It should return client_profiles with id 9 and 11.
What is wrong with my subquery?
It looks like you were trying to filter twice on the client_profile table, once in the JOIN/ON clause and another time in the WHERE clause.
Moving everything in the where clause looks like this:
SELECT `cp`.*
FROM `contacts`
JOIN (
SELECT
`client_profiles`.`id`,
`client_profiles`.`contact_id`,
`client_profiles`.`created_at`
FROM `client_profiles`
ORDER BY created_at DESC
LIMIT 1
) cp ON `contacts`.`id` = `cp`.`contact_id`
Tell me what you think.
Should be something like maybe:
SELECT *
FROM `client_profiles`
INNER JOIN `contacts`
ON `contacts`.`id` = `client_profiles`.`contact_id`
GROUP BY `client_profiles`.`contact_id`
ORDER BY created_at desc;
http://sqlfiddle.com/#!2/a3f21b/9
You need to prequery the client profiles table grouped by each contact.. From that, re-join to the client to get the person, then again to the client profiles table based on same contact ID, but also matching the max date from the internal prequery using max( created_at )
SELECT
c.id,
c.first_name,
c.last_name,
IDByMaxDate.maxCreate,
cp.id as clientProfileID
from
( select contact_id,
MAX( created_at ) maxCreate
from
client_profiles
group by
contact_id ) IDByMaxDate
JOIN contacts c
ON IDByMaxDate.contact_id = c.id
JOIN client_profiles cp
ON IDByMaxDate.contact_id = cp.contact_id
AND IDByMaxDate.maxCreate = cp.created_at

mysql query, selecting from 3 tables

I am wrangling with a query pulling unique values from 3 tables. Is this better done in 2 separate queries?
the query is to:
count as returned (
all leadID from lds where status = "ok"
AND leadID is also in rlds with recID="999"
AND rdate > (03-20-2012)
+
(all distinct leadID from plds where recID="999"
AND change != NULL
AND pdate > (03-20-2012))
the result of the working query should be "2": leadID 1 and leadID 4
table lds:
leadID | status
1 | ok
2 | bad
3 | ok
table plds:
leadID | recID | change | pdate
4 | 999 | ch1 | 03-27-2012
4 | 999 | ch2 | 03-27-2012
5 | 888 | NULL | 03-27-2012
table rlds:
leadID | recID2 | rdate
1 | 999 | 03-27-2012
6 | 999 | 03-27-2012
Thanks!
SELECT Ids.leadId
FROM
Ids JOIN
rlds ON rlds.leadId = Ids.LeadId AND recID = 999 AND rdate > '03-20-2012'
WHERE Ids.Status = 'ok'
UNION
SELECT leadId
FROM pIds
WHERE recID = 999 AND change IS NOT NULL AND pdate > '03-20-2012'
You can use the UNION key word to retreive a array a 2 row, the first one containing the count of your first match and the seconde the count of your second match.
You can then use the SUM keyword to sum the two row altogether in an enclosing select. The result will be a single row contening your count.
SELECT sum(1)
FROM
(
SELECT count(1)
FROM lds
INNER JOIN rlds ON lds.leadid = plds.leadid
WHERE lds.status='ok' AND rlds.recid2=999 AND rlds.pdate > '03-20-2012'
UNION
SELECT count(1)
FROM plds
WHERE plds.recid=999
AND change != NULL
AND pdate > '03-20-2012'
) AS tmpcount

How to return multiple rows when compared to multiple values?

So my current table looks like this :
| UserID | QuestionID | GameID |
| 1 | 30 | 2 |
| 1 | 30 | 3 |
| 2 | 30 | 4 |
| 3 | 30 | 2 |
| 4 | 30 | 3 |
How would I return all the rows where the same UserID has a value for GameID 1, 2, 3 and 4? Basically trying to return all the users who have played Game 1, 2, 3 and 4
Assuming no duplicates of game id..
Select userID
from table
where gameID in (1,2,3,4)
group by UserID
having count(GameID) = 4
If I get the answer correct, it might be:
SELECT p.* FROM poll AS p WHERE p.UserID = <your_user_id> AND p.GameID IN (1,2,3,4)
or you are seeking for:
SELECT p.*, GROUP_CONCAT(p.GameID) FROM poll AS p WHERE p.UserID = <user_id> GROUP BY p.GameID
Assuming the tables are normalized and joined properly, simply use a WHERE clause:
WHERE colname = value1 OR colname = value2 ...
or use the In operator
WHERE colname = In (value,value...)
http://beginner-sql-tutorial.com/sql-logical-operators.htm
SELECT
UserID
FROM (
SELECT
UserID,
GROUP_CONCAT(GameID ORDER BY GameID) as Games
FROM some_table
GROUP BY UserID
) AS baseview
WHERE Games="1,2,3,4"
Please understand, this is a performance killer - it creates the GROUP_CONCAT for all users.