Attain value present in earlier set but missing in later set - sql

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
)

Related

How to find consecutive unique values in a column ordered in the same way as the original table?

So I have a table with name ID, Time and Values. Values column has multiple same values. Time is of datetime2 datatype and is in ascending order of unique values. ID is also a primary key with int values.
I want the result as continuous unique values of Values column in the same order as it appears in the original table.
I have tried using window function Lead to find next values from a given value of Values column, but I am not sure how to find unique next values.
Original table "TableTest"
ID Time Value
1 2019-06-24 18:23:04.0400000 A
2 2019-06-24 18:23:04.0420000 A
3 2019-06-24 18:23:04.0450000 B
4 2019-06-24 18:23:04.0670000 A
5 2019-06-24 18:23:04.0690000 C
6 2019-06-24 18:23:04.0700000 C
Since the "A" with ID 4 is not coming in continuation with the "A" of ID 1, I want it in my result. Hence I want the result as below.
ID Time Value
1 2019-06-24 18:23:04.0400000 A
3 2019-06-24 18:23:04.0450000 B
4 2019-06-24 18:23:04.0670000 A
5 2019-06-24 18:23:04.0690000 C
Try this below-
WITH your_table(ID,Time,Value)
AS
(
SELECT 1,'2019-06-24 18:23:04.0400000','A' UNION ALL
SELECT 2,'2019-06-24 18:23:04.0420000','A' UNION ALL
SELECT 3,'2019-06-24 18:23:04.0450000','B' UNION ALL
SELECT 4,'2019-06-24 18:23:04.0670000','A' UNION ALL
SELECT 5,'2019-06-24 18:23:04.0690000','C' UNION ALL
SELECT 6,'2019-06-24 18:23:04.0700000','C'
)
SELECT A.ID,A.Time,A.Value
FROM
(
SELECT *, LAG(Value) OVER(ORDER BY ID) Lag_Value
FROM your_table
)A
WHERE value <> Lag_Value OR Lag_Value IS NULL
Actually you just need to filter out the rows with same [value] as the previous row.
If your [id] column is not continious, then you need to use a ROW_NUMBER() function to generate continiou row id and join on it.
WITH TABLE_TEST AS (
SELECT 1 AS ID, CAST('2019-06-24 18:23:04.0400000' AS DATETIME2) AS Time, 'A' AS [VALUE] UNION
SELECT 2 AS ID, CAST('2019-06-24 18:23:04.0420000' AS DATETIME2) AS Time, 'A' AS [VALUE] UNION
SELECT 3 AS ID, CAST('2019-06-24 18:23:04.0450000' AS DATETIME2) AS Time, 'B' AS [VALUE] UNION
SELECT 4 AS ID, CAST('2019-06-24 18:23:04.0670000' AS DATETIME2) AS Time, 'A' AS [VALUE] UNION
SELECT 5 AS ID, CAST('2019-06-24 18:23:04.0690000' AS DATETIME2) AS Time, 'C' AS [VALUE] UNION
SELECT 6 AS ID, CAST('2019-06-24 18:23:04.0700000' AS DATETIME2) AS Time, 'C' AS [VALUE]
)
SELECT
t1.ID
,t1.Time
,t1.value
FROM TABLE_TEST t1
LEFT JOIN TABLE_TEST t2
ON t1.id = t2.id + 1
WHERE t1.value<> ISNULL(t2.value, '')
Compare each row with its previous row and if value of Value is same ignore the current row. t1.ID = 1 because first row will always be in the output.
select
*
from
TableTest t1, TableTest t2
where
t1.ID = 1 or
(t1.ID - 1 = t2.ID and
t1.Value != t2.Value)

Duplicate Counts - TSQL

I want to get All records that has duplicate values for SOME of the fields (i.e. Key columns).
My code:
CREATE TABLE #TEMP (ID int, Descp varchar(5), Extra varchar(6))
INSERT INTO #Temp
SELECT 1,'One','Extra1'
UNION ALL
SELECT 2,'Two','Extra2'
UNION ALL
SELECT 3,'Three','Extra3'
UNION ALL
SELECT 1,'One','Extra4'
SELECT ID, Descp, Extra FROM #TEMP
;WITH Temp_CTE AS
(SELECT *
, ROW_NUMBER() OVER (PARTITION BY ID, Descp ORDER BY (SELECT 0))
AS DuplicateRowNumber
FROM #TEMP
)
SELECT * FROM Temp_cte
DROP TABLE #TEMP
The last column tells me how many times each row has appeared based on ID and Descp values.
I want that row but I ALSO need another column* that indicates both rows for ID = 1 and Descp = 'One' has showed up more than once.
So an extra column* (i.e. MultipleOccurances (bool)) which has 1 for two rows with ID = 1 and Descp = 'One' and 0 for other rows as they are only showing up once.
How can I achieve that? (I want to avoid using Count(1)>1 or something if possible.
Edit:
Desired output:
ID Descp Extra DuplicateRowNumber IsMultiple
1 One Extra1 1 1
1 One Extra4 2 1
2 Two Extra2 1 0
3 Three Extra3 1 0
SQL Fiddle
You say "I want to avoid using Count" but it is probably the best way. It uses the partitioning you already have on the row_number
SELECT *,
ROW_NUMBER() OVER (PARTITION BY ID, Descp
ORDER BY (SELECT 0)) AS DuplicateRowNumber,
CASE
WHEN COUNT(*) OVER (PARTITION BY ID, Descp) > 1 THEN 1
ELSE 0
END AS IsMultiple
FROM #Temp
And the execution plan just shows a single sort
Well, I have this solution, but using a Count...
SELECT T1.*,
ROW_NUMBER() OVER (PARTITION BY T1.ID, T1.Descp ORDER BY (SELECT 0)) AS DuplicateRowNumber,
CASE WHEN T2.C = 1 THEN 0 ELSE 1 END MultipleOcurrences FROM #temp T1
INNER JOIN
(SELECT ID, Descp, COUNT(1) C FROM #TEMP GROUP BY ID, Descp) T2
ON T1.ID = T2.ID AND T1.Descp = T2.Descp

Next/previous record based on current

I have a table which is not sorted by any of column. Is there any way to select next/previous record if I know only Id of current? (I'm using mssql)
Id Label Date
---------------------
1 label1 2011-01-10
7 label2 2011-01-15 -- how to get previous?
5 label3 2011-01-12 -- I know id of this record
10 label10 2011-01-25 -- how to get next?
12 label8 2011-01-13
2 label5 2011-01-29
Thanks in advance!
try this:
VALUES (1, 'label1', '2011-01-10'), (7, 'label2', '2011-01-15'),
(5, 'label3', '2011-01-12'), (10, 'label10', '2011-01-25'),
(12, 'label8', '2011-01-13'), (2, 'label5', '2011-01-29')
select * from table007;
Declare #inptID int=12;
;WITH CTE
as
(
select *, ROW_NUMBER() over (order by (select 0)) as rn
from table007
)
select *
from CTE
where rn in( select rn-1 from CTE where id = #inptID)
union all
select * from CTE where rn in(select rn + 1 from CTE where id = #inptID);
SQL Fiddle Demo
DEMO
If it is not sorted by any column, there is no definitive next or previous record. Data in SQL Server has no order, other than that specified by an ORDER BY clause.
If you really want the previous from the list you enclosed, here is a way.
declare #t table(Id int, Label varchar(10), Date date, s int identity(1,1))
insert #t (id, label, date)
values(1,'label1','2011-01-10'),(7,'label2','2011-01-15'),
(5,'label3','2011-01-12'),(10,'label10','2011-01-25'),
(12,'label8','2011-01-13'),(2,'label5','2011-01-29')
--select the data with a self join
select t1.id as previous_id, t2.id, t2.Label, t2.Date, t3.id, t3.id as next_id
from #t t1
right join
#t t2 on t1.s + 1 = t2.s
left join
#t t3 on t2.s = t3.s - 1

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.

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;