I have a working query that seems awfully inefficient; I'm wondering if I'm missing a simple way to improve it.
Simple table:
id date master_id
-------------------------
1 2015-02-01 0
2 2015-02-02 0
3 2015-02-03 0
4 2015-02-04 1
5 2015-02-02 1
6 2015-02-17 1
7 2015-02-27 1
8 2015-01-01 1
Objective: Get all rows where the master_id is zero, OR the master_id is not zero and no other rows of the same master_id have an earlier date. Order every result by date.
Current query, using a groupwise minimum subquery to create the second WHERE condition.
SELECT *
FROM `test`
WHERE `master_id` =0
OR `id` IN (
SELECT test.`id`
FROM (
SELECT `master_id`, MIN(`date`) AS mindate
FROM `test`
WHERE `master_id` 0
GROUP BY `master_id`
) AS x
INNER JOIN `test` ON x.`master_id` = test.`master_id`
AND x.mindate= test.`date`
)
ORDER BY `date`
It works, but the EXPLAIN makes it seem inefficient:
id select_type table type possible_keys key key_len ref rows Extra
-------------------------------------------------------------------------------------------------------------
1 PRIMARY test ALL NULL NULL NULL NULL 8 Using where; Using filesort
2 DEPENDENT SUBQUERY derived3 system NULL NULL NULL NULL 1
2 DEPENDENT SUBQUERY test eq_ref PRIMARY PRIMARY 4 func 1 Using where
3 DERIVED test ALL NULL NULL NULL NULL 8 Using where; Using temporary; Using filesort
Can I improve this? Should I break it into two queries, one for ID=0 and one for the groupwise min? Thanks in advance.
Avoiding the inner join can improve the query:
SELECT *
FROM `test`
WHERE `master_id` =0
OR `id` IN (
SELECT t1.id
FROM (SELECT *
FROM test t2
WHERE t2.master_id!=0
ORDER BY t2.date ASC) t1
GROUP BY t1.master_id
)
ORDER BY `date`;
How about this...
SELECT * FROM test WHERE master_id = 0
UNION
SELECT x.*
FROM test x
JOIN (SELECT master_id,MIN(date) min_date FROM test GROUP BY master_id) y
ON y.master_id = x.master_id
AND y.min_date = x.date;
Related
I have a table like this:
CREATE TABLE `data` (
`id` int(11) NOT NULL,
`deviceId` int(11) NOT NULL,
`position_x` int(11) NOT NULL,
`position_y` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `data`
ADD PRIMARY KEY (`id`);
COMMIT;
id, deviceId, position_x, position_y
1 1 100 0
2 2 150 50
3 3 200 20
4 1 220 20
5 1 210 10
6 2 100 40
7 3 120 50
8 3 130 60
9 2 240 15
I need the "newest" two rows per DeviceID, where a bigger ID means newer.
Right now, I'm selecting the newest row per Device via this query:
SELECT
id,
deviceId,
position_x, position_y
FROM data
WHERE deviceId > 0 AND
id IN (SELECT MAX(id) FROM data GROUP BY deviceId)
And in a loop, where I output the data, I select the second latest row for every deviceId in an individual query, which is kinda slow/dirty:
SELECT
position_x
position_y
FROM data
WHERE deviceId = :deviceId AND
id < :id
ORDER BY id DESC
LIMIT 1
Is there a way to combine both queries or at least, in one query, select the second row for every deviceId from query 1?
Thanks
You can try using row_number()
select * from
(
SELECT
id,
deviceId,
position_x, position_y,row_number() over(partition by deviceid order by id desc) as rn
FROM data
WHERE deviceId > 0
)A where rn=2
You can use a correlated subquery for this as well:
SELECT d.*
FROM data d
WHERE d.deviceId > 0 AND
d.id = (SELECT d2.id
FROM data d2
WHERE d2.deviceId = d.deviceId
ORDER BY d2.id DESC
LIMIT 1, 1
);
With an index on data(deviceId, id desc), you might be impressed at the performance.
How can I select a certain number of rows, based on a condition (using IN or any other conditional), and if the number of rows returned by the condition is less than LIMIT x then to select from the remainig rows until the LIMIT x is met?
So, if I have the following table:
id comment ord
1 ... null
2 ... 1
3 ... 2
4 ... null
then the result set should be, using LIMIT 3:
id comment ord
2 ... 1
3 ... 2
1 ... null
If the ord column is not null, then I want to select the respective row(s), if not, I want to select one or more from the rest of the rows, having ord NULL, until the LIMIT 3 condition is reached.
Another example, if I have the next table data:
id comment ord
1 ... 3
2 ... 1
3 ... 2
4 ... null
Then the result set should be
id comment ord
2 ... 1
3 ... 2
1 ... 3
I have tried this mysql code:
SELECT t.* FROM table t
WHERE
t.ord IN (SELECT t1.ord FROM table t1 WHERE t1.ord IS NOT NULL ORDER BY t1.ord ASC)
OR
t.id IN (SELECT t2.id FROM table t2 WHERE t2.ord IS NULL ORDER BY t2.id ASC)
LIMIT 3;
But I always get the rows that have ord NULL, even if I have some ord columns not null.
Any help please?
Put the check for whether ord is null in your ORDER BY:
SELECT *
FROM table
ORDER BY ord IS NULL, ord, id
LIMIT 3
ord IS NULL will be 0 for non-null ord, 1 for null ord, so this will put all the non-null rows first. Within those it will order by ord, then the remainder will be ordered by id.
Then LIMIT 3 will select the first 3 of all of these.
I have one table:
| ID | ADV_ID | USER_ID |
| 1 | 22 | NULL |
| 2 | 22 | 3 |
| 5 | 44 | NULL |
and now, I want to select row where adv_id = 22 and user_id = 3. If that row doesn't exist, I want to get row where adv_id = 22 and user_id is null.
I tried in that way:
SELECT * FROM `table` WHERE adv_id = 22 AND (user_id = 3 OR user_id is null)
but this query return two rows - with user_id = NULL and with user_id = 3. I want to get one row - with user_id = 3 or (if not exist), with user_id = NULL.
How I can do it in one query?
Thanks.
Use conditional aggregation:
SELECT t1.*
FROM yourTable t1
INNER JOIN
(
SELECT
ADV_ID,
CASE WHEN COUNT(CASE WHEN USER_ID = 3 THEN 1 END) > 0 THEN 3 END USER_ID
FROM yourTable
) t2
ON t1.ADV_ID = t2.ADV_ID AND
((t1.USER_ID IS NULL AND t2.USER_ID IS NULL) OR (t1.USER_ID = t2.USER_ID))
WHERE
t1.ADV_ID = 22;
Demo
For an explanation, the subquery I have aliased as t2 aggregates over the ADV_ID, and outputs the value 3 if that value occurs in one or more records, otherwise it outputs NULL. Then, we join this subquery back to your original table on the condition that both USER_ID values are NULL, or, if not, that the two USER_ID values match.
You may modify the demo to see that it generates the output you want for other inputs.
SELECT *
FROM test
WHERE ADV_ID IS NOT NULL AND USER_ID IS NOT NULL
UNION ALL
SELECT *
FROM test
WHERE USER_ID IS NULL AND NOT EXISTS (
SELECT 1
FROM test
WHERE ADV_ID IS NOT NULL AND USER_ID IS NOT NULL
)
Select all rows with the first condition: ADV_ID IS NOT NULL AND USER_ID IS NOT NULL
and then UNION ALL with the same table if the first condition is NOT EXISTS.
So we only get results if the first condition is not returned any rows.
The MySQL UNION ALL operator is used to combine the result sets of 2 or more SELECT statements.
try like that:
SELECT * FROM `table` t1 WHERE (t1.adv_id = 44)
AND ((t1.user_id = 3) OR
(NOT EXISTS (select * from `table` t2 where t2.adv_id=t1.adv_id and t2.user_id = 3) AND t1.user_id is null ))
DEMO
I have the following explain info:
1 PRIMARY <derived2> ALL NULL NULL NULL NULL 15
1 PRIMARY j eq_ref PRIMARY PRIMARY 4 tmp.sel_job_id 1
1 PRIMARY jd ref code code 768 test_query.j.j_code 2 Using index
2 DERIVED t index NULL date_2 9 NULL 15 Using where
4 DEPENDENT SUBQUERY jtl eq_ref Multiple_keys,tester_id Multiple_keys 8 test_query.t.id 1 Using where; Using index
3 DEPENDENT SUBQUERY jtl eq_ref Multiple_keys,tester_id Multiple_keys 8 test_query.t.id 1 Using where; Using index
T table has over 55k records and jtl table over 60k records, j table has over 100 records and jd 30 records.
My query is:
SELECT temp_tbl.*, j.name AS job_name, jd.code AS job_details_code FROM (
SELECT t.*, (
SELECT jtl.job_id FROM job_tester_link jtl
WHERE t.id = jtl.tester_id AND
t.id NOT IN (11220012, 11218876, 11340156) AND
jtl.job_id = '20' AND
t.active = 'Y' LIMIT 1
) sel_job_id
FROM tester t
WHERE (
SELECT jtl.job_id
FROM job_tester_link jtl
WHERE t.id = jtl.tester_id AND
t.id NOT IN (11220012, 11218876, 11340156) AND
jtl.job_id = '20' AND
t.active = 'Y'
LIMIT 1
) IS NOT NULL
ORDER BY t.date DESC
LIMIT 0,15
) temp_tbl
LEFT JOIN job j ON temp_tbl.sel_job_id = j.id
LEFT JOIN job_details jd ON j.j_code = jd.code;
My problem is the execution time (over 3 secs). How can this query be improved?
Thanks.
i have one table name as a_ad_display with following column
displayId | campaignId | displaytime(date)
1 | 2 | 2012-12-19 12:41:08
2 | 2 | 2012-12-19 01:41:08
3 | 1 | 2012-12-20 10:41:08
4 | 1 | 2012-12-20 02:41:08
5 | 1 | 2012-12-21 12:41:08
6 | 2 | 2012-12-21 01:41:08
7 | 1 | 2012-12-21 12:41:08
8 | 1 | 2012-12-21 02:41:08
there is a multiple entry with same campaignId should be there.now what i want is to first of all fetch the campaignId with the highest count in a_ad_display and for that campaignId i want to get total number of count within the date
eg..from above table i have 1 number campaignId with highest row count
so for 1 number campaignId i want to display records like this..
**Date** | **countPerDay**
2012-12-19 | 0
2012-12-20 | 2
2012-12-21 | 3
how can i achieve this in mysql database...hope anyone can guide me...
You can use this query
select
count(campaignId) as TCount,
DATE(displayTime) as `Date`
from a_ad_display
where campaignId = 1
group by DATE(displayTime)
Although it will not display 0.
EDIT :
OK This is modified and fetches your result.
select
date(a.displayTime) as `DisplayTime`,
ifnull(l.TCount,0) as TCount
from a_ad_display as a
left join (select
count(campaignId) as TCount,
displayTime
from a_ad_display
where campaignId = 1
group by DATE(displayTime)) as l
on date(a.displayTime) = date(l.displayTime)
group by DATE(a.displayTime)
Demo
MORE EDITS :
select
date(a.displayTime) as `DisplayTime`,
ifnull(l.TCount,0) as TCount
from a_ad_display as a
left join (select
count(campaignId) as TCount,
displayTime
from a_ad_display
where campaignId = 1
group by DATE(displayTime)) as l
on date(a.displayTime) = date(l.displayTime)
group by DATE(a.displayTime)
order by a.displaytime desc limit 5
This is how you can select latest 5 records. If you want to select the latest campaign you can use a subquery instead of 1 take a look at this query
select
date(a.displayTime) as `DisplayTime`,
ifnull(l.TCount,0) as TCount
from a_ad_display as a
left join (select
count(campaignId) as TCount,
displayTime
from a_ad_display
where campaignId = (select campaignId from a_ad_display group by campaignId order by count(campaignId) desc limit 1)
group by DATE(displayTime)) as l
on date(a.displayTime) = date(l.displayTime)
group by DATE(a.displayTime)
EDITS :
I have edited the query this will fetch higher occurance campaignId and fetch results accordingly
Demo
this will show 0 also
select
sum(case when campaignId = #inputCompainid then 1 else 0 end) as TCount,
DATE(displayTime) as `Date`
from a_ad_display
group by DATE(displayTime)
Following query gives u list of records for each campaignId
select campaignId,date(displaytime) as Date,count(*) as countPerDay from a_ad_display group by campaignId,date(displaytime);
if you need for specfic campaignId, use this :
select campaignId,date(displaytime) as Date,count(*) as countPerDay from a_ad_display group by campaignId,date(displaytime) having campaignId=1;
sory for asking this, but which one of this query is better??
1.the answer by Mr.raheel shan
EXPLAIN SELECT DATE(a.displayTime) AS `DisplayTime`,IFNULL(l.TCount,0) AS TCount
FROM a_ad_display AS a
LEFT JOIN (SELECT
COUNT(campaignId) AS TCount,
displayTime
FROM a_ad_display
WHERE campaignId = 1
GROUP BY DATE(displayTime)) AS l
ON DATE(a.displayTime) = DATE(l.displayTime)
GROUP BY DATE(a.displayTime);
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY <derived2> ALL 2
1 PRIMARY a ALL 8 Using temporary; Using filesort
2 DERIVED a_ad_display ALL 8 Using where; Using temporary; Using filesort
2.or using this one
EXPLAIN SELECT DATE(a_ad_display.`displaytime`) AS `DATE`,
COUNT(a.`campaignId`) AS `COUNT Per Day`
FROM a_ad_display
LEFT JOIN (SELECT `a_ad_display`.`displayId`,
`a_ad_display`.`campaignId`
FROM `a_ad_display`
WHERE `a_ad_display`.`campaignId`=1) a
ON (`a_ad_display`.`displayId`=a.`displayId`)
GROUP BY `a_ad_display`.`displaytime`;
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY a_ad_display ALL 8 Using temporary; Using filesort
1 PRIMARY <derived2> ALL 5
2 DERIVED a_ad_display ALL 8 Using where
thanks before & after :D
EDIT for highest campaignId occurance
SELECT DATE(a_ad_display.`displaytime`) AS `DATE`, COUNT(a.`campaignId`) AS `COUNT Per Day` FROM a_ad_display
LEFT JOIN (SELECT `a_ad_display`.`displayId`,`a_ad_display`.`campaignId` FROM `a_ad_display`
INNER JOIN (SELECT COUNT(`a_ad_display`.`campaignId`) AS `count`, `a_ad_display`.`campaignId`
FROM `a_ad_display`
GROUP BY `a_ad_display`.`campaignId`
ORDER BY `count` DESC
LIMIT 1) b ON (a_ad_display.`campaignId`=b.`campaignId`)
) a ON (`a_ad_display`.`displayId`=a.`displayId`)
GROUP BY `DATE`;
GROUP BY a_ad_display.displaytime; /* wrong in this statement ..., sorry*/
it should be working now ..... try again.
You can check in here http://sqlfiddle.com/#!2/bc75b/1, hope it's workin :D