Mysql: Get next and previous in duplicated records - mysql

My database has two columns ID and Timestamp.
4 1597228600
8 1597228700
12 1597228700
11 1597228800
14 1597228800
9 1597228900
10 1597228900
1 1597228900
2 1597229000
I need to get next (previous) record of the given id and timestamp ordered by timestamp. If the timestamp has duplicates, the record with higher(lower) id should be returned.
In the example Next and Prev records of the 11(1597228800) are 14 and 12. Next and Prev records of the 14(1597228800) are 1 and 11.
I tried to use CASE condition with subquery, but this solution has issues
SELECT id
FROM tbl
WHERE timestamp_value >= '1597228800'
AND id > (case when ( SELECT min(id) min_id FROM tbl WHERE id > 11 AND timestamp_value = 1597228800) is null then 0 else 11 end)
ORDER BY timestamp_value
LIMIT 1

I think that this will do:
select t.*
from tablename t
cross join (select * from tablename where id = ?) i
where t.id in (
(
select id from tablename
where (id < i.id and timestamp = i.timestamp) or timestamp < i.timestamp
order by timestamp desc, id desc limit 1
),
(
select id from tablename
where (id > i.id and timestamp = i.timestamp) or timestamp > i.timestamp
order by timestamp, id limit 1
)
)
Replace ? with the id that you want to search for.
The 2 subqueries return the ids of the previous and the next id of ?.
See the demo.

Related

MySQL query to order by time and return n rows after specific id

I have a table in my database (versions: MariaDB 10.3.17, MySQL 5.7) as follows:
id name timestamp
-----------------------------
154875 AXC 154875869
362574 RTB 154875800
962548 MNV 154875969
365847 XRT 154875123
...
what I need:
sort the rows on timestamp descending
then return 24 rows after (below) where id=something
for example for id=962548, the first 3 rows of the expected output will be:
id name timestamp
-----------------------------
154875 AXC 154875869
362574 RTB 154875800
365847 XRT 154875123
how to implement it in MySQL?
Join the query that returns the row with id = something to the table under your conditions:
select t.*
from tablename t
inner join (select * from tablename where id = 365847) c
on t.timestamp < c.timestamp or (t.timestamp = c.timestamp and t.id < c.id)
order by t.timestamp desc, t.id desc
limit 24
but I'm not sure what you mean by below, so maybe you want the opposite order:
select t.*
from tablename t
inner join (select * from tablename where id = 365847) c
on t.timestamp > c.timestamp or (t.timestamp = c.timestamp and t.id > c.id)
order by t.timestamp desc, t.id desc
limit 24
You need to select elements that have the timestamp value greater than your id timestamp, using a query like this:
SELECT *
FROM table
WHERE timestamp>(select timestamp
from table
where id = 'current_id')
ORDER BY timestamp LIMIT 24;
I'd query it something like that:
SELECT * FROM tab
WHERE timestamp >= (SELECT timestamp FROM tab WHERE id = 154875)
AND id <> 154875
ORDER BY timestamp DESC, id DESC
LIMIT 2

How to select last and last but one records

I have a table with 3 columns id, type, value like in image below.
What I'm trying to do is to make a query to get the data in this format:
type previous current
month-1 666 999
month-2 200 15
month-3 0 12
I made this query but it gets just the last value
select *
from statistics
where id in (select max(id) from statistics group by type)
order
by type
EDIT: Live example http://sqlfiddle.com/#!9/af81da/1
Thanks!
I would write this as:
select s.*,
(select s2.value
from statistics s2
where s2.type = s.type
order by id desc
limit 1, 1
) value_prev
from statistics s
where id in (select max(id) from statistics s group by type) order by type;
This should be relatively efficient with an index on statistics(type, id).
select
type,
ifnull(max(case when seq = 2 then value end),0 ) previous,
max( case when seq = 1 then value end ) current
from
(
select *, (select count(*)
from statistics s
where s.type = statistics.type
and s.id >= statistics.id) seq
from statistics ) t
where seq <= 2
group by type

Can I replace this loop in a sql server query

I have a list of items, sorted by date descending, and it checks them like this:
counted = 0
DateToCheck = now
foreach(item)
{
if( abs(item.date - DateToCheck) > 14 days )
{
counted++
}
DateToCheck = item.date
}
The goal is to get a count of items on the list that did not occur within 14 days of the previous item.
The table is just a list of dates, like this:
index ItemDate
307000 2017-08-17
307001 2017-04-25
307002 2016-09-23
307003 2016-08-26
307004 2016-04-30
307005 2016-03-01
307006 2016-03-01
The result here should be a count of 6, the last one is ignored since it is within 14 days of the one before.
You can use this query.
DECLARE #item TABLE([index] int, [date] DATETIME)
INSERT INTO #item
VALUES( 307006 ,'2017-08-17'),
(307005 ,'2017-04-25'),
(307004 ,'2016-09-23'),
(307003 ,'2016-08-26'),
(307002 ,'2016-04-30'),
(307001 ,'2016-03-01'),
(307000 ,'2016-03-01')
SELECT
count(*)
FROM #item T1
OUTER APPLY (
SELECT TOP 1 *
FROM #item T2
WHERE T2.[index] < T1.[index]
ORDER BY T2.[index] DESC) T
WHERE DATEDIFF(DAY, T.[date], T1.[date]) > 14
You can use this query if you do not have a ID column. Use ID column directly if you have one.
;WITH TBL AS (
SELECT ROW_NUMBER() OVER(ORDER BY ItemDate ASC) Id, ItemDate FROM TABLE_NAME
)
SELECT COUNT(a.ItemDate) FROM TBL a INNER JOIN TBL b ON b.ID = a.ID + 1 WHERE DATEDIFF(d, a.CreatedOn, b.CreatedOn) > 14;
With ID column, query changes to
SELECT COUNT(a.ItemDate) FROM TABLE_NAME a INNER JOIN TABLE_NAME b ON b.ID = a.ID + 1 WHERE DATEDIFF(d, a.CreatedOn, b.CreatedOn) > 14;

Change parameter in MySQL request SELECT

I am trying to change 'from_user' with a parameter from the other table and it doesn't work but when I am using the same table it works like a charm:
SELECT from_user, message_contents, message_read, to_user, date
FROM table1
WHERE date IN (
SELECT MAX( date )
FROM table1
WHERE to_user = 1 GROUP BY from_user
)
ORDER BY from_user ASC , date DESC
but this one just show one record but not all latest ones:
SELECT table2.`display_name`, message_contents, message_read, to_user, date
FROM table1, table2
WHERE table1.`from_user` = table2.`ID`
AND date IN ( SELECT MAX( date )
FROM table1
WHERE to_user = 1 GROUP BY from_user
)
ORDER BY from_user ASC , date DESC
Can anybody help to change 'from_user' with table2.display_name parameter but to get all recent records from mySQL?
You are joining two tables on table1.from_user = table2.id
So, if you don't want that table two to affect the number of rows than you can make a query like this:
SELECT
table1.from_user,
table2.`display_name`,
message_contents,
message_read,
to_user,
date
FROM table1
LEFT JOIN table2 ON table1.`from_user` = table2.`ID`
WHERE
date IN (SELECT MAX(date) FROM table1 WHERE to_user = 1 GROUP BY from_user)
ORDER BY from_user ASC , date DESC
I added also table1.from_user on the select clause which will help you see the from users which don't have a display name.

MySql - How get value in previous row and value in next row? [duplicate]

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.