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
Related
I have table like
SELECT id, name, date FROM `table` ORDER BY `date` DESC, id DESC
...
10 |a|2020-01-08 20:40:00
9 |b|2020-01-08 20:40:00
8 |c|2020-01-08 20:40:00
500 |d|2020-01-06 22:49:00
7 |e|2020-01-06 22:00:00
...
How to get next and previous of a record. (ex: i have info of a record with id = 8 then how to get a next record is 9 and a previous record is 500)
Method #1 (requires MySQL 8+):
SQL
-- Previous ID
WITH cte_desc AS (SELECT * FROM `table` ORDER BY `date` DESC, id DESC),
cte_r AS (SELECT * FROM `table` WHERE id = #r_id)
SELECT id AS prev_id
FROM cte_desc
WHERE `date` < (SELECT `date` FROM cte_r)
OR `date` = (SELECT `date` FROM cte_r) AND id < (SELECT id FROM cte_r)
LIMIT 1;
-- Next ID
WITH cte_asc AS (SELECT * FROM `table` ORDER BY `date`, id),
cte_r AS (SELECT * FROM `table` WHERE id = #r_id)
SELECT id AS next_id
FROM cte_asc
WHERE `date` > (SELECT `date` FROM cte_r)
OR `date` = (SELECT `date` FROM cte_r) AND id > (SELECT id FROM cte_r)
LIMIT 1;
where #r_id is set to the ID of the row you want to find the previous/next for = 8 in your example.
Explanation
Two Common Table Expressions are defined: cte_desc sorts the table and cte_r gets the current row. The query part then finds the top row for which either the date value is strictly less than that of the chosen row or for which it is equal but the id is strictly less.
Online Demo
Dbfiddle demo: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=5380e374f24243d578db28b9f89b9c8c
Method #2 (for earlier MySQL versions)
Similar to above - just slightly longer when there is no CTE support:
SQL
-- Previous ID
SELECT id AS prev_id
FROM (SELECT * FROM `table` ORDER BY `date` DESC, id DESC) sub
WHERE `date` < (SELECT `date` FROM `table` WHERE id = #r_id)
OR `date` = (SELECT `date` FROM `table` WHERE id = #r_id)
AND id < (SELECT id FROM `table` WHERE id = #r_id)
LIMIT 1;
-- Next ID
SELECT id AS next_id
FROM (SELECT * FROM `table` ORDER BY `date`, id) sub
WHERE `date` > (SELECT `date` FROM `table` WHERE id = #r_id)
OR `date` = (SELECT `date` FROM `table` WHERE id = #r_id)
AND id > (SELECT id FROM `table` WHERE id = #r_id)
LIMIT 1;
Online Demo
Rextester demo: https://rextester.com/MTW78358
Method #3 (Slower? See first comments):
-- Previous ID
SELECT id AS prev_id
FROM `table`
WHERE CONCAT(`date`, LPAD(id, 8, '0')) =
(SELECT MAX(CONCAT(`date`, LPAD(id, 8, '0')))
FROM `table`
WHERE CONCAT(`date`, LPAD(id, 8, '0')) < (SELECT CONCAT(`date`, LPAD(id, 8, '0'))
FROM `table`
WHERE id = #r_id));
-- Next ID
SELECT id AS next_id
FROM `table`
WHERE CONCAT(`date`, LPAD(id, 8, '0')) =
(SELECT MIN(CONCAT(`date`, LPAD(id, 8, '0')))
FROM `table`
WHERE CONCAT(`date`, LPAD(id, 8, '0')) > (SELECT CONCAT(`date`, LPAD(id, 8, '0'))
FROM `table`
WHERE id = #r_id));
Online Demo
Rextester demo: https://rextester.com/BSQQL24519
Explanation
The ordering is by date/time then by ID so to simplify the searching, these are concatenated into a single string - but there is the usual snag of a string ordering placing e.g. 10 after 1 rather than after 9. To overcome this, the IDs are padded with zeros up to the number of digits of the maximum integer in MySQL (4294967295) - using the LPAD function. Having done this groundwork, the previous row can then be found by looking for the largest one that is less than the one for the current id value using MAX and a subselect.
You will need to find first of all, current record's position available in the current ordered list, then you will be able to find the previous record as well as next record.
PREVIOUS RECORD
SELECT row_number, id,name,`date`
FROM (
SELECT #row := #row + 1 AS row_number, id,name,`date`
FROM `table` AS t
JOIN (SELECT #row := 0) r
ORDER BY `date` DESC, id DESC
) main
WHERE row_number = (
SELECT current_row - 1
FROM (
SELECT #curRow := #curRow + 1 AS current_row, t.id,t.name,t.`date`
FROM `table` AS t
JOIN (SELECT #curRow := 0) r
ORDER BY `date` DESC, id DESC
) t1
WHERE id = 8
);
NEXT RECORD
SELECT row_number, id,name,`date`
FROM (
SELECT #row := #row + 1 AS row_number, id,name,`date`
FROM `table` AS t
JOIN (SELECT #row := 0) r
ORDER BY `date` DESC, id DESC
) main
WHERE row_number = (
SELECT current_row + 1
FROM (
SELECT #curRow := #curRow + 1 AS current_row, t.id,t.name,t.`date`
FROM `table` AS t
JOIN (SELECT #curRow := 0) r
ORDER BY `date` DESC, id DESC
) t1
WHERE id = 8
);
Try this query:
SELECT MAX(a.id) AS id
FROM mytable a
JOIN (SELECT id,NAME,DATE FROM mytable WHERE id=8) b
ON a.id <> b.id AND a.date < b.date
UNION ALL
SELECT MIN(a.id) AS id
FROM mytable a
JOIN (SELECT id,NAME,DATE FROM mytable WHERE id=8) b
ON a.id > b.id AND a.date >= b.date;
Fiddle here : https://www.db-fiddle.com/f/gTv6Hyeq9opHW83r6Cxfck/4
Or you can use a variable to single define the value:
SET #val = 8;
SELECT MAX(a.id) AS id
FROM mytable a
JOIN (SELECT id,NAME,DATE FROM mytable WHERE id=#val) b
ON a.id <> b.id AND a.date < b.date
UNION ALL
SELECT MIN(a.id) AS id
FROM mytable a
JOIN (SELECT id,NAME,DATE FROM mytable WHERE id=#val) b
ON a.id > b.id AND a.date >= b.date;
if you know date value of record with id=8 you can query next:
SELECT * FROM table
WHERE id <> 8 AND date >= '2020-01-08 20:40:00'
ORDER BY `date` ASC, id ASC
LIMIT 1
and for previous one(inversed):
SELECT * FROM table
WHERE id <> 8 AND date <= '2020-01-08 20:40:00'
ORDER BY `date` DESC, id DESC
LIMIT 1
if you wish to get both with single query, you can use UNION: https://dev.mysql.com/doc/refman/8.0/en/union.html
If you have at least MySQL 8.0 you could use something like this:
SET #row_number = 0;
SET #target = 8;
WITH cte AS (
SELECT (#row_number:=#row_number + 1) AS num,
t.*
FROM (SELECT *
FROM table_name
ORDER BY date DESC) t
)
SELECT *
FROM cte
WHERE num BETWEEN (SELECT num-1 FROM cte WHERE id = #target)
AND (SELECT num+1 FROM cte WHERE id = #target);
The CTE gives you a row number ordered by the date column. The second part of the query pulls everything from teh CTE that has a row number within 1 of the target row you specified (in this case row id 8).
MySQL 8.0+ is required for CTEs. IF you do not have at least MySQL 8.0 you would have to use temp tables for this method.
Previous:
SELECT id, name, date FROM `table` ORDER BY `date` DESC, id DESC LIMIT 1
Next:
SELECT id, name, date FROM `table` ORDER BY `date` ASC, id ASC LIMIT 1
If you need both in a single query:
( SELECT id, name, date FROM `table` ORDER BY `date` DESC, id DESC LIMIT 1 )
UNION ALL
( SELECT id, name, date FROM `table` ORDER BY `date` ASC, id ASC LIMIT 1 )
If you are using 8.0, you can try this:
SELECT LEAD(id) OVER (ORDER BY id DESC) AS 'Next',id,LAG(id) OVER (ORDER BY id DESC) AS 'Previous'
FROM table
WHERE id = 8
ORDER BY `date` DESC, id DESC
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;
I have following table in MySql
Number
1
2
3
2
3
4
4
4
I would like to get numbers by count but with excluded highest and lowest count. This should be the result
Number Count
2 2
3 2
Please help me with this query.
Try this:
SELECT number, COUNT(*) cnt
FROM mytable
GROUP BY number
HAVING cnt <> (SELECT COUNT(*)
FROM mytable
GROUP BY number
ORDER BY COUNT(*) LIMIT 1)
AND
cnt <> (SELECT COUNT(*)
FROM mytable
GROUP BY number
ORDER BY COUNT(*) DESC LIMIT 1)
The two subqueries used in the HAVING clause return the minimum and maximum count. Hence the two predicates of the HAVING clause filter out the groups with the highest and lowest count.
A solution is using user variable and sub-query.
Query needed:
select Number, Count
from (
select Number, count(*) as Count, #rank := #rank+1 as rank
from t1 cross join (select #rank := 0) param
group by Number
order by Count ) t2
where rank not in (1, #rank);
Demo:
create table t1(Number int);
insert into t1 values(1),(2),(3),(2),(3),(4),(4),(4);
select Number, Count
from (
select Number, count(*) as Count, #rank := #rank+1 as rank
from t1 cross join (select #rank := 0) param
group by Number
order by Count ) t2
where rank not in (1, #rank);
Output:
mysql> select Number, Count
-> from (
-> select Number, count(*) as Count, #rank := #rank+1 as rank
-> from t1 cross join (select #rank := 0) param
-> group by Number
-> order by Count ) t2
-> where rank not in (1, #rank);
+--------+-------+
| Number | Count |
+--------+-------+
| 2 | 2 |
| 3 | 2 |
+--------+-------+
2 rows in set (0.00 sec)
select number,count(number)
from numbers
where number not in ((select max(number) from numbers),(select min(number) from numbers) )
group by number
sqlfiddle demo
First select all rows except the maximum and minimum numbers.
Then find the count(number) from the result set.
Query
select t.number, count(t.number) as `Count` from(
select * from your_table_name
where number not in (
select max(number) from your_table_name
)
and number not in (
select min(number) from your_table_name
)
)t
group by t.num;
SELECT column FROM tbl
LIMIT 10 OFFSET 20
SELECT MIN(taka) AS mintaka, MAX(taka) AS maxtaka
FROM (SELECT taka FROM tbl LIMIT 10 OFFSET 20);
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.
This question already has answers here:
How to get next/previous record in MySQL?
(23 answers)
Closed 4 years ago.
I have the following table, named Example:
id(int 11) //not autoincriment
value (varchar 100)
It has the following rows of data:
0 100
2 150
3 200
6 250
7 300
Note that id values are not contiguous.
I've written this SQL so far:
SELECT * FROM Example WHERE id = 3
However, I don't know how to get the value of previous id and value of the next id...
Please help me get previous value and next value if id = 3 ?
P.S.: in my example it will be: previous - 150, next - 250.
Select the next row below:
SELECT * FROM Example WHERE id < 3 ORDER BY id DESC LIMIT 1
Select the next row above:
SELECT * FROM Example WHERE id > 3 ORDER BY id LIMIT 1
Select both in one query, e.g. use UNION:
(SELECT * FROM Example WHERE id < 3 ORDER BY id DESC LIMIT 1)
UNION
(SELECT * FROM Example WHERE id > 3 ORDER BY id LIMIT 1)
That what you mean?
A solution would be to use temporary variables:
select
#prev as previous,
e.id,
#prev := e.value as current
from
(
select
#prev := null
) as i,
example as e
order by
e.id
To get the "next" value, repeat the procedure. Here is an example:
select
id, previous, current, next
from
(
select
#next as next,
#next := current as current,
previous,
id
from
(
select #next := null
) as init,
(
select
#prev as previous,
#prev := e.value as current,
e.id
from
(
select #prev := null
) as init,
example as e
order by e.id
) as a
order by
a.id desc
) as b
order by
id
Check the example on SQL Fiddle
May be overkill, but it may help you
please try this sqlFiddle
SELECT value,
(SELECT value FROM example e2
WHERE e2.value < e1.value
ORDER BY value DESC LIMIT 1) as previous_value,
(SELECT value FROM example e3
WHERE e3.value > e1.value
ORDER BY value ASC LIMIT 1) as next_value
FROM example e1
WHERE id = 3
Edit: OP mentioned to grab value of previous id and value of next id in one of the comments so the code is here SQLFiddle
SELECT value,
(SELECT value FROM example e2
WHERE e2.id < e1.id
ORDER BY id DESC LIMIT 1) as previous_value,
(SELECT value FROM example e3
WHERE e3.id > e1.id
ORDER BY id ASC LIMIT 1) as next_value
FROM example e1
WHERE id = 3
SELECT *,
(SELECT value FROM example e1 WHERE e1.id < e.id ORDER BY id DESC LIMIT 1 OFFSET 0) as prev_value,
(SELECT value FROM example e2 WHERE e2.id > e.id ORDER BY id ASC LIMIT 1 OFFSET 0) as next_value
FROM example e
WHERE id=3;
And you can place your own offset after OFFSET keyword if you want to select records with higher offsets for next and previous values from the selected record.
Here's my solution may suit you:
SELECT * FROM Example
WHERE id IN (
(SELECT MIN(id) FROM Example WHERE id > 3),(SELECT MAX(id) FROM Example WHERE id < 3)
)
Demo: http://sqlfiddle.com/#!9/36c1d/2
A possible solution if you need it all in one row
SELECT t.id, t.value, prev_id, p.value prev_value, next_id, n.value next_value
FROM
(
SELECT t.id, t.value,
(
SELECT id
FROM table1
WHERE id < t.id
ORDER BY id DESC
LIMIT 1
) prev_id,
(
SELECT id
FROM table1
WHERE id > t.id
ORDER BY id
LIMIT 1
) next_id
FROM table1 t
WHERE t.id = 3
) t LEFT JOIN table1 p
ON t.prev_id = p.id LEFT JOIN table1 n
ON t.next_id = n.id
Sample output:
| ID | VALUE | PREV_ID | PREV_VALUE | NEXT_ID | NEXT_VALUE |
|----|-------|---------|------------|---------|------------|
| 3 | 200 | 2 | 150 | 4 | 250 |
Here is SQLFiddle demo
This query uses a user defined variable to calculate the distance from the target id, and a series of wrapper queries to get the results you want. Only one pass is made over the table, so it should perform well.
select * from (
select id, value from (
select *, (#x := ifnull(#x, 0) + if(id > 3, -1, 1)) row from (
select * from mytable order by id
) x
) y
order by row desc
limit 3
) z
order by id
See an SQLFiddle
If you don't care about the final row order you can omit the outer-most wrapper query.
If you do not have an ID this has worked for me.
Next:
SELECT * FROM table_name
WHERE column_name > current_column_data
ORDER BY column_name ASC
LIMIT 1
Previous:
SELECT * FROM table_name
WHERE column_name < current_column_data
ORDER BY column_name DESC
LIMIT 1
I use this for a membership list where the search is on the last name of the member. As long as you have the data from the current record it works fine.