SQLite select next and previous row based on a where clause - sql

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;

Related

SQL Server find consecutive failure records

I've look at so many other questions and nothing quite fits my question or gets me the answer I need, maybe I'm just slow today :(
DECLARE #t TABLE (
[InstructionId] INT,
[InstructionDetailId] INT,
[Sequence] INT,
[Status] INT
)
INSERT INTO #t SELECT 222,111,1, 2
INSERT INTO #t SELECT 222,112,2,2
INSERT INTO #t SELECT 222,113,3,4
INSERT INTO #t SELECT 222,114,4,4
INSERT INTO #t SELECT 222,115,5,2
INSERT INTO #t SELECT 222,116,6,4
INSERT INTO #t SELECT 222,117,7,2
INSERT INTO #t SELECT 222,118,8,4
INSERT INTO #t SELECT 222,119,9,4
INSERT INTO #t SELECT 222,120,10,2
INSERT INTO #t SELECT 222,121,11,2
I need to find for which InstructionDetailId's there are consecutive failures (Status = 4) by using the [Sequence] field for checking the order to determine if they are consecutive. So for the above InstructionDetailId 113 and 114 would be consecutive failures as their [Sequence] is 3 & 4, same for InstructionDetailId 118 and 119 would be consecutive failures. I've tried so many row number variation and cte's and I can't quite crack it :( This is for SQL Server 2008 R2 by the way.
Expected output:
InstructionId InstructionDetailId Sequence Status
222 113 3 4
222 114 4 4
222 118 8 4
222 119 9 4
Thanx all!
Perhaps the simplest method is to use lag() and lead():
select t.*
from (select t.*,
lag(t.status) over (partition by t.InstructionId order by t.sequence) as prev_status,
lead(t.status) over (partition by t.InstructionId order by t.sequence) as next_status
from #t t
) t
where status = prev_status or status = next_status;
You can use APPLY :
select t.*
from #t t outer apply
( select top (1) t1.*
from #t t1
where t1.InstructionId = t.InstructionId and
t1.Sequence < t.Sequence
order by t1.Sequence desc
) t1 outer apply
( select top (1) t2.*
from #t t2
where t2.InstructionId = t.InstructionId and
t2.Sequence > t.Sequence
order by t2.Sequence
) t2
where t.status = 4 and (t.status = t1.status or t.status = t2.status);
Could you do something like...
select t1.InstructionID,
t1.InstructionDetailID,
t1.Sequence,
t1.Status,
from #t t1
inner join #t t2 on t1.InstructionID = t2.InstructionID
and t1.Sequence = (t2.Sequence - 1)
and t1.Status = t2.Status
and t1.Status = 4

Attain value present in earlier set but missing in later set

I can't get my head around why this is so difficult to achieve!
All I want to do is retrieve a value from an earlier set of results that does not exist in a later set of results from within the same table.
This is what I'm trying to achieve:
select b.Id
from table a
where
not exists
(select b.Id from table b where b.Id = a.Id and
b.time = (select max(time) from table where time < a.time))
I know this is not syntactically correct, the difficulty is accessing the b.Id while maintaining a reference to the other set's time and it's an issue in every form I've tried, such as using except. I've also tried outer joins to the same table to try to find the missing Id but to no avail.
I found it easy to find the Id of a missing row present in the later set and not the previous, but not the other way around.
I'm using SQL Server 2008R2.
EDIT
This is hard to pen so I'll give an example:
Table
- PK - Id - Time
------------------
- 1 - 100 - 13:00
- 2 - 99 - 13:00
- 3 - 100 - 11:00
- 4 - 99 - 11:00
- 5 - 98 - 11:00
- 6 - 100 - 10:00
- 7 - 99 - 10:00
- 8 - 98 - 10:00
- 9 - 97 - 10:00
So the result I expect would be rows:
{ 5, 98, 13:00 } and { 9, 97, 11:00 } as thse IDs are missing from the set that comes before. Note the time is from the later set, to show that they're missing from that set of time.
I would consider doing this with group by and having. If I understand correctly:
select t.id
from table t
group by t.id
having max(t.time) < #LaterTime and
sum(case when t.time >= #EarlierTime then 1 else 0 end) > 0;
Try to use table variables.
DECLARE #table1 TABLE (column1, column2, column3)
DECLARE #table2 TABLE (column1, column2, column3)
SELECT INTO #table1 (column1, column2, column3)
VALUES
FROM IntendedTable
WHERE Date < DesiredDate
SELECT INTO #table1 (column1, column2, column3)
VALUES
FROM IntendedTable
WHERE Date > DesiredDate
SELECT *
FROM #table1
WHERE NOT EXISTS (SELECT * FROM #table2)
The above isn't syntactically correct, but it would likely be the approach I would take.
Ids having exactly one time, the first one
select *
from table
where id in (
select t1.id
from table t1
group by t1.id
having count(distinct t1.time) = 1 )
and time = (select min(t2.time) from table t2)
EDIT
Following OP comments, all missing rows above min time for the id
select id,time
from (
select t1.id,t2.time
from
(select distinct id from table ) t1
cross join
(select distinct time from table) t2
except
select distinct id,time
from table
) tt
where time > (select min(t3.time) from table t3 where t3.id=tt.id)
I think this is what you want. Gives expected result.
CREATE TABLE #T (PK INT , ID INT , [TIME] TIME)
INSERT INTO #T
SELECT 1 ,100 , '13:00' UNION ALL
SELECT 2 ,99 , '13:00' UNION ALL
SELECT 3 ,100 , '11:00' UNION ALL
SELECT 4 ,99 , '11:00' UNION ALL
SELECT 5 ,98 , '11:00' UNION ALL
SELECT 6 ,100 , '10:00' UNION ALL
SELECT 7 ,99 , '10:00' UNION ALL
SELECT 8 ,98 , '10:00' UNION ALL
SELECT 9 ,97 , '10:00'
;WITH R
AS
(
SELECT DENSE_RANK() OVER (ORDER BY T1.TIME DESC) RANK,*
FROM #T T1
)
SELECT DISTINCT R1.PK, R1.ID, R2.TIME
FROM R R1
INNER JOIN R R2 ON R1.RANK = R2.RANK + 1
WHERE
NOT EXISTS
(
SELECT ID, TIME FROM R R2 WHERE R1.RANK = R2.RANK + 1 AND R1.ID = R2.ID
)

Is there something equivalent to putting an order by clause in a derived table?

This is sybase 15.
Here's my problem.
I have 2 tables.
t1.jobid t1.date
------------------------------
1 1/1/2012
2 4/1/2012
3 2/1/2012
4 3/1/2012
t2.jobid t2.userid t2.status
-----------------------------------------------
1 100 1
1 110 1
1 120 2
1 130 1
2 100 1
2 130 2
3 100 1
3 110 1
3 120 1
3 130 1
4 110 2
4 120 2
I want to find all the people who's status for THEIR two most recent jobs is 2.
My plan was to take the top 2 of a derived table that joined t1 and t2 and was ordered by date backwards for a given user. So the top two would be the most recent for a given user.
So that would give me that individuals most recent job numbers. Not everybody is in every job.
Then I was going to make an outer query that joined against the derived table searching for status 2's with a having a sum(status) = 4 or something like that. That would find the people with 2 status 2s.
But sybase won't let me use an order by clause in the derived table.
Any suggestions on how to go about this?
I can always write a little program to loop through all the users, but I was gonna try to make one horrendus sql out of it.
Juicy one, no?
You could rank the rows in the subquery by adding an extra column using a window function. Then select the rows that have the appropriate ranks within their groups.
I've never used Sybase, but the documentation seems to indicate that this is possible.
With Table1 As
(
Select 1 As jobid, '1/1/2012' As [date]
Union All Select 2, '4/1/2012'
Union All Select 3, '2/1/2012'
Union All Select 4, '3/1/2012'
)
, Table2 As
(
Select 1 jobid, 100 As userid, 1 as status
Union All Select 1,110,1
Union All Select 1,120,2
Union All Select 1,130,1
Union All Select 2,100,1
Union All Select 2,130,2
Union All Select 3,100,1
Union All Select 3,110,1
Union All Select 3,120,1
Union All Select 3,130,1
Union All Select 4,110,2
Union All Select 4,120,2
)
, MostRecentJobs As
(
Select T1.jobid, T1.date, T2.userid, T2.status
, Row_Number() Over ( Partition By T2.userid Order By T1.date Desc ) As JobCnt
From Table1 As T1
Join Table2 As T2
On T2.jobid = T1.jobid
)
Select *
From MostRecentJobs As M2
Where Not Exists (
Select 1
From MostRecentJobs As M1
Where M1.userid = M2.userid
And M1.JobCnt <= 2
And M1.status <> 2
)
And M2.JobCnt <= 2
I'm using a number of features here which do exist in Sybase 15. First, I'm using common-table expressions both for my sample data and clump my queries together. Second, I'm using the ranking function Row_Number to order the jobs by date.
It should be noted that in the example data you gave, no user satisfies the requirement of having their two most recent jobs both be of status "2".
__
Edit
If you are using a version of Sybase that does not support ranking functions (e.g. Sybase 15 prior to 15.2), then you need simulate the ranking function using Counts.
Create Table #JobRnks
(
jobid int not null
, userid int not null
, status int not null
, [date] datetime not null
, JobCnt int not null
, Primary Key ( jobid, userid, [date] )
)
Insert #JobRnks( jobid, userid, status, [date], JobCnt )
Select T1.jobid, T1.userid, T1.status, T1.[date], Count(T2.jobid)+ 1 As JobCnt
From (
Select T1.jobid, T2.userid, T2.status, T1.[date]
From #Table2 As T2
Join #Table1 As T1
On T1.jobid = T2.jobid
) As T1
Left Join (
Select T1.jobid, T2.userid, T2.status, T1.[date]
From #Table2 As T2
Join #Table1 As T1
On T1.jobid = T2.jobid
) As T2
On T2.userid = T1.userid
And T2.[date] < T1.[date]
Group By T1.jobid, T1.userid, T1.status, T1.[date]
Select *
From #JobRnks As J1
Where Not Exists (
Select 1
From #JobRnks As J2
Where J2.userid = J1.userid
And J2.JobCnt <= 2
And J2.status <> 2
)
And J1.JobCnt <= 2
The reason for using the temp table here is for performance and ease of reading. Technically, you could plug in the query for the temp table into the two places used as a derived table and achieve the same result.

Display only the first row per match

I have a table (AreaPartners), and I want to match only the first "Name" record in each group, order by "ID", grouped by "Area". So for the table below:
Area Name ID
AB ISmith 748
AB AWood 750
AB HArcher 751
AB DMunslow 753
AB DCornelius 754
BH MLee 301
BH NMcClean 307
BH DMiles 309
BH LPayze 325
BH MPinnock 329
I'd want to return the results ISmith for AB and MLee for BH.
How do I go about doing this? I believe it's something to do with the Group By function, but I can't for the life of me get it to work.
Try this:
SELECT yourTable.Area, yourTable.Name
FROM yourTable INNER JOIN (
SELECT MIN(Id) AS MinId
FROM yourTable
GROUP BY Area) M ON yourTable.Id = M.MinId
Update because of comment (There is no table variable and Partition over is not a MS access statement). You can also do it with an IN statement:
SELECT
yourTable.Area,
yourTable.Name
FROM yourTable
WHERE yourTable.Id IN
(
SELECT
MIN(tbl.Id) AS MinId
FROM
yourTable as tbl
GROUP BY
tbl.Area
)
In MSSQL you can write this:
DECLARE #tbl TABLE
(
Area VARCHAR(100),
Name VARCHAR(100),
ID INT
)
INSERT INTO #tbl
SELECT 'AB','ISmith',748
UNION ALL
SELECT 'AB','AWood',750
UNION ALL
SELECT 'AB','HArcher',751
UNION ALL
SELECT 'AB','DMunslow',753
UNION ALL
SELECT 'AB','DCornelius',754
UNION ALL
SELECT 'BH','MLee',301
UNION ALL
SELECT 'BH','NMcClean',307
UNION ALL
SELECT 'BH','DMiles',309
UNION ALL
SELECT 'BH','LPayze',325
UNION ALL
SELECT 'BH','MPinnock',325
;WITH CTE
AS
(
SELECT
RANK() OVER(PARTITION BY tbl.Area ORDER BY ID) AS iRank,
tbl.ID,
tbl.Area,
tbl.Name
FROM
#tbl AS tbl
)
SELECT
*
FROM
CTE
WHERE
CTE.iRank=1

SQL - order by list order

I have the following query that returns rows based on a comma seperated list
Select * from Table where RecordID in (22,15,105,1,65,32)
I would like the results of this query to return to in the order of the ID's in the list. Is that possible with SQL?
Thanks in advance
select * from Table
where RecordID in (22,15,105,1,65,32)
order by (
case RecordID
when 22 then 1
when 15 then 2
when 105 then 3
when 1 then 4
when 65 then 5
when 32 then 6 end)
If you need the output to appear in a particular order, then you need to specify that order, using something the server can sort. Not knowing which engine you're working against, the general scheme would be to create a temp table or use rowset constructors to pair each record ID with its desired sort order.
E.g. (SQL Server)
declare #T table (RecordID int,Position int)
insert into #T (RecordID,Position)
select 22,1 union all
select 15,2 union all
select 105,3 union all
select 1,4 union all
select 65,5 union all
select 32,6
select * from Table t inner join #T t2 on t.RecordID = t2.RecordID order by t2.Position
I'd to the ordering in the client, but if you really want to do it in SQL, do it like this:
declare #T table (id int identity(1,1), RecordID int)
insert into #T (RecordID)
values (22), (15), (105), (1), (65), (32)
select * from
[table] t
inner join #t s on t.id=s.recordid
where t.id in (22, 15, 105, 1, 65, 32)
order by s.id
(works in SQL Server 2008)
Yes. You add the ORDER BY recordedid clause at the end.
The last time I had to do this I ended up doing a union all and generating a select statement for each id, i.e.
select * from Table where RecordID = 22
union all
select * from table where recordid = 15
etc.
It was a pain but it worked.
Use ORDER BY against the RecordID
Select * from Table where RecordID in (22,15,105,1,65,32) ORDER BY RecordID