Improve MySQL Union Statement - mysql

I have the following mySql statement, which I am sure can be made more efficient, I'm just not sure what would be the best way... the only difference is the equip_id that changes in the WHERE clause...
==============
(SELECT
`receipt_ts`,
`driver_id`,
`equip_id`,
`pos_lon`,
`pos_lat`,
`pos_timestamp`
FROM `log_messaging`
WHERE `equip_id`='207' AND `tran_type`='T.2.12.0'
ORDER BY receipt_ts DESC LIMIT 1 )
UNION
(SELECT
`receipt_ts`,
`driver_id`,
`equip_id`,
`pos_lon`,
`pos_lat`,
`pos_timestamp`
FROM `log_messaging`
WHERE `equip_id`='212' AND `tran_type`='T.2.12.0'
ORDER BY receipt_ts DESC LIMIT 1 )
UNION
(SELECT
`receipt_ts`,
`driver_id`,
`equip_id`,
`pos_lon`,
`pos_lat`,
`pos_timestamp`
FROM `log_messaging`
WHERE `equip_id`='213' AND `tran_type`='T.2.12.0'
ORDER BY receipt_ts DESC LIMIT 1 );
================
I am unioning each returned row to build a dataset of max(receipt_ts) for multiple equip_id's. (I need to get the most recent positioning for the equipment.
Sometimes the query ends up being for 100+ unique equip_id's.
I am trying to make the query execute quicker than it currently is (about 7 seconds for ~100 UNIONS as above...
Point me in the right direction??
Thanks!

I would use the IN clause :
SELECT receipt_ts, driver_id, equip_id, pos_lon, pos_lat, pos_timestamp
FROM log_messaging a
JOIN (SELECT c.equip_id, max(c.receipt_ts) as receipt
FROM log_messaging c
WHERE equip_id in ('207', '212', '213')
AND tran_type='T.2.12.0'
GROUP by c.equip_id) b USING(equip_id)
WHERE b.receipt = a.receipt_ts
ORDER BY a.receipt_ts
Note that if you really want to use the UNION (I don't see why you would) but want to optimize it, you could use the UNION ALL which would be more performent as UNION check datas for duplicata removal which consume more process.

First, you should use union all rather than union. So start with this query:
(SELECT receipt_ts, driver_id, equip_id, pos_lon, pos_lat, pos_timestamp
FROM log_messaging
WHERE equip_id = '207' AND tran_type = 'T.2.12.0'
ORDER BY receipt_ts DESC
LIMIT 1
) UNION ALL
(SELECT receipt_ts, driver_id, equip_id, pos_lon, pos_lat, pos_timestamp
FROM log_messaging
WHERE equip_id = '212' AND tran_type = 'T.2.12.0'
ORDER BY receipt_ts DESC
LIMIT 1
) UNION ALL
(SELECT receipt_ts, driver_id, equip_id, pos_lon, pos_lat, pos_timestamp
FROM log_messaging
WHERE equip_id = '213' AND tran_type = 'T.2.12.0'
ORDER BY receipt_ts DESC
LIMIT 1
) ;
This is a reasonable query. I would suggest that you create an index on log_messaging(tran_type, equip_id, recipt_ts).

Related

Get random records in MySql

I have a sql table, Employee which has 4 columns.
Id
Name
Address
Status – Fixed Type (Enum). Having 3 fixed values – OPEN, CLOSED and PENDING.
Let’s assume Total Records in the table are 200.
Now, I want to form a query which would return -
50 random records which have status “OPEN”.
25 random records which have status “CLOSED”.
45 random records which have status “PENDING”.
I have tried with the one fragmented query and it worked fine but looking for single query solution for whole scenario.
Select * from Employee a where a.Status = 'OPEN' ORDER BY RAND() LIMIT 50
Select * from Employee a where a.Status = 'CLOSED' ORDER BY RAND() LIMIT 25
Select * from Employee a where a.Status = 'PENDING' ORDER BY RAND() LIMIT 45
Any help would be highly appreciated.
Do you need it as a single query (mildly painful) or as 3 separate queries?
As 3 separate queries, the simplest option would be something like
Select * from Employee where Status = 'OPEN' ORDER BY RAND() LIMIT 50;
Select * from Employee where Status = 'CLOSED' ORDER BY RAND() LIMIT 25;
Select * from Employee where Status = 'PENDING' ORDER BY RAND() LIMIT 45;
Use Union to combine multiple queries and remember to assign alias for each sub/parent query.
select * from ((Select * from Employee a where a.Status = 'OPEN' ORDER BY RAND() LIMIT 50)
union
(Select * from Employee b where b.Status = 'CLOSED' ORDER BY RAND() LIMIT 25)
union
(Select * from Employee c where c.Status = 'PENDING' ORDER BY RAND() LIMIT 45)) d;
I hope this will be useful for someone.

Fetch only 2 results, one for each different value of a concrete field

In a MYSQL table with those 5 fields: id, user_id, date, type, uid where type can be 1 or 2, I'm looking for a single query where I can fetch 2 results, one for type=1 and another one for type=2 based on date field.
Right now i have the following query which only gives me the last uid without taking care of the type field.
SELECT t.uid
FROM table AS t
WHERE t.user_id = 666
ORDER BY t.date
DESC LIMIT 1
Does anyone know how should modify this query so i can get the last uid for type=1 and the last one for type=2 based on date field? I would like to keep a a single query
Union all is probably the simplest method:
(select t.*
from t
where t.user_id = 666 and t.type = 1
order by date desc
limit 1
) union all
(select t.*
from t
where t.user_id = 666 and t.type = 2
order by date desc
limit 1
)
Finally i updated the query following this "paradigm":
http://dev.mysql.com/doc/refman/5.7/en/example-maximum-column-group-row.html
http://jan.kneschke.de/projects/mysql/groupwise-max/
This is how the query ended up:
SELECT s1.type, s1.uid
FROM t AS s1
LEFT JOIN t AS s2 ON s1.type = s2.type AND s1.date < s2.date
WHERE s2.date IS NULL;
Here's a visual example: http://hastebin.com/ibinidasuw.vhdl
Credits are for snoyes from #sql on Freenode. :)

Select sum of top three scores for each user

I am having trouble writing a query for the following problem. I have tried some existing queries but cannot get the results I need.
I have a results table like this:
userid score timestamp
1 50 5000
1 100 5000
1 400 5000
1 500 5000
2 100 5000
3 1000 4000
The expected output of the query is like this:
userid score
3 1000
1 1000
2 100
I want to select a top list where I have n best scores summed for each user and if there is a draw the user with the lowest timestamp is highest. I really tried to look at all old posts but could not find one that helped me.
Here is what I have tried:
SELECT sum(score) FROM (
SELECT score
FROM results
WHERE userid=1 ORDER BY score DESC LIMIT 3
) as subquery
This gives me the results for one user, but I would like to have one query that fetches all in order.
This is a pretty typical greatest-n-per-group problem. When I see those, I usually use a correlated subquery like this:
SELECT *
FROM myTable m
WHERE(
SELECT COUNT(*)
FROM myTable mT
WHERE mT.userId = m.userId AND mT.score >= m.score) <= 3;
This is not the whole solution, as it only gives you the top three scores for each user in its own row. To get the total, you can use SUM() wrapped around that subquery like this:
SELECT userId, SUM(score) AS totalScore
FROM(
SELECT userId, score
FROM myTable m
WHERE(
SELECT COUNT(*)
FROM myTable mT
WHERE mT.userId = m.userId AND mT.score >= m.score) <= 3) tmp
GROUP BY userId;
Here is an SQL Fiddle example.
EDIT
Regarding the ordering (which I forgot the first time through), you can just order by totalScore in descending order, and then by MIN(timestamp) in ascending order so that users with the lowest timestamp appears first in the list. Here is the updated query:
SELECT userId, SUM(score) AS totalScore
FROM(
SELECT userId, score, timeCol
FROM myTable m
WHERE(
SELECT COUNT(*)
FROM myTable mT
WHERE mT.userId = m.userId AND mT.score >= m.score) <= 3) tmp
GROUP BY userId
ORDER BY totalScore DESC, MIN(timeCol) ASC;
and here is an updated Fiddle link.
EDIT 2
As JPW pointed out in the comments, this query will not work if the user has the same score for multiple questions. To settle this, you can add an additional condition inside the subquery to order the users three rows by timestamp as well, like this:
SELECT userId, SUM(score) AS totalScore
FROM(
SELECT userId, score, timeCol
FROM myTable m
WHERE(
SELECT COUNT(*)
FROM myTable mT
WHERE mT.userId = m.userId AND mT.score >= m.score
AND mT.timeCol <= m.timeCol) <= 3) tmp
GROUP BY userId
ORDER BY totalScore DESC, MIN(timeCol) ASC;
I am still working on a solution to find out how to handle the scenario where the userid, score, and timestamp are all the same. In that case, you will have to find another tiebreaker. Perhaps you have a primary key column, and you can choose to take a higher/lower primary key?
Query for selecting top three scores from table.
SELECT score FROM result
GROUP BY id
ORDER BY score DESC
LIMIT 3;
Can you please try this?
SELECT score FROM result GROUP BY id ORDER BY score DESC, timestamp ASC LIMIT 3;
if 2 users have same score then it will set order depends on time.
You can use a subquery
SELECT r.userid,
( SELECT sum(r2.score)
FROM results r2
WHERE r2.userid = r.userid
ORDER BY score DESC
LIMIT 3
) as sub
FROM result r
GROUP BY r.userid
ORDER BY sub desc
You should do it like this
SELECT SUM(score) as total, min(timestamp) as first, userid FROM scores
GROUP BY userid
ORDER BY total DESC, first ASC
This is way more efficient than sub queries. If you want to extract more fields than userid, then you need to add them to the group by.
This will of cause not limit the number of scores pr user, which indeed seems to require a subquery to solve.

how do I get most recent 10 calls in group concat with a query?

Here is my table structure
phone_calls(id, phone_number, call_id, timestamp, colx, col y )
I want to retrieve 10 most recent calls from phone_calls table within a group concat, without sub query
Try this (without a subquery it will not work):
SELECT
GROUP_CONCAT(call_id)
FROM (
SELECT
call_id
FROM
phone_calls
ORDER BY
id DESC
LIMIT 10
) as tmp
UPDATE: without sub-query:
SET #c:='';
SELECT
#c:=CONCAT(#c,',',call_id)
FROM
phone_calls
ORDER BY
id DESC
LIMIT 10;
SELECT #c;
Ok I'm not sure if this is your problem, but to use group_concat you need a group by column right? Not sure if this will work but you can try it, I used this dummy col way before
select 1 as col, group_concat(call_id) as latest_calls from phone_calls
ORDER BY timestamp DESC GROUP BY col LIMIT 10
you'll have an extra useless column col, but latest_calls should be correct

Most Common Value SubQuery MySql

Is there any other way to write this query ?
I tried doing it in a subquery but it doesn't work because of the multiple columns. It seems like this query only works by itself. Please correct me
Records
PK recordId
dateViewed
CarViewed
I tried this
SELECT R.`dateViewed` FROM Records ,(
SELECT R.CarViewed, COUNT(R.CarViewed) as cnt FROM Records R
GROUP BY R.CarViewed
ORDER BY cnt DESC
LIMIT 1 ) AS favouriteCarOfTheDay
GROUP BY R.`dateViewed
Then I tried this
SELECT R.`dateViewed` ,COUNT(R.CarViewed) as cnt FROM Records ,
(
SELECT R.CarViewed FROM Records R
GROUP BY R.CarViewed
ORDER BY cnt DESC
LIMIT 1 ) AS favouriteCarOfTheDay
GROUP BY R.`dateViewed
Along many other queries I tried, I have no idea how to get it working.
In a nutshell for a specific date, I would like to get the most common cars that were viewed.
Like :
dateViewed favouriteCarOfTheDay
2012-09-22 | Nissan
2012-09-23 | BMW
try this
SELECT R.`dateViewed` ,COUNT(R.CarViewed) as cnt ,R.CarViewed FROM Records R
GROUP BY R.`dateViewed
ORDER BY COUNT(R.CarViewed) DESC
I think the following should work (disclaimer, not all my own work, adapted from an answer at another question)
SELECT DISTINCT
R.dateViewed,
R.CarViewed
FROM Records R
WHERE
R.dateViewed =
(SELECT R2.dateViewed FROM
(
SELECT R1.dateViewed, COUNT(*) AS numViewed
FROM Records R1
WHERE R1.CarViewed = R.CarViewed
GROUP BY R1.dateViewed
ORDER BY R1.numViewed DESC
) AS R2
LIMIT 1
)
ORDER BY r.dateViewed
Such things are really awful to do in MySQL so it might actually by slower than two correlated subquery but at least it returns both the car and it's viewcount:
SELECT counts.`dateViewed`,counts.`CarViewed` as favourite_car, counts.cnt
FROM
(SELECT R.`dateViewed` ,R.`CarViewed`, COUNT(*) as cnt
FROM Records
GROUP BY R.`dateViewed` ,R.`CarViewed`
) as counts JOIN
(SELECT R.`dateViewed`, MAX(cnt) as cnt
FROM
(SELECT R.`dateViewed` ,R.`CarViewed`, COUNT(*) as cnt
FROM Records
GROUP BY R.`dateViewed` ,R.`CarViewed`
) as q
GROUP BY R.`dateViewed`) as maxes
ON counts.cnt=maxes.cnt