简体   繁体   中英

SQLite select next and previous row based on a where clause

I want to be able to get the next and previous row using SQLite.

id  statusid  date
168 1   2010-01-28 16:42:27.167
164 1   2010-01-28 08:52:07.207
163 1   2010-01-28 08:51:20.813
161 1   2010-01-28 07:10:35.373
160 1   2010-01-27 16:09:32.550
 46 2   2010-01-30 17:13:45.750
145 2   2010-01-30 17:13:42.607
142 2   2010-01-30 16:11:58.020
140 2   2010-01-30 15:45:00.543

For example:

Given id 46 I would like to return ids 160 (the previous one) and 145 (the next one)

Given id 160 I would like to return ids 161 (the previous one) and 46 (the next one) etc...

Be aware that the data is ordered by statusId then dateCreated DESC and HAS to work using SQLite.

select * from @t order by statusId, dateCreated desc

Test data created in sql server...

set nocount on; set dateformat ymd;
declare @t table(id int, statusId int, dateCreated datetime)
insert into @t
select 168,1,'2010-01-28 16:42:27.167' union
select 164,1,'2010-01-28 08:52:07.207' union
select 163,1,'2010-01-28 08:51:20.813' union
select 161,1,'2010-01-28 07:10:35.373' union
select 160,1,'2010-01-27 16:09:32.550' union
select  46,2,'2010-01-30 17:13:45.750' union
select 145,2,'2010-01-30 17:13:42.607' union
select 142,2,'2010-01-30 16:11:58.020' union
select 140,2,'2010-01-30 15:45:00.543'

Using SQL server 2005+ this would be fairly trivial!

EDIT:

Here's the same test data script but for SQLite which is the focus of the question.

create table t (id int, statusId int, dateCreated datetime);
insert into t
select 168,1,'2010-01-28 16:42:27.167' union
select 164,1,'2010-01-28 08:52:07.207' union
select 163,1,'2010-01-28 08:51:20.813' union
select 161,1,'2010-01-28 07:10:35.373' union
select 160,1,'2010-01-27 16:09:32.550' union
select  46,2,'2010-01-30 17:13:45.750' union
select 145,2,'2010-01-30 17:13:42.607' union
select 142,2,'2010-01-30 16:11:58.020' union
select 140,2,'2010-01-30 15:45:00.543';

EDIT 2 Please note that the data is not a good example so I have change the id 146 to 46

This problem is a lot more complicated than it first appears. The two order by fields have to be handled separately and then combined with a union and grab the appropriate result. To get both previous and next, we need another union, so we end up with a union with sub-unions.

This works with the supplied data. I tested many inputs and got the right previous/next outputs. When using, make sure you get ALL instances of 146 to replace.

SELECT *
FROM
(
    SELECT  t1.*
    FROM    t t1,
            (
                SELECT  *
                FROM    t
                WHERE   id = 146
            ) t2
    WHERE   t1.statusid = t2.statusid
      AND   t1.dateCreated >= t2.dateCreated
      AND   t1.id <> 146

    UNION

    SELECT  t1.*
    FROM    t t1,
            (
                SELECT  *
                FROM    t
                WHERE   id = 146
            ) t2
    WHERE   t1.statusid < t2.statusid


    ORDER BY             
            t1.statusid DESC,
            t1.dateCreated 

    LIMIT 1
)

UNION

SELECT *
FROM 
(
    SELECT  t1.*
    FROM    t t1,
            (
                SELECT  *
                FROM    t
                WHERE   id = 146
            ) t2
    WHERE   t1.statusid = t2.statusid
      AND   t1.dateCreated <= t2.dateCreated
      AND   t1.id <> 146

    UNION

    SELECT  t1.*
    FROM    t t1,
            (
                SELECT  *
                FROM    t
                WHERE   id = 146
            ) t2
    WHERE   t1.statusid > t2.statusid


    ORDER BY             
            t1.statusid,
            t1.dateCreated DESC

    LIMIT 1
)

ORDER BY             
        statusid,
        dateCreated DESC
;
select id from theTable where id>@id order by id desc limit 1
union
select id from theTable where id<@id order by id desc limit 1

You might want to look into using SQLIte primitives such as rowid ( https://www.sqlitetutorial.net/sqlite-primary-key/ ) OR creating your own set of rowid's based on the order that you output (possibly based on a sub-select count(*) query).

You can then do a select based on rowid-1 or rowid+1.

EDIT: Some code. Mostly for my own reference.

-- create a new table with the desired order
DROP TABLE IF EXISTS tt;
CREATE TABLE tt AS
SELECT * FROM t ORDER BY statusId, dateCreated DESC;

-- remove all primary's (rowid's)
VACUUM;

-- create new table from ordered one, and add ascending rowid
DROP TABLE IF EXISTS ttt;
CREATE TABLE ttt AS
SELECT rowid, * FROM tt;

-- create a new table with the row before and after the desired row
-- still need to work out how to do this with a variable for easily changing the id
SELECT t1.*,t3.*,t4.*
FROM ttt t1
LEFT JOIN ttt t3 ON t3.rowid=((SELECT CAST(rowid AS INT) FROM ttt WHERE ttt.id=46)-1)
LEFT JOIN ttt t4 ON t4.rowid=((SELECT CAST(rowid AS INT) FROM ttt WHERE ttt.id=46)+1)
WHERE t1.id=46;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM