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
First of all, good news: I have only 1 table.
Now, I have 3 similar queries:
SELECT id FROM (
SELECT * FROM my_table
ORDER BY columnA
DESC LIMIT 600
) AS aliasA
WHERE (revenue > 10000);
SELECT id FROM (
SELECT * FROM my_table
ORDER BY columnB
DESC LIMIT 400, 999999999999
) AS aliasB
WHERE (revenue > 10000);
SELECT id FROM (
SELECT * FROM my_table
ORDER BY columnC
DESC LIMIT 800;
) AS aliasC
WHERE (revenue > 10000);
Notice the WHERE clause are the same.
Is there a way to combine these 3 queries so that I can search from the intersection of the 3 sub-queries (find rows that match all 3 sub-queries, and also my WHERE clause)?
By the way, if my single queries (before combining) can be simplified, please let me know.
Thanks!
SELECT ID FROM
(SELECT id FROM (
SELECT *
FROM my_table
WHERE (revenue > 10000)
ORDER BY columnA DESC
LIMIT 600
) AS aliasA) AS a
INNER JOIN (
SELECT id FROM (
SELECT *
FROM my_table
WHERE (revenue > 10000)
ORDER BY columnB DESC
LIMIT 400, 999999999999
) AS aliasB
)
AS b ON a.id = b.id
INNER JOIN (
SELECT id FROM (
SELECT *
FROM my_table
WHERE (revenue > 10000)
ORDER BY columnC DESC
LIMIT 800
) AS aliasC
)
AS c ON a.id = c.id AND b.id = c.id
then you can search this query
In your case, the best solution will be JOIN USING (id):
SELECT id
FROM (SELECT id FROM my_table ORDER BY columnA DESC LIMIT 600)
aliasA
JOIN (SELECT id FROM my_table ORDER BY columnB DESC LIMIT 400, 999999999999)
aliasB USING (id)
JOIN (SELECT id FROM my_table ORDER BY columnC DESC LIMIT 800)
aliasC USING (id)
JOIN my_table USING (id)
WHERE (revenue > 10000);
If you didn't have LIMIT in your subquery, you could have used id IN:
SELECT id FROM my_table
WHERE (
id IN (SELECT id FROM my_table ORDER BY columnA DESC LIMIT 600)
AND
id IN (SELECT id FROM my_table ORDER BY columnB DESC LIMIT 400, 999999999999)
AND
id IN (SELECT id FROM my_table ORDER BY columnC DESC LIMIT 800)
AND
revenue > 10000
)
However LIMIT is not supported in subquery, so you can't do it.
If you were using other SQLs, you could have used INTERSECT:
(SELECT id FROM my_table ORDER BY columnA DESC LIMIT 600)
INTERSECT
(SELECT id FROM my_table ORDER BY columnB DESC LIMIT 400, 999999999999)
INTERSECT
(SELECT id FROM my_table ORDER BY columnC DESC LIMIT 800)
INTERSECT
(SELECT id FROM my_table WHERE revenue > 10000)
However MySql doesn't support INTERSECT.
You can use INNER JOIN on key: id then put where clause at the end. All 3 queries is making use of revenue > 10000 and it can be combined in onenwhere clause since all revenue columnn comes from the same table.
select t1.id from (
SELECT id, revenue FROM (
SELECT * FROM my_table
ORDER BY columnA
DESC LIMIT 600
) AS aliasA) as t1
INNER JOIN (
SELECT id FROM (
SELECT * FROM my_table
ORDER BY columnB
DESC LIMIT 400, 999999999999
) AS aliasB) as t2 on t1.id= t2.id
INNER JOIN (
SELECT id, revenue FROM (
SELECT * FROM my_table
ORDER BY columnC
DESC LIMIT 800;
) AS aliasC) as t3 on t1.id =t3.id
WHERE (t1.revenue > 10000);
I need to find the cumulative sum for the following data:
Following query:
SELECT created, COUNT( * )
FROM `transactions`
GROUP BY created
Gives me:
created COUNT( * )
2015-8-09 1
2015-8-15 1
2015-8-16 2
2015-8-17 1
2015-8-23 1
I tried to do the cumulative sum like:
SELECT t1.created, COUNT( * ) , SUM( t2.totalcount ) AS sum
FROM transactions t1
INNER JOIN (
SELECT id, created c, COUNT( * ) AS totalcount
FROM transactions
GROUP BY created
ORDER BY created
)t2 ON t1.id >= t2.id
GROUP BY t1.created
ORDER BY t1.created
but the results it gives arent as expected:
created COUNT( * ) sum
2015-8-09 5 6
2015-8-15 3 4
2015-8-16 6 8
2015-8-17 1 1
2015-8-23 4 5
How do i produce the following result:
created COUNT( * ) sum
2015-8-09 1 1
2015-8-15 1 2
2015-8-16 2 4
2015-8-17 1 5
2015-8-23 1 6
select tmp.*, #sum := #sum + cnt as cum_sum
from
(
SELECT created, COUNT( * ) as cnt
FROM `transactions`
GROUP BY created
ORDER BY created
) tmp
cross join (select #sum := 0) s
Your inner query is selecting id without grouping on it. Let's rework it in terms of the date.
SELECT t1.created, COUNT( * ) AS daycount, SUM( t2.totalcount ) AS sum
FROM transactions t1
INNER JOIN ( SELECT created, COUNT( * ) AS totalcount
FROM transactions
GROUP BY created
) t2 ON t1.created >= t2.created
GROUP BY t1.created
ORDER BY t1.created;
Or you might want to put the totalcount inline:
SELECT t1.created, COUNT(*) AS daycount
, ( SELECT COUNT(*) FROM transactions t2
WHERE t2.created <= t1.created ) AS totalcount
FROM transactions t1
GROUP BY created
ORDER BY CREATED;
I have this table e.g.:
Id StatusDate Status
1 20-08-2014
1 15-08-2014
1 09-08-2014 P
2 17-08-2014
1 10-08-2014
2 12-08-2014
2 06-07-2014 P
1 30-07-2014
2 02-07-2014
2 01-07-2014 P
...... and so on
I want to select count by ID where status is blank until I hit the first 'P' in ascending order of date group by ID. So my results will be like this.
ID Count
1 3
2 2
Try it out. Not tested
SELECT t1.ID, count(*) FROM table t1
WHERE t1.StatusDate >= (SELECT MAX(t2.StatusDate) FROM table t2
WHERE t1.ID = t2.ID AND t2.Status = 'P')
GROUP BY t1.ID
Assuming your table name is StatusTable This will work:
SELECT
ID,
COUNT(*) AS `Count`
FROM StatusTable AS st
WHERE
st.Status = ''
AND st.StatusDate > (
SELECT st2.StatusDate
FROM `StatusTable` AS st2
WHERE st.ID = st2.ID
AND st2.Status = 'P'
ORDER BY st2.StatusDate DESC
LIMIT 1
)
GROUP BY st.ID
ORDER BY st.ID
One option is to use a JOIN and COUNT rows which have a lower statusdate value, like this:
SELECT t1.id, SUM(CASE WHEN t1.statusdate > t2.statusdate THEN 1 ELSE 0 END) AS mycount
FROM t t1 JOIN (
SELECT id, MIN(statusdate) statusdate
FROM t
WHERE status = 'P'
GROUP BY id
) t2
ON t1.id = t2.id
GROUP BY t1.id
Working Demo: http://sqlfiddle.com/#!2/d9d91/2