this code gets id and latest timestamps for each ID
select id, max(start_time) as start1, max(end_time) as end1 from table group by id
how do I get the second highest timestamps as start2 and end2?
One option uses correlated subqueries:
select
id,
max(start_time) as start1,
max(end_time) as end1,
(
select t1.start_time
from mytable t1
where t1.id = t.id
order by t1.start_time desc
limit 1, 1
) start2,
(
select t1.end_time
from mytable t1
where t1.id = t.id
order by t1.end_time desc
limit 1, 1
) end2
from mytable t
group by id
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
This is my table :
What I'm trying to do, is to take the last disponibility of a user, by caserne. Example, I should have this result :
id id_user id_caserne id_dispo created_at
31 21 12 1 2019-10-24 01:21:46
33 21 13 1 2019-10-23 20:17:21
I've tried this sql, but it does not seems to work all the times :
SELECT * FROM
( SELECT id, id_dispo, id_user, id_caserne, MAX(created_at)
FROM disponibilites GROUP BY id_user, id_caserne, id_dispo
ORDER BY created_at desc ) AS sub
GROUP BY id_user, id_caserne
What am I doing wrong ?
I would simply use filtering in the where clause using a correlated subquery:
select d.*
from disponibilites d
where d.created_at = (select max(d2.created_at)
from disponibilites d2
where d2.id_user = d.id_user
);
EDIT:
Based on your comments:
select d.*
from disponibilites d
where d.created_at = (select max(d2.created_at)
from disponibilites d2
where d2.id_user = d.id_user and
d2.id_caserne = d.id_caserne
where date(d2.created_at) = date(d.created_at)
);
You can use a correlated subquery, as demonstrated by Gordon Linoff, or a window function if your RDBMS supports it:
select * from (
select
t.*,
rank() over(partition by id_caserne, id_user order by created_at desc) rn
from disponibilites t
) x
where rn = 1
Another option is to use a correlated subquery without aggregation, only with a sort and limit:
select *
from mytable t
where created_at = (
select created_at
from mytable t1
where t1.id_user = t.id_user and t1.id_caserne = t.id_caserne
order by created_at desc
limit 1
)
With an index on (id_user, id_caserne, created_at), this should be a very efficient option.
you can join your max(created_date) to your original table
select t1.* from disponibilites t1
inner join
(select max(created_at), id_caserne, id
from disponibilites
group by id_caserne, id) t2
on t2.id = t1.id
I have the following select in my mysql database:
select t.col1, t.THE_DATE, (select ?? as PREVIOUS_DATE)
from DUMMY_TABLE t
order by date
What I am trying to achieve is have the 'PREVIOUS_DATE' contain the value of the previous row's 'THE_DATE' column if there is one.
So if DUMMY_TABLE has the data :
col1 THE_DATE
x 10-01-2010
x 10-01-2012
x 10-01-2009
my select should return
col1 THE_DATE PREVIOUS_DATE
x 10-01-2009
x 10-01-2010 10-01-2009
x 10-01-2012 10-01-2010
You need order by clause in subquery with limit clause :
select t.col1, t.the_date,
( select t1.the_date
from dummy_table t1
where t1.col = t.col and
t1.the_date < t.the_date
order by t1.the_date desc
limit 1
) as PREVIOUS_DATE
from dummy_table t
order by the_date;
Need assistance with simplifying this SQL query to possibly a single SELECT:
(SELECT * FROM `deals`
WHERE category_id = 1
ORDER BY id desc
LIMIT 10)
UNION
(SELECT * FROM `deals`
WHERE category_id = 2
ORDER BY id desc
LIMIT 10)
UNION
(SELECT * FROM `deals`
WHERE category_id = 4
ORDER BY id desc
LIMIT 10)
UNION
(SELECT * FROM `deals`
WHERE category_id = 5
ORDER BY id desc
LIMIT 10)
UNION
(SELECT * FROM `deals`
WHERE category_id = 6
ORDER BY id desc
LIMIT 10)
UNION
(SELECT * FROM `deals`
WHERE category_id = 8
ORDER BY id desc
LIMIT 10)
UNION
(SELECT * FROM `deals`
WHERE category_id = 9
ORDER BY id desc
LIMIT 10)
UNION
(SELECT * FROM `deals`
WHERE category_id = 10
ORDER BY id desc
LIMIT 10)
UNION
(SELECT * FROM `deals`
WHERE category_id = 17
ORDER BY id desc
LIMIT 10)
I've been told to try using GROUP BY and HAVING. However, any query I tried didn't work in the slightest...
Any help will be greatly appreciated!
EDIT - apologies, forgot to mention database engine is MySQL
You can condense this down with a window function to limit each group bucket to 10.
SELECT
*
FROM
(
SELECT
*,
ROW_NUMBER() OVER PARTITION BY(category_id ORDER BY id DESC) AS GroupOrder
FROM `deals`
WHERE category_id BETWEEN 1 AND 10
)AS X
WHERE
GroupOrder<=10
I'm not sure, I need to know if you need that limit of 10, is this like take top 10 of all those things?
if not then
SELECT * FROM `deals`
WHERE category_id between 0 and 10 or category_id=17
ORDER BY category_id asc, id desc
For older versions of MySQL without the windowing functions, here is the code.
SELECT T1.ID, T1.Category_ID, T1.Name
FROM (
SELECT #row_num := IF(#prev_value=concat_ws('',t.Category_ID),#row_num+1,1) AS RowNumber
,t.*
,#prev_value := concat_ws('',t.Category_ID)
FROM data t,
(SELECT #row_num := 1) x,
(SELECT #prev_value := '') y
ORDER BY t.Category_ID
) T1
WHERE T1.RowNumber < 10
AND T1.Category_ID IN (1,2,3,4,5,6,7,8,9,10)
You will need to add the necessary field names to the other select.
This uses the technique described here