Select the distinct latest 2 rows based on the timestamp column - mysql

I have a table like below. I want to extract the latest(based on time) 2 rows having same id. If no rows are same do not return anything. Then subtract the values of the latest row with the second latest and return a table with the ID and the value result.
Below is the table. 1st column is the id. Second is the value, third is the time. Id is not primary or unique
Id value time
3 2 2019-01-11 18:59:07.403
2 7 2019-01-10 18:58:40.400
4 5 2019-01-12 18:58:42.400
2 2 2019-01-11 18:59:23.147
5 -5 2019-01-12 18:58:42.400
3 8 2019-01-12 18:59:27.670
2 5 2019-01-12 18:59:43.777
The result should be
id value
2 3
3 6

One possible solution uses aggregation to get the IDs which occur more than once and correlated subqueries with ORDER BY and LIMIT to get the latest and second latest value.
SELECT x.id,
(SELECT t.value
FROM elbat t
WHERE t.id = x.id
ORDER BY t.time DESC
LIMIT 0, 1)
-
(SELECT t.value
FROM elbat t
WHERE t.id = x.id
ORDER BY t.time DESC
LIMIT 1, 1) value
FROM (SELECT t.id
FROM elbat t
GROUP BY t.id
HAVING count(*) > 1) x;
db<>fiddle

In MySQL 8+, you can use window functions and conditional aggregation
select t.id,
sum(case when seqnum = 1 then value else - value end) as diff
from (select t.*,
row_number() over (partition by id order by time desc) as seqnum
from elbat t
) t
where seqnum in (1, 2)
group by id
order by max(time) desc
limit 2;
The same idea can be adapted to earlier versions, using variables:
select t.id,
sum(case when seqnum = 1 then value else - value end) as diff
from (select t.*,
(#rn := if(#i = id, #rn + 1,
if(#i := id, 1, 1)
)
) as seqnum
from (select t.* from elbat t order by id, time desc) t cross join
(select #i := -1, #rn := 0) params
) t
where seqnum in (1, 2)
group by id
order by max(time) desc
limit 2;

Related

Find the most frequent value in mysql,display all in case of a tie

For example, we have 1, 2 and 3 are the most frequent values at the same time, how to return them when it is a tie?
id
1
1
1
2
2
2
3
3
3
4
You could try:
SELECT id
FROM yourTable
GROUP BY id
HAVING COUNT(*) = (SELECT COUNT(*) FROM yourTable
GROUP BY id ORDER BY COUNT(*) DESC LIMIT 1);
On more recent versions of MySQL 8+, we can use RANK here:
WITH cte AS (
SELECT id, RANK() OVER (ORDER BY COUNT(*) DESC) rnk
FROM yourTable
GROUP BY id
)
SELECT id
FROM cte
WHERE rnk = 1;

How to get all rows with second highest value

I have this following table:
name value year
A 1 2015
A 2 2014
A 3 2013
B 1 2015
B 3 2013
C 1 2015
C 2 2014
How can I get, for each name, the row with the second highest year, like this:
name value year
A 2 2014
B 3 2013
C 2 2014
I tried the following query but no success:
select name, value, year
from TABLE_NAME
WHERE year IN (select year from TABLE_NAME order by year desc limit 1,1)
The previous query gives me this error:
"SQL Error (1235): This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery' "
And I can't change the MySQL version (5.6.25) right now, because the solution is already in production.
Any help, please?
One way to solve n per group in MySQL is to simulate ROW_NUMBER. Note that this will only return one value per name.
SELECT
name,
value,
year
FROM
(SELECT
t.name,
t.value,
t.year,
#rn := if(#prev = t.name, #rn + 1,1) as rn,
#prev:=t.name
FROM
test_table as t
JOIN (SELECT #Prev:= Null, #Rn := 0) as v
ORDER BY
t.name,
T.year desc) as t
WHERE
rn = 2;
How this works.
SELECT #Prev:= Null, #Rn := 0 initializes two variables #Prev and #Rn.
#rn := if(#prev = t.name, #rn + 1,1) as rn set the variable of #rn to either 1 or #rn + 1 depending on if #prev = t.Name and returns the value of #rn as the column rn
#prev:=t.name sets the value of #prev equal to the current value of name
if you run
SELECT
t.name,
t.value,
t.year,
#prev = t.name as eval,
#rn := if(#prev = t.name, #rn + 1,1) as rn,
#prev:=t.name as prev
FROM
test_table as t
JOIN (SELECT #Prev:= Null, #Rn := 0) as v
ORDER BY
t.name,
T.year desc
I would expect something like
name value year eval rn prev
A 1 2015 false 1 null
A 2 2014 true 2 A
A 3 2013 true 3 A
B 1 2015 false 1 A
B 3 2013 true 2 B
C 1 2015 false 1 B
C 2 2014 true 2 C
Wrapping into a subquery and the filtering for rn=2 gives you the desired result
My strategy is to use a grouping to find the highest years. Then join with the original table to remove the highest years. Finally do a grouping on the combined table to find the second highest year for each name. (If you need value you can do an INNER JOIN with the original table to find it.)
SELECT name, MAX(year)
FROM
(SELECT name, year
FROM TABLE_NAME) AS x1
INNER JOIN
(SELECT name, MAX(year) AS year
FROM TABLE_NAME
GROUP BY name, year
) AS x2
ON x1.name = x2.name AND x1.year <> x2.year
GROUP BY name
ORDER BY name ASC ;
Try this :
select * from test_table where year = (select distinct year from test_table order by year desc limit 1,1)
It should work if you rewrite it as a join:
select b.name, b.value, b.year
from (select year
from table_name
order by year desc
limit 1, 1) a
join table_name b
on b.year = a.year

mysql - Get top 5 records for each group of grouped results

select *,COUNT(feed_id) from
(SELECT feed_contents.*, feed.feed_url, feed.lang_direction, feed.feed_title
FROM feed_contents
INNER JOIN feed ON feed_contents.feed_id = feed.feed_id
INNER JOIN user_feeds ON feed_contents.feed_id = user_feeds.feed_id
WHERE user_feeds.user_id = 13
AND DATE(feed_contents.content_date) >= CURDATE() - INTERVAL 90 DAY
ORDER BY feed_contents.content_date desc) as tbl
group by feed_id
order by content_date desc
limit 0,20
i have this query to get results from multiple tables,
in result it returning one record against feed_id,
i want 5 records against each feed_is
output is like
http://screencast.com/t/HHxNOOSdSX4S
i want max 5 from each
You can use variables for this:
SELECT feed_id, content_date, feed_url, lang_direction, feed_title
FROM (
SELECT feed_id, content_date, feed_url, lang_direction, feed_title,
#rn := IF(#fid = feed_id, #rn + 1,
IF(#fid := feed_id, 1, 1)) AS rn
FROM (
SELECT feed_contents.feed_id,
feed_contents.content_date,
feed.feed_url,
feed.lang_direction,
feed.feed_title
FROM feed_contents
INNER JOIN feed ON feed_contents.feed_id = feed.feed_id
INNER JOIN user_feeds ON feed_contents.feed_id = user_feeds.feed_id
WHERE user_feeds.user_id = 13 AND
DATE(feed_contents.content_date) >= CURDATE() - INTERVAL 90 DAY) AS tbl
CROSS JOIN (SELECT #rn := 0, #fid := 0) AS vars
ORDER BY feed_id, content_date DESC) AS s
WHERE s.rn <= 5
Variable #rn is used to enumerate records within each feed_id partition. Once feed_id value changes, #rn is being reset to 1, so as to start counting for the next partition. Records are numbered in descending order according to field content_date.

SELECT Current and Previous row WHERE condition

id value
---------
1 a
2 b
3 c
4 a
5 t
6 y
7 a
I want to select all rows where the value is 'a' and the row before it
id value
---------
1 a
3 c
4 a
6 y
7 a
I looked into
but I want to get all such rows in one query.
Please help me start
Thank you
I think the easiest way might be to use variables:
select t.*
from (select t.*,
(rn := if(value = 'a', 1, #rn + 1) as rn
from table t cross join
(select #rn := 0) params
order by id desc
) t
where rn in (1, 2)
order by id;
An alternative method uses a correlated subquery to get the previous value and then uses this in the where clause:
select t.*
from (select t.*,
(select t2.value
from table t2
where t2.id < t.id
order by t2.id desc
limit 1
) as prev_value
from table t
) t
where value = 'a' or prev_value = 'a';
With an index on id, this might even be faster than the method using variables.

Select from a select statement from a defined value

I've the following table structure:
id |name |date
1 a 2012-01-01
2 a 2011-01-01
3 a 2010-01-01
4 a 2014-01-01
5 a 2011-01-01
I'd like to perform a select order by date (desc), and after select the first 3 rows from the results by a condition which would be where id = 1. So the second part of the query would be "give me the first 3 rows starting from the row whose id equals to 1"
EDIT:
After the first "part" the result would be:
SELECT id, name, date FROM table ORDER BY date DESC
id |name |date
4 a 2014-01-01
1 a 2012-01-01
2 a 2011-01-01
5 a 2011-01-01
3 a 2010-01-01
After the second part it should look like this (so the first 3 after the row whose id is 1):
id |name |date
2 a 2011-01-01
5 a 2011-01-01
3 a 2010-01-01
I have no any idea how could I solve it, please help me.
EDIT:
This is the concrete code I'd like to re-write:
SELECT `id`, `questions`.`userid`, `categories`.`name`, `user`.`username`, `title`,
`details`, `date` FROM `questions`
LEFT JOIN `user`
ON `questions`.`userid` = `user`.`userid`
LEFT JOIN `categories`
ON `questions`.`categoryid` = `categories`.`categoryid`
ORDER BY `date` DESC LIMIT 10
SELECT *
FROM table
WHERE date < (SELECT date FROM table WHERE id = 1)
ORDER BY date DESC
LIMIT 3
This isn't pretty because MySQL doesn't support row_number() or common table expressions, but it should work. Basically, get the row number ordered by the date, then select those whose row number is greater than an arbitrary value (in this case 1). Finally use limit to select the number of records you want.
SELECT id, name, mydate
FROM (
SELECT id, name, mydate, #rn:=#rn+1 rn
FROM mytable, (select #rn:=0) t
ORDER BY mydate DESC
) t2
WHERE rn > (
select rn
from (
SELECT id, name, mydate, #rn:=#rn+1 rn
FROM mytable, (select #rn:=0) t
ORDER BY mydate DESC
) t2
where id = 1
)
LIMIT 3
SQL Fiddle Demo
This is what you want to do... if finds the first id thats equal to 4 and then selects those out. then limit the offset to go to the next row and pull out 3
SELECT id, name, m_date from(
SELECT id, name, m_date, #a := id, if(#a = 4, #b := 1, #b) AS join_id
FROM test
join(SELECT #a := 0, #b := 0) t
ORDER BY m_date DESC
) AS tt
WHERE join_id = 1
LIMIT 1,3
SELECT temp.`id`, temp.`userid`, `categories`.`name`, `user`.`username`, temp.`title`,
temp.`details`, temp.`date` FROM (
SELECT `id`, `categoryid`, `details`, `title`, `userid`, `date`, #a := id, if(#a = 11, #b := 1, #b) AS join_id
FROM `questions`
join(SELECT #a := 0, #b := 0) t
ORDER BY `date` DESC
) as temp
LEFT JOIN `user`
ON temp.`userid` = `user`.`userid`
LEFT JOIN `categories`
ON temp.`categoryid` = `categories`.`categoryid`
WHERE join_id = 1
LIMIT 1,10;
SEE FIDDLE for clarification